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