1 // Copyright (c) 2015 Sergio Gonzalez. All rights reserved.
2 // License: https://github.com/serge-rgb/milton#license
3 
4 #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM "gl.h"
5 #include <imgui.h>
6 #include "imgui_impl_sdl.h"
7 #include "imgui_impl_opengl3.h"
8 
9 #include "milton.h"
10 #include "gl_helpers.h"
11 #include "gui.h"
12 #include "persist.h"
13 #include "bindings.h"
14 
15 
16 static void
cursor_set_and_show(SDL_Cursor * cursor)17 cursor_set_and_show(SDL_Cursor* cursor)
18 {
19     SDL_SetCursor(cursor);
20     platform_cursor_show();
21 }
22 
23 LayoutType
get_current_keyboard_layout()24 get_current_keyboard_layout()
25 {
26     LayoutType layout = LayoutType_QWERTY;  // Default to QWERTY bindings.
27 
28     char keys[] = {
29         (char)SDL_GetKeyFromScancode(SDL_SCANCODE_Q),
30         (char)SDL_GetKeyFromScancode(SDL_SCANCODE_R),
31         (char)SDL_GetKeyFromScancode(SDL_SCANCODE_Y),
32         '\0',
33     };
34 
35     if ( strcmp(keys, "qry") == 0 ) {
36         layout = LayoutType_QWERTY;
37     }
38     else if ( strcmp(keys, "ary") == 0 ) {
39         layout = LayoutType_AZERTY;
40     }
41     else if ( strcmp(keys, "qrz") == 0 ) {
42         layout = LayoutType_QWERTZ;
43     }
44     else if ( strcmp(keys, "q,f") ) {
45         layout = LayoutType_DVORAK;
46     }
47     else if ( strcmp(keys, "qwj") == 0 ) {
48         layout = LayoutType_COLEMAK;
49     }
50 
51     return layout;
52 }
53 
54 void
shortcut_handle_key(Milton * milton,PlatformState * platform,SDL_Event * event,MiltonInput * input,b32 is_keyup)55 shortcut_handle_key(Milton* milton, PlatformState* platform, SDL_Event* event, MiltonInput* input, b32 is_keyup)
56 {
57     ImGuiIO& io = ImGui::GetIO();
58 
59     if (io.WantCaptureKeyboard) {
60         int key = event->key.keysym.scancode;
61         IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
62         io.KeysDown[key] = (event->type == SDL_KEYDOWN);
63         io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
64         io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
65         io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
66         io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
67     }
68     else {
69         MiltonBindings* bindings = &milton->settings->bindings;
70 
71         SDL_Keymod m = SDL_GetModState();
72         SDL_Keycode k = event->key.keysym.sym;
73 
74         i8 active_key = 0;
75         if (k >= 1 && k <= 127) {
76             active_key = k;
77         }
78         else {
79             switch (k) {
80                 case SDLK_F1:  { active_key = Binding::F1;  } break;
81                 case SDLK_F2:  { active_key = Binding::F2;  } break;
82                 case SDLK_F3:  { active_key = Binding::F3;  } break;
83                 case SDLK_F4:  { active_key = Binding::F4;  } break;
84                 case SDLK_F5:  { active_key = Binding::F5;  } break;
85                 case SDLK_F6:  { active_key = Binding::F6;  } break;
86                 case SDLK_F7:  { active_key = Binding::F7;  } break;
87                 case SDLK_F8:  { active_key = Binding::F8;  } break;
88                 case SDLK_F9:  { active_key = Binding::F9;  } break;
89                 case SDLK_F10: { active_key = Binding::F10; } break;
90                 case SDLK_F11: { active_key = Binding::F11; } break;
91                 case SDLK_F12: { active_key = Binding::F12; } break;
92                 default: {  } break;
93             }
94         }
95 
96         u32 active_modifiers = 0;
97 
98         if (m & KMOD_CTRL) { active_modifiers |= Modifier_CTRL; }
99         if (m & KMOD_SHIFT) { active_modifiers |= Modifier_SHIFT; }
100         if (m & KMOD_GUI) { active_modifiers |= Modifier_WIN; }
101         if (m & KMOD_ALT) { active_modifiers |= Modifier_ALT; }
102         if (SDL_GetKeyboardState(NULL)[SDL_SCANCODE_SPACE]) { active_modifiers |= Modifier_SPACE; }
103 
104         if (is_keyup) {
105 
106             // Switch on k again, this time catching when some of the modifiers were released.
107             switch (k) {
108                 case SDLK_LSHIFT:
109                 case SDLK_RSHIFT: {
110                     active_modifiers |= Modifier_SHIFT;
111                 } break;
112                 case SDLK_LALT:
113                 case SDLK_RALT: {
114                     active_modifiers |= Modifier_ALT;
115                 } break;
116                 case SDLK_LGUI:
117                 case SDLK_RGUI: {
118                     active_modifiers |= Modifier_WIN;
119                 } break;
120                 case SDLK_LCTRL:
121                 case SDLK_RCTRL: {
122                     active_modifiers |= Modifier_CTRL;
123                 } break;
124             }
125 
126             for (sz i = Action_COUNT + 1; i < Action_COUNT_WITH_RELEASE; ++i) {
127                 Binding* b = &bindings->bindings[i];
128                 if ( (!event->key.repeat || b->accepts_repeats) &&
129                      active_modifiers == b->modifiers &&
130                      active_key == b->bound_key &&
131                      b->on_release &&
132                      b->action != Action_NONE ) {
133                     binding_dispatch_action(b->action, input, milton, platform->pointer);
134                     platform->force_next_frame = true;
135                 }
136             }
137         }
138         // keydown
139         else  {
140             for (sz i = 0; i < Action_COUNT; ++i) {
141                 Binding* b = &bindings->bindings[i];
142 
143                 if ( (!event->key.repeat || b->accepts_repeats) &&
144                      active_modifiers == b->modifiers &&
145                      active_key == b->bound_key &&
146                      !b->on_release &&
147                      b->action != Action_NONE ) {
148                     binding_dispatch_action(b->action, input, milton, platform->pointer);
149                     platform->force_next_frame = true;
150                 }
151             }
152 
153         }
154         if ( k == SDLK_SPACE && !is_keyup ) {
155             platform->is_space_down = true;
156         }
157     }
158 }
159 
160 void
panning_update(PlatformState * platform)161 panning_update(PlatformState* platform)
162 {
163     auto reset_pan_start = [platform]() {
164         platform->pan_start = VEC2L(platform->pointer);
165         platform->pan_point = platform->pan_start;  // No huge pan_delta at beginning of pan.
166     };
167 
168     platform->was_panning = platform->is_panning;
169 
170     // Panning from GUI menu, waiting for input
171     if ( platform->waiting_for_pan_input ) {
172         if ( platform->is_pointer_down ) {
173             platform->waiting_for_pan_input = false;
174             platform->is_panning = true;
175             reset_pan_start();
176         }
177         // Space cancels waiting
178         if ( platform->is_space_down ) {
179             platform->waiting_for_pan_input = false;
180         }
181     }
182     else {
183         if ( platform->is_panning ) {
184             if ( (!platform->is_pointer_down && !platform->is_space_down)
185                  || !platform->is_pointer_down ) {
186                 platform->is_panning = false;
187             }
188             else {
189                 platform->pan_point = VEC2L(platform->pointer);
190             }
191         }
192         else {
193             if ( (platform->is_space_down && platform->is_pointer_down)
194                  || platform->is_middle_button_down ) {
195                 platform->is_panning = true;
196                 reset_pan_start();
197             }
198         }
199     }
200 }
201 
202 MiltonInput
sdl_event_loop(Milton * milton,PlatformState * platform)203 sdl_event_loop(Milton* milton, PlatformState* platform)
204 {
205     MiltonInput milton_input = {};
206     milton_input.mode_to_set = MiltonMode::MODE_COUNT;
207 
208     b32 pointer_up = false;
209 
210     v2i input_point = {};
211 
212     platform->num_pressure_results = 0;
213     platform->num_point_results = 0;
214     platform->keyboard_layout = get_current_keyboard_layout();
215 
216     SDL_Event event;
217     while ( SDL_PollEvent(&event) ) {
218         ImGui_ImplSDL2_ProcessEvent(&event);
219 
220         SDL_Keymod keymod = SDL_GetModState();
221 
222 #if 0
223         if ( (keymod & KMOD_ALT) )
224         {
225             milton_input.mode_to_set = MiltonMode_EYEDROPPER;
226         }
227 #endif
228 
229 
230 #if defined(_MSC_VER)
231 #pragma warning (push)
232 #pragma warning (disable : 4061)
233 #endif
234         switch ( event.type ) {
235             case SDL_QUIT: {
236                 platform_cursor_show();
237                 milton_try_quit(milton);
238             } break;
239             case SDL_SYSWMEVENT: {
240                 f32 pressure = NO_PRESSURE_INFO;
241                 SDL_SysWMEvent sysevent = event.syswm;
242                 EasyTabResult er = EASYTAB_EVENT_NOT_HANDLED;
243                 if (!EasyTab) { break; }
244 
245                 i32 bit_touch_old = (EasyTab->Buttons & EasyTab_Buttons_Pen_Touch);
246 
247                 er = platform_handle_sysevent(platform, &sysevent);
248 
249                 if ( er == EASYTAB_OK ) {
250                     i32 bit_touch = (EasyTab->Buttons & EasyTab_Buttons_Pen_Touch);
251                     i32 bit_lower = (EasyTab->Buttons & EasyTab_Buttons_Pen_Lower);
252                     i32 bit_upper = (EasyTab->Buttons & EasyTab_Buttons_Pen_Upper);
253 
254                     // Pen in use but not drawing
255                     b32 taking_pen_input = EasyTab->PenInProximity
256                                            && bit_touch
257                                            && !( bit_upper || bit_lower );
258 
259                     if ( taking_pen_input ) {
260                         platform->is_pointer_down = true;
261 
262                         for ( int pi = 0; pi < EasyTab->NumPackets; ++pi ) {
263                             v2l point = { EasyTab->PosX[pi], EasyTab->PosY[pi] };
264 
265                             platform_point_to_pixel(platform, &point);
266 
267                             if ( point.x >= 0 && point.y >= 0 ) {
268                                 if ( platform->num_point_results < MAX_INPUT_BUFFER_ELEMS ) {
269                                     milton_input.points[platform->num_point_results++] = point;
270                                 }
271                                 if ( platform->num_pressure_results < MAX_INPUT_BUFFER_ELEMS ) {
272                                     milton_input.pressures[platform->num_pressure_results++] = EasyTab->Pressure[pi];
273                                 }
274                             }
275                         }
276                     }
277 
278                     if ( !bit_touch && bit_touch_old ) {
279                         pointer_up = true;  // Wacom does not seem to send button-up messages after
280                                             // using stylus buttons while stroking.
281                     }
282 
283 
284                     if ( EasyTab->NumPackets > 0 ) {
285                         v2i point = { EasyTab->PosX[EasyTab->NumPackets-1], EasyTab->PosY[EasyTab->NumPackets-1] };
286 
287                         platform_point_to_pixel_i(platform, &point);
288 
289                         platform->pointer = point;
290                     }
291                 }
292 
293                 if (er == EASYTAB_NEEDS_REINIT) {
294                     platform_dialog("Tablet information changed. You might want to restart Milton", "Tablet info changed.");
295                 }
296             } break;
297             case SDL_MOUSEBUTTONDOWN: {
298                 if ( event.button.windowID != platform->window_id ) {
299                     break;
300                 }
301 
302                 if (   (event.button.button == SDL_BUTTON_LEFT && ( EasyTab == NULL || !EasyTab->PenInProximity))
303                      || event.button.button == SDL_BUTTON_MIDDLE
304                      // Ignoring right click events for now
305                      /*|| event.button.button == SDL_BUTTON_RIGHT*/ ) {
306 
307                     if ( ImGui::GetIO().WantCaptureMouse ) {
308                         platform->force_next_frame = true;
309                     }
310                     else {
311                         v2l long_point = { event.button.x, event.button.y };
312 
313                         platform_point_to_pixel(platform, &long_point);
314 
315                         v2i point = v2i{(int)long_point.x, (int)long_point.y};
316 
317                         if ( !platform->is_panning && point.x >= 0 && point.y > 0 ) {
318                             milton_input.click = point;
319 
320                             platform->is_pointer_down = true;
321                             platform->pointer = point;
322                             platform->is_middle_button_down = (event.button.button == SDL_BUTTON_MIDDLE);
323 
324                             if ( platform->num_point_results < MAX_INPUT_BUFFER_ELEMS ) {
325                                 milton_input.points[platform->num_point_results++] = VEC2L(point);
326                             }
327                             if ( platform->num_pressure_results < MAX_INPUT_BUFFER_ELEMS ) {
328                                 milton_input.pressures[platform->num_pressure_results++] = NO_PRESSURE_INFO;
329                             }
330                         }
331                     }
332                 }
333             } break;
334             case SDL_MOUSEBUTTONUP: {
335                 if ( event.button.windowID != platform->window_id ) {
336                     break;
337                 }
338                 if ( event.button.button == SDL_BUTTON_LEFT
339                      || event.button.button == SDL_BUTTON_MIDDLE
340                      || event.button.button == SDL_BUTTON_RIGHT ) {
341                     if ( event.button.button == SDL_BUTTON_MIDDLE ) {
342                         platform->is_middle_button_down = false;
343                     }
344                     if ( ImGui::GetIO().WantCaptureMouse ) {
345                         // NOTE(ameen): button-click events that cause UI changes have 1 frame delay to update.
346                         platform->force_next_frame = true;
347                     }
348                     pointer_up = true;
349                     milton_input.flags |= MiltonInputFlags_CLICKUP;
350                     milton_input.flags |= MiltonInputFlags_END_STROKE;
351                 }
352             } break;
353             case SDL_MOUSEMOTION: {
354                 if (event.motion.windowID != platform->window_id) {
355                     break;
356                 }
357 
358                 input_point = {event.motion.x, event.motion.y};
359 
360                 platform_point_to_pixel_i(platform, &input_point);
361 
362                 platform->pointer = input_point;
363 
364                 // In case the wacom driver craps out, or anything goes wrong (like the event queue
365                 // overflowing ;)) then we default to receiving WM_MOUSEMOVE. If we catch a single
366                 // point, then it's fine. It will get filtered out in milton_stroke_input
367 
368                 if (EasyTab == NULL || !EasyTab->PenInProximity) {
369                     if (platform->is_pointer_down) {
370                         if (!platform->is_panning &&
371                             (input_point.x >= 0 && input_point.y >= 0)) {
372                             if (platform->num_point_results < MAX_INPUT_BUFFER_ELEMS) {
373                                 milton_input.points[platform->num_point_results++] = VEC2L(input_point);
374                             }
375                             if (platform->num_pressure_results < MAX_INPUT_BUFFER_ELEMS) {
376                                 milton_input.pressures[platform->num_pressure_results++] = NO_PRESSURE_INFO;
377                             }
378                         }
379                     }
380                 }
381                 break;
382             }
383             case SDL_MOUSEWHEEL: {
384                 if ( event.wheel.windowID != platform->window_id ) {
385                     break;
386                 }
387                 if ( !ImGui::GetIO().WantCaptureMouse ) {
388                     milton_input.scale += event.wheel.y;
389                     v2i zoom_center = platform->pointer;
390 
391                     milton_set_zoom_at_point(milton, zoom_center);
392                     // ImGui has a delay of 1 frame when displaying zoom info.
393                     // Force next frame to have the value up to date.
394                     platform->force_next_frame = true;
395                 }
396 
397                 break;
398             }
399             case SDL_KEYDOWN: {
400                 shortcut_handle_key(milton, platform, &event, &milton_input, /*is_keyup*/false);
401             } break;
402             case SDL_KEYUP: {
403                 if ( event.key.windowID != platform->window_id ) {
404                     break;
405                 }
406 
407                 SDL_Keycode keycode = event.key.keysym.sym;
408 
409                 if ( keycode == SDLK_SPACE ) {
410                     platform->is_space_down = false;
411                 }
412                 shortcut_handle_key(milton, platform, &event, &milton_input, /*is_keyup*/true);
413             } break;
414             case SDL_WINDOWEVENT: {
415                 if ( platform->window_id != event.window.windowID ) {
416                     break;
417                 }
418                 switch ( event.window.event ) {
419                     // Just handle every event that changes the window size.
420                 case SDL_WINDOWEVENT_MOVED:
421                     platform->num_point_results = 0;
422                     platform->num_pressure_results = 0;
423                     platform->is_pointer_down = false;
424                     break;
425                 case SDL_WINDOWEVENT_RESIZED:
426                 case SDL_WINDOWEVENT_SIZE_CHANGED: {
427 
428                     v2i size = { event.window.data1, event.window.data2 };
429                     platform_point_to_pixel_i(platform, &size);
430 
431                     platform->width = size.w;
432                     platform->height = size.h;
433 
434 
435                     milton_input.flags |= MiltonInputFlags_FULL_REFRESH;
436                     glViewport(0, 0, platform->width, platform->height);
437                     break;
438                 }
439                 case SDL_WINDOWEVENT_LEAVE:
440                     if ( event.window.windowID != platform->window_id ) {
441                         break;
442                     }
443                     if ( milton->current_mode != MiltonMode::DRAG_BRUSH_SIZE ) {
444                         platform_cursor_show();
445                     }
446                     break;
447                     // --- A couple of events we might want to catch later...
448                 case SDL_WINDOWEVENT_ENTER:
449                     {
450                     } break;
451                     break;
452                 case SDL_WINDOWEVENT_FOCUS_GAINED:
453                     break;
454                 default:
455                     break;
456                 }
457             } break;
458             default: {
459                 break;
460             }
461         }
462 #if defined(_MSC_VER)
463 #pragma warning (pop)
464 #endif
465         if ( platform->should_quit ) {
466             break;
467         }
468     }  // ---- End of SDL event loop
469 
470     if ( pointer_up ) {
471         // Add final point
472         if ( !platform->is_panning && platform->is_pointer_down ) {
473             milton_input.flags |= MiltonInputFlags_END_STROKE;
474             input_point = { event.button.x, event.button.y };
475 
476             platform_point_to_pixel_i(platform, &input_point);
477 
478             if ( platform->num_point_results < MAX_INPUT_BUFFER_ELEMS ) {
479                 milton_input.points[platform->num_point_results++] = VEC2L(input_point);
480             }
481         }
482         platform->is_pointer_down = false;
483 
484         platform->num_point_results = 0;
485     }
486 
487     return milton_input;
488 }
489 
490 // ---- milton_main
491 
492 int
milton_main(bool is_fullscreen,char * file_to_open)493 milton_main(bool is_fullscreen, char* file_to_open)
494 {
495     {
496         static char* release_string
497 #if MILTON_DEBUG
498                 = "Debug";
499 #else
500                 = "Release";
501 #endif
502 
503         milton_log("Running Milton %d.%d.%d (%s) \n", MILTON_MAJOR_VERSION, MILTON_MINOR_VERSION, MILTON_MICRO_VERSION, release_string);
504     }
505     // Note: Possible crash regarding SDL_main entry point.
506     // Note: Event handling, File I/O and Threading are initialized by default
507     milton_log("Initializing SDL... ");
508     SDL_Init(SDL_INIT_VIDEO);
509     milton_log("Done.\n");
510 
511     PlatformState platform = {};
512 
513     PlatformSettings prefs = {};
514 
515     milton_log("Loading preferences...\n");
516     if ( platform_settings_load(&prefs) ) {
517         milton_log("Prefs file window size: %dx%d\n", prefs.width, prefs.height);
518     }
519 
520     i32 window_width = 1280;
521     i32 window_height = 800;
522     {
523         if (prefs.width > 0 && prefs.height > 0) {
524             if ( !is_fullscreen ) {
525                 window_width = prefs.width;
526                 window_height = prefs.height;
527             }
528             else {
529                 // TODO: Does this work on retina mac?
530                 milton_log("Running fullscreen\n");
531                 SDL_DisplayMode dm;
532                 SDL_GetDesktopDisplayMode(0, &dm);
533 
534                 window_width = dm.w;
535                 window_height = dm.h;
536             }
537         }
538     }
539 
540     milton_log("Window dimensions: %dx%d \n", window_width, window_height);
541 
542     platform.ui_scale = 1.0f;
543 
544     platform.keyboard_layout = get_current_keyboard_layout();
545 
546 #if USE_GL_3_2
547     i32 gl_version_major = 3;
548     i32 gl_version_minor = 2;
549     milton_log("Requesting OpenGL 3.2 context.\n");
550 #else
551     i32 gl_version_major = 2;
552     i32 gl_version_minor = 1;
553     milton_log("Requesting OpenGL 2.1 context.\n");
554 #endif
555 
556     SDL_Window* window = NULL;
557     milton_log("Creating Milton Window\n");
558 
559     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
560     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
561     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_version_major);
562     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_version_minor);
563     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
564     #if USE_GL_3_2
565         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
566     #endif
567     #if MILTON_DEBUG
568         SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
569     #endif
570 
571     #if MULTISAMPLING_ENABLED
572         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
573         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, MSAA_NUM_SAMPLES);
574     #endif
575 
576     Uint32 sdl_window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
577 
578     if (is_fullscreen) {
579         sdl_window_flags |= SDL_WINDOW_FULLSCREEN;
580     }
581     else {
582         sdl_window_flags |= SDL_WINDOW_RESIZABLE;
583     }
584 
585     window = SDL_CreateWindow("Milton",
586                               SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
587                               window_width, window_height,
588                               sdl_window_flags);
589 
590     if ( !window ) {
591         milton_log("SDL Error: %s\n", SDL_GetError());
592         milton_die_gracefully("SDL could not create window\n");
593     }
594 
595     platform.window = window;
596 
597     // Milton works in pixels, but macOS works distinguishing "points" and
598     // "pixels", with most APIs working in points.
599 
600     v2l size_px = { window_width, window_height };
601     platform_point_to_pixel(&platform, &size_px);
602 
603     platform.width = size_px.w;
604     platform.height = size_px.h;
605 
606     SDL_GLContext gl_context = SDL_GL_CreateContext(window);
607 
608     if ( !gl_context ) {
609         milton_die_gracefully("Could not create OpenGL context\n");
610     }
611 
612     if ( !gl::load() ) {
613         milton_die_gracefully("Milton could not load the necessary OpenGL functionality. Exiting.");
614     }
615 
616     // Init ImGUI
617     ImGui::CreateContext();
618 
619 
620 #if USE_GL_3_2
621     const char* gl_version = "#version 330 \n";
622 #else
623     const char* gl_version = "#version 120 \n";
624 #endif
625 
626     ImGui_ImplSDL2_InitForOpenGL(window, &gl_context);
627     ImGui_ImplOpenGL3_Init(gl_version);
628 
629     SDL_GL_SetSwapInterval(1);
630 
631     int actual_major = 0;
632     int actual_minor = 0;
633     glGetIntegerv(GL_MAJOR_VERSION, &actual_major);
634     glGetIntegerv(GL_MINOR_VERSION, &actual_minor);
635     if ( !(actual_major == 0 && actual_minor == 0)
636          && (actual_major < gl_version_major
637              || (actual_major == gl_version_major && actual_minor < gl_version_minor)) ) {
638         milton_die_gracefully("This graphics driver does not support OpenGL 2.1+");
639     }
640     milton_log("Created OpenGL context with version %s\n", glGetString(GL_VERSION));
641     milton_log("    and GLSL %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
642 
643     // ==== Initialize milton
644 
645     Milton* milton = arena_bootstrap(Milton, root_arena, 1024*1024);
646 
647     // Ask for native events to poll tablet events.
648     SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
649 
650     SDL_SysWMinfo sysinfo;
651     SDL_VERSION(&sysinfo.version);
652 
653     // Platform-specific setup
654 #if defined(_MSC_VER)
655 #pragma warning (push, 0)
656 #endif
657     if ( SDL_GetWindowWMInfo( window, &sysinfo ) ) {
658         platform_init(&platform, &sysinfo);
659     }
660     else {
661         milton_die_gracefully("Can't get system info!\n");
662     }
663 #if defined(_MSC_VER)
664 #pragma warning (pop)
665 #endif
666 
667     platform.ui_scale = platform_ui_scale(&platform);
668     milton_log("UI scale is %f\n", platform.ui_scale);
669     // Initialize milton
670     PATH_CHAR* file_to_open_ = NULL;
671     PATH_CHAR buffer[MAX_PATH] = {};
672 
673     if ( file_to_open ) {
674         file_to_open_ = (PATH_CHAR*)buffer;
675     }
676 
677     str_to_path_char(file_to_open, (PATH_CHAR*)file_to_open_, MAX_PATH*sizeof(*file_to_open_));
678 
679     milton_init(milton, platform.width, platform.height, platform.ui_scale, (PATH_CHAR*)file_to_open_);
680     milton->platform = &platform;
681     milton->gui->menu_visible = true;
682     if ( is_fullscreen ) {
683         milton->gui->menu_visible = false;
684     }
685 
686     milton_resize_and_pan(milton, {}, {platform.width, platform.height});
687 
688     platform.window_id = SDL_GetWindowID(window);
689 
690     i32 display_hz = platform_monitor_refresh_hz();
691 
692     platform_setup_cursor(&milton->root_arena, &platform);
693 
694     // Sometimes SDL sets the window position such that it's impossible to move
695     // without using Windows shortcuts that not everyone knows. Check if this
696     // is the case and set a good default.
697     if (!is_fullscreen) {
698         const int pixel_padding = platform_titlebar_height(&platform);
699         int x = 0, y = 0;
700         SDL_GetWindowPosition(window, &x, &y);
701         SDL_SetWindowPosition(window, min(max(0, x), platform.width - pixel_padding), min(max(pixel_padding, y), platform.height  - pixel_padding));
702     }
703 
704     // ImGui setup
705     {
706         milton_log("ImGUI setup\n");
707         ImGuiIO& io = ImGui::GetIO();
708         io.IniFilename = NULL;  // Don't save any imgui.ini file
709         PATH_CHAR fname[MAX_PATH] = TO_PATH_STR("/usr/local/share/milton/Carlito.ttf");
710         platform_fname_at_exe(fname, MAX_PATH);
711         FILE* fd = platform_fopen(fname, TO_PATH_STR("rb"));
712 
713         if ( fd ) {
714             size_t  ttf_sz = 0;
715             void*   ttf_data = NULL;
716             //ImFont* im_font =  io.Fonts->ImFontAtlas::AddFontFromFileTTF("carlito.ttf", 14);
717             // Load file to memory
718             if ( fseek(fd, 0, SEEK_END) == 0 ) {
719                 long ttf_sz_long = ftell(fd);
720                 if ( ttf_sz_long != -1 ) {
721                     ttf_sz = (size_t)ttf_sz_long;
722                     if ( fseek(fd, 0, SEEK_SET) == 0 ) {
723                         ttf_data = ImGui::MemAlloc(ttf_sz);
724                         if ( ttf_data ) {
725                             if ( fread(ttf_data, 1, ttf_sz, fd) == ttf_sz ) {
726                                 ImFont* im_font = io.Fonts->ImFontAtlas::AddFontFromMemoryTTF(ttf_data, (int)ttf_sz, int(14*platform.ui_scale));
727                             }
728                             else {
729                                 milton_log("WARNING: Error reading TTF file\n");
730                             }
731                         }
732                         else {
733                             milton_log("WARNING: could not allocate data for font!\n");
734                         }
735                     }
736                 }
737             }
738             fclose(fd);
739         }
740     }
741     // Initalize system cursors
742     {
743         platform.cursor_default   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
744         platform.cursor_hand      = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
745         platform.cursor_crosshair = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
746         platform.cursor_sizeall   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
747 
748         cursor_set_and_show(platform.cursor_default);
749     }
750 
751     // ---- Main loop ----
752 
753     while ( !platform.should_quit ) {
754         PROFILE_GRAPH_END(system);
755         PROFILE_GRAPH_BEGIN(polling);
756 
757         u64 frame_start_us = perf_counter();
758 
759         ImGuiIO& imgui_io = ImGui::GetIO();
760 
761         MiltonInput milton_input = sdl_event_loop(milton, &platform);
762 
763         // Handle pen orientation to switch to eraser or pen.
764         if ( EasyTab != NULL && EasyTab->PenInProximity ) {
765             static int previous_orientation = 0;
766 
767             // TODO: This logic needs to handle primitives, not just eraser/pen
768             bool changed = false;
769             if ( EasyTab->Orientation.Altitude < 0 && previous_orientation >= 0 ) {
770                 milton_input.mode_to_set = MiltonMode::ERASER;
771                 changed = true;
772             }
773             else if ( EasyTab->Orientation.Altitude > 0 && previous_orientation <= 0 ) {
774                 milton_input.mode_to_set = MiltonMode::PEN;
775                 changed = true;
776             }
777             if ( changed ) {
778                 previous_orientation = EasyTab->Orientation.Altitude;
779             }
780         }
781 
782         panning_update(&platform);
783 
784         static b32 first_run = true;
785         if ( first_run ) {
786             first_run = false;
787             milton_input.flags = MiltonInputFlags_FULL_REFRESH;
788         }
789 
790         {
791             int x = 0;
792             int y = 0;
793             SDL_GetMouseState(&x, &y);
794 
795             // Convert x,y to pixels
796             {
797                 v2l v = { (long)x, (long)y };
798                 platform_point_to_pixel(&platform, &v);
799                 x = v.x;
800                 y = v.y;
801             }
802 
803             // NOTE: Calling SDL_SetCursor more than once seems to cause flickering.
804 
805             // Handle system cursor and platform state related to current_mode
806             {
807                     static b32 was_exporting = false;
808 
809                     if ( platform.is_panning || platform.waiting_for_pan_input ) {
810                         cursor_set_and_show(platform.cursor_sizeall);
811                     }
812                     // Show resize icon
813                     #if !MILTON_HARDWARE_BRUSH_CURSOR
814                         #define PAD 20
815                         else if (x > milton->view->screen_size.w - PAD
816                              || x < PAD
817                              || y > milton->view->screen_size.h - PAD
818                              || y < PAD ) {
819                             cursor_set_and_show(platform.cursor_default);
820                         }
821                         #undef PAD
822                     #endif
823                     else if ( ImGui::GetIO().WantCaptureMouse ) {
824                         cursor_set_and_show(platform.cursor_default);
825                     }
826                     else if ( milton->current_mode == MiltonMode::EXPORTING ) {
827                         cursor_set_and_show(platform.cursor_crosshair);
828                         was_exporting = true;
829                     }
830                     else if ( was_exporting ) {
831                         cursor_set_and_show(platform.cursor_default);
832                         was_exporting = false;
833                     }
834                     else if ( milton->current_mode == MiltonMode::EYEDROPPER ) {
835                         cursor_set_and_show(platform.cursor_crosshair);
836                         platform.is_pointer_down = false;
837                     }
838                     else if ( milton->gui->visible
839                               && is_inside_rect_scalar(get_bounds_for_picker_and_colors(&milton->gui->picker), x,y) ) {
840                         cursor_set_and_show(platform.cursor_default);
841                     }
842                     else if ( milton->current_mode == MiltonMode::PEN ||
843                               milton->current_mode == MiltonMode::ERASER ||
844                               milton->current_mode == MiltonMode::PRIMITIVE ) {
845                         #if MILTON_HARDWARE_BRUSH_CURSOR
846                             cursor_set_and_show(platform.cursor_brush);
847                         #else
848                             platform_cursor_hide();
849                         #endif
850                     }
851                     else if ( milton->current_mode == MiltonMode::HISTORY ) {
852                         cursor_set_and_show(platform.cursor_default);
853                     }
854                     else if ( milton->current_mode == MiltonMode::DRAG_BRUSH_SIZE ) {
855                         platform_cursor_hide();
856                     }
857                     else if ( milton->current_mode != MiltonMode::PEN || milton->current_mode != MiltonMode::ERASER ) {
858                         platform_cursor_hide();
859                     }
860                 }
861         }
862         // NOTE:
863         //  Previous Milton versions had a hack where SDL was modified to call
864         //  milton_osx_tablet_hook, where it would fill up some arrays.
865         //  Here we would call milton_osx_poll_pressures to access those arrays.
866         //
867         //  OSX support is currently in limbo. Those two functions still exist
868         //  but are not called anywhere.
869         //    -Sergio 2018/07/08
870 
871         i32 input_flags = (i32)milton_input.flags;
872 
873         ImGui_ImplOpenGL3_NewFrame();
874         ImGui_ImplSDL2_NewFrame(window);
875         ImGui::NewFrame();
876 
877         // Avoid the case where we stop changing the brush size when we hover over GUI elements.
878         if ( milton->current_mode == MiltonMode::DRAG_BRUSH_SIZE ) {
879             ImGui::GetIO().WantCaptureMouse = false;
880         }
881 
882         // Clear our pointer input because we captured an ImGui widget!
883         if ( ImGui::GetIO().WantCaptureMouse ) {
884             platform.num_point_results = 0;
885             platform.is_pointer_down = false;
886             input_flags |= MiltonInputFlags_IMGUI_GRABBED_INPUT;
887         }
888 
889         milton_imgui_tick(&milton_input, &platform, milton, &prefs);
890 
891         // Clear pan delta if we are zooming
892         if ( milton_input.scale != 0 ) {
893             milton_input.pan_delta = {};
894         }
895         else if ( platform.is_panning ) {
896             input_flags |= MiltonInputFlags_PANNING;
897             platform.num_point_results = 0;
898         }
899         else if ( platform.was_panning ) {
900             // Just finished panning. Refresh the screen.
901             input_flags |= MiltonInputFlags_FULL_REFRESH;
902         }
903 
904         if ( platform.num_pressure_results < platform.num_point_results ) {
905             platform.num_point_results = platform.num_pressure_results;
906         }
907 
908         milton_input.flags = (MiltonInputFlags)( input_flags | (int)milton_input.flags );
909 
910         mlt_assert (platform.num_point_results <= platform.num_pressure_results);
911 
912         milton_input.input_count = platform.num_point_results;
913 
914         v2l pan_delta = platform.pan_point - platform.pan_start;
915         if (    pan_delta.x != 0
916              || pan_delta.y != 0
917              || platform.width != milton->view->screen_size.x
918              || platform.height != milton->view->screen_size.y ) {
919             milton_resize_and_pan(milton, pan_delta, {platform.width, platform.height});
920         }
921         milton_input.pan_delta = pan_delta;
922 
923         // Reset pan_start. Delta is not cumulative.
924         platform.pan_start = platform.pan_point;
925 
926         // ==== Update and render
927         PROFILE_GRAPH_END(polling);
928         PROFILE_GRAPH_BEGIN(GL);
929         milton_update_and_render(milton, &milton_input);
930         if ( !(milton->flags & MiltonStateFlags_RUNNING) ) {
931             platform.should_quit = true;
932         }
933         {
934             ImGuiIO& io = ImGui::GetIO(); (void)io;
935             ImGui::Render();
936             SDL_GL_MakeCurrent(window, gl_context);
937             PUSH_GRAPHICS_GROUP("ImGui");
938             ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
939             POP_GRAPHICS_GROUP();
940         }
941         PROFILE_GRAPH_END(GL);
942         PROFILE_GRAPH_BEGIN(system);
943         SDL_GL_SwapWindow(window);
944 
945         platform_event_tick();
946 
947         // Sleep if the frame took less time than the refresh rate.
948         u64 frame_time_us = perf_counter() - frame_start_us;
949 
950         f32 expected_us = (f32)1000000 / display_hz;
951         if ( frame_time_us < expected_us ) {
952             f32 to_sleep_us = expected_us - frame_time_us;
953             //  milton_log("Sleeping at least %d ms\n", (u32)(to_sleep_us/1000));
954             SDL_Delay((u32)(to_sleep_us/1000));
955         }
956         #if REDRAW_EVERY_FRAME
957         platform.force_next_frame = true;
958         #endif
959         // IMGUI events might update until the frame after they are created.
960         if ( !platform.force_next_frame ) {
961             SDL_WaitEvent(NULL);
962         }
963         else {
964             platform.force_next_frame = false;
965         }
966     }
967 
968     platform_deinit(&platform);
969 
970     arena_free(&milton->root_arena);
971 
972     // Save preferences.
973     v2l size =  { platform.width,platform.height };
974     platform_pixel_to_point(&platform, &size);
975 
976     prefs.width  = size.w;
977     prefs.height = size.h;
978     platform_settings_save(&prefs);
979 
980     SDL_Quit();
981 
982     return 0;
983 }
984