1 // Copyright (c) 2015 Sergio Gonzalez. All rights reserved.
2 // License: https://github.com/serge-rgb/milton#license
3 
4 
5 #include "gui.h"
6 
7 #include "localization.h"
8 #include "color.h"
9 #include "renderer.h"
10 #include "milton.h"
11 #include "persist.h"
12 #include "platform.h"
13 
14 
15 #define NUM_BUTTONS 5
16 #define BOUNDS_RADIUS_PX 80
17 
18 // If reset_gui is true, the default window position and size will be set.
19 void
gui_layer_window(MiltonInput * input,PlatformState * platform,Milton * milton,f32 brush_window_height,PlatformSettings * prefs,b32 reset_gui)20 gui_layer_window(MiltonInput* input, PlatformState* platform, Milton* milton, f32 brush_window_height, PlatformSettings* prefs, b32 reset_gui)
21 {
22     float ui_scale = milton->gui->scale;
23     MiltonGui* gui = milton->gui;
24     const Rect pbounds = get_bounds_for_picker_and_colors(&gui->picker);
25     CanvasState* canvas = milton->canvas;
26 
27     // Layer window
28 
29     // Use default size on first program start on this computer.
30     f32 left   = ui_scale*10;
31     f32 top    = ui_scale*20 + (float)pbounds.bottom + brush_window_height;
32     f32 width  = ui_scale*300;
33     f32 height = ui_scale*230;
34     if ( reset_gui ) {
35         ImGui::SetNextWindowPos(ImVec2(left, top));
36         ImGui::SetNextWindowSize(ImVec2(width, height));
37     }
38     else {
39         if ( prefs->layer_window_width != 0 && prefs->layer_window_height != 0 ) {
40             // If there are preferences already, use those for the layer window.
41             left   = prefs->layer_window_left;
42             top    = prefs->layer_window_top;
43             width  = prefs->layer_window_width;
44             height = prefs->layer_window_height;
45         }
46 
47         ImGui::SetNextWindowPos(ImVec2(left, top), ImGuiSetCond_FirstUseEver);
48         ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiSetCond_FirstUseEver);
49     }
50 
51     if ( ImGui::Begin(loc(TXT_layers)) ) {
52         i32 angle = (milton->view->angle / PI) * 180;
53         while (angle < 0) { angle += 360; }
54         while (angle > 360) { angle -= 360; }
55         if (ImGui::SliderInt(loc(TXT_rotation), &angle, 0.0f, 360)) {
56             milton->view->angle = (f32)angle / 180.0f * PI;
57             input->flags |= (i32)MiltonInputFlags_PANNING;
58             gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
59         }
60 
61         CanvasView* view = milton->view;
62         // left
63         ImGui::BeginChild("left pane", ImVec2(150, 0), true);
64 
65         static b32 is_renaming = false;
66         static i32 layer_renaming_idx = -1;
67         static b32 focus_rename_field = false;
68 
69 
70         Layer* layer = milton->canvas->root_layer;
71         while ( layer->next ) { layer = layer->next; }  // Move to the top layer.
72         while ( layer ) {
73 
74             bool v = layer->flags & LayerFlags_VISIBLE;
75             ImGui::PushID(layer->id);
76 
77             if ( ImGui::Checkbox("##select", &v) ) {
78                 layer::layer_toggle_visibility(layer);
79                 input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
80             }
81 
82             ImGui::PopID();
83             ImGui::SameLine();
84 
85             // Draw the layers list. If in renaming mode, draw the layer that's being renamed as an InputText.
86             // Else just draw them as a list of Selectables.
87             if ( !ImGui::IsWindowFocused() ) {
88                 is_renaming = false;
89             }
90 
91             if ( is_renaming ) {
92                 if ( layer->id == layer_renaming_idx ) {
93                     if ( focus_rename_field ) {
94                         ImGui::SetKeyboardFocusHere(0);
95                         focus_rename_field = false;
96                     }
97                     if ( ImGui::InputText("##rename",
98                                           milton->canvas->working_layer->name,
99                                           13,
100                                           //MAX_LAYER_NAME_LEN,
101                                           ImGuiInputTextFlags_EnterReturnsTrue
102                                           //,ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL
103                                          )) {
104                         is_renaming = false;
105                     }
106                 }
107                 else {
108                     if ( ImGui::Selectable(layer->name,
109                                            milton->canvas->working_layer == layer,
110                                            ImGuiSelectableFlags_AllowDoubleClick) ) {
111                         if ( ImGui::IsMouseDoubleClicked(0) ) {
112                             layer_renaming_idx = layer->id;
113                             focus_rename_field = true;
114                         }
115                         is_renaming = false;
116                         milton_set_working_layer(milton, layer);
117                     }
118                 }
119             }
120             else {
121                 if ( ImGui::Selectable(layer->name,
122                                        milton->canvas->working_layer == layer,
123                                        ImGuiSelectableFlags_AllowDoubleClick) ) {
124                     if ( ImGui::IsMouseDoubleClicked(0) ) {
125                         layer_renaming_idx = layer->id;
126                         is_renaming = true;
127                         focus_rename_field = true;
128                     }
129                     milton_set_working_layer(milton, layer);
130                 }
131             }
132 
133             layer = layer->prev;
134         }
135         ImGui::EndChild();
136         ImGui::SameLine();
137 
138         ImGui::BeginGroup();
139         ImGui::BeginChild("item view", ImVec2(0, 25));
140         if ( ImGui::Button(loc(TXT_new_layer)) ) {
141             milton_new_layer(milton);
142         }
143         ImGui::SameLine();
144 
145 
146         ImGui::Separator();
147         ImGui::EndChild();
148         ImGui::BeginChild("buttons");
149 
150         ImGui::Text(loc(TXT_move));
151 
152         Layer* a = NULL;
153         Layer* b = NULL;
154         if ( ImGui::Button(loc(TXT_up)) ) {
155             b = milton->canvas->working_layer;
156             a = b->next;
157         }
158         ImGui::SameLine();
159         if ( ImGui::Button(loc(TXT_down)) ) {
160             a = milton->canvas->working_layer;
161             b = a->prev;
162         }
163 
164 
165         if ( a && b ) {
166             // n <-> a <-> b <-> p
167             // n <-> b <-> a <-> p
168             Layer* n = a->next;
169             Layer* p = b->prev;
170             b->next = n;
171             if ( n ) n->prev = b;
172             a->prev = p;
173             if ( p ) p->next = a;
174 
175             a->next = b;
176             b->prev = a;
177 
178             // Make sure root is first
179             while ( milton->canvas->root_layer->prev ) {
180                 milton->canvas->root_layer = milton->canvas->root_layer->prev;
181             }
182             input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
183         }
184 
185         if ( milton->canvas->working_layer->next
186              || milton->canvas->working_layer->prev ) {
187             static bool deleting = false;
188             if ( deleting == false ) {
189                 if ( ImGui::Button(loc(TXT_delete)) ) {
190                     deleting = true;
191                 }
192             }
193             else if ( deleting ) {
194                 ImGui::Text(loc(TXT_are_you_sure));
195                 ImGui::Text(loc(TXT_cant_be_undone));
196                 if ( ImGui::Button(loc(TXT_yes)) ) {
197                     milton_delete_working_layer(milton);
198                     input->flags |= MiltonInputFlags_FULL_REFRESH;
199                     deleting = false;
200                 }
201                 ImGui::SameLine();
202                 if ( ImGui::Button(loc(TXT_no)) ) {
203                     deleting = false;
204                 }
205             }
206         }
207         static b32 show_effects = false;
208         {
209             // ImGui::GetWindow(Pos|Size) works in here because we are inside Begin()/End() calls.
210             {
211                 ImGui::Text(loc(TXT_opacity));
212                 f32 alpha = canvas->working_layer->alpha;
213                 if ( ImGui::SliderFloat("##opacity", &alpha, 0.0f, 1.0f) ) {
214                     // Used the slider. Ask if it's OK to convert the binary format.
215                     if ( milton->persist->mlt_binary_version < 3 ) {
216                         milton_log("Modified milton file from %d to 3\n", milton->persist->mlt_binary_version);
217                         milton->persist->mlt_binary_version = 3;
218                     }
219                     input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
220 
221                     alpha = clamp(alpha, 0.0f, 1.0f);
222 
223                     canvas->working_layer->alpha = alpha;
224                 }
225 
226                 static b32 selecting = false;
227 
228                 ImGui::Separator();
229 
230                 if ( ImGui::Button(loc(TXT_blur)) ) {
231                     LayerEffect* e = arena_alloc_elem(&milton->canvas_arena, LayerEffect);
232                     e->next = milton->canvas->working_layer->effects;
233                     milton->canvas->working_layer->effects = e;
234                     e->enabled = true;
235                     e->blur.original_scale = milton->view->scale;
236                     e->blur.kernel_size = 10;
237                     input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
238                 }
239 
240                 LayerEffect* prev = NULL;
241                 int effect_id = 1;
242                 for ( LayerEffect* e = milton->canvas->working_layer->effects; e != NULL; e = e->next ) {
243                     ImGui::PushID(effect_id);
244                     static bool v = 0;
245                     if ( ImGui::Checkbox(loc(TXT_enabled), (bool*)&e->enabled) ) {
246                         input->flags |= MiltonInputFlags_FULL_REFRESH;
247                     }
248                     if ( ImGui::SliderInt(loc(TXT_level), &e->blur.kernel_size, 2, 100, 0) ) {
249                         if (e->blur.kernel_size % 2 == 0) {
250                             --e->blur.kernel_size;
251                         }
252                         input->flags |= MiltonInputFlags_FULL_REFRESH;
253                     }
254                     {
255                         if (ImGui::Button(loc(TXT_delete_blur))) {
256                             if (prev) {
257                                 prev->next = e->next;
258                             } else {  // Was the first.
259                                 milton->canvas->working_layer->effects = e->next;
260                             }
261                             input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
262                         }
263                     }
264                     ImGui::PopID();
265                     prev = e;
266                     ImGui::Separator();
267                     effect_id++;
268                 }
269                 // ImGui::Slider
270             }
271         }
272         ImGui::EndChild();
273         ImGui::EndGroup();
274 
275         // Remember the current window layout for the next time the program runs.
276         ImVec2 pos  = ImGui::GetWindowPos();
277         ImVec2 size = ImGui::GetWindowSize();
278         prefs->layer_window_left   = pos.x;
279         prefs->layer_window_top    = pos.y;
280         prefs->layer_window_width  = size.x;
281         prefs->layer_window_height = size.y;
282 
283     } ImGui::End();
284 }
285 
286 
287 // gui_brush_window returns the height of the rendered brush tool window. This can be used to position other windows below it.
288 // If reset_gui is true, the default window position and size will be set.
289 i32
gui_brush_window(MiltonInput * input,PlatformState * platform,Milton * milton,PlatformSettings * prefs,b32 reset_gui)290 gui_brush_window(MiltonInput* input, PlatformState* platform, Milton* milton, PlatformSettings* prefs, b32 reset_gui)
291 {
292     b32 show_brush_window = (current_mode_is_for_drawing(milton));
293     auto imgui_window_flags = ImGuiWindowFlags_NoCollapse;
294     MiltonGui* gui = milton->gui;
295 
296     const Rect pbounds = get_bounds_for_picker_and_colors(&gui->picker);
297 
298     // Use default size on first program start on this computer.
299     f32 left = milton->gui->scale * 10;
300     f32 top = milton->gui->scale * 10 + (float)pbounds.bottom;
301     f32 width = milton->gui->scale * 300;
302     f32 height = milton->gui->scale * 230;
303     if ( reset_gui ) {
304         ImGui::SetNextWindowPos(ImVec2(left, top));
305         ImGui::SetNextWindowSize({width, height});
306     }
307     else {
308         if ( prefs->brush_window_width != 0 && prefs->brush_window_height ) {
309             // If there are preferences already, use those for the layer window.
310             left =   prefs->brush_window_left;
311             top =    prefs->brush_window_top;
312             width =  prefs->brush_window_width;
313             height = prefs->brush_window_height;
314         }
315 
316         ImGui::SetNextWindowPos(ImVec2(left, top), ImGuiSetCond_FirstUseEver);
317         ImGui::SetNextWindowSize({width, height}, ImGuiSetCond_FirstUseEver);
318     }
319 
320     // Brush Window
321     if ( show_brush_window ) {
322         if ( ImGui::Begin(loc(TXT_brushes), NULL, imgui_window_flags) ) {
323             if ( milton->current_mode == MiltonMode::PEN ||
324                  milton->current_mode == MiltonMode::PRIMITIVE ) {
325                 const float pen_alpha = milton_get_brush_alpha(milton);
326                 mlt_assert(pen_alpha >= 0.0f && pen_alpha <= 1.0f);
327                 float mut_alpha = pen_alpha*100;
328                 ImGui::SliderFloat(loc(TXT_opacity), &mut_alpha, 1, 100, "%.0f%%");
329 
330                 mut_alpha /= 100.0f;
331                 if (mut_alpha > 1.0f ) mut_alpha = 1.0f;
332                 if ( mut_alpha != pen_alpha ) {
333                     milton_set_brush_alpha(milton, mut_alpha);
334                     gui->flags |= (i32)MiltonGuiFlags_SHOWING_PREVIEW;
335                 }
336             }
337             const auto size = milton_get_brush_radius(milton);
338             auto mut_size = size;
339 
340 
341             if (ImGui::CheckboxFlags(loc(TXT_size_relative_to_canvas),
342                                     reinterpret_cast<u32*>(&milton->working_stroke.flags),
343                                     StrokeFlag_RELATIVE_TO_CANVAS)) {
344                 // Just set it to be relative...
345             }
346 
347             ImGui::SliderInt(loc(TXT_brush_size), &mut_size, 1, MILTON_MAX_BRUSH_SIZE);
348 
349             if ( mut_size != size ) {
350                 milton_set_brush_size(milton, mut_size);
351                 milton->gui->flags |= (i32)MiltonGuiFlags_SHOWING_PREVIEW;
352             }
353 
354             if ( milton->current_mode != MiltonMode::PEN ) {
355                 if ( ImGui::Button(loc(TXT_switch_to_brush)) ) {
356                     input->mode_to_set = MiltonMode::PEN;
357                 }
358             }
359 
360             if ( milton->current_mode != MiltonMode::PRIMITIVE ) {
361                 if ( ImGui::Button(loc(TXT_switch_to_primitive)) ) {
362                     input->mode_to_set = MiltonMode::PRIMITIVE;
363                 }
364             }
365 
366             if ( milton->current_mode != MiltonMode::ERASER ) {
367                 if ( ImGui::Button(loc(TXT_switch_to_eraser)) ) {
368                     input->mode_to_set = MiltonMode::ERASER;
369                 }
370             }
371         }
372 
373         {
374             if (!(milton->working_stroke.flags & StrokeFlag_ERASER)) {
375                 ImGui::CheckboxFlags(loc(TXT_opacity_pressure), reinterpret_cast<u32*>(&milton->working_stroke.flags), StrokeFlag_PRESSURE_TO_OPACITY);
376                 if (milton->working_stroke.flags & StrokeFlag_PRESSURE_TO_OPACITY) {
377                     int brush_enum = milton_get_brush_enum(milton);
378                     f32* min_opacity = &milton->brushes[brush_enum].pressure_opacity_min;
379 
380                     ImGui::SliderFloat(loc(TXT_minimum), min_opacity, 0.0f, milton->brushes[brush_enum].alpha);
381                 }
382                 ImGui::CheckboxFlags(loc(TXT_soft_brush), reinterpret_cast<u32*>(&milton->working_stroke.flags), StrokeFlag_DISTANCE_TO_OPACITY);
383                 if (milton->working_stroke.flags & StrokeFlag_DISTANCE_TO_OPACITY) {
384                     int brush_enum = milton_get_brush_enum(milton);
385                     f32* hardness = &milton->brushes[brush_enum].hardness;
386 
387                     ImGui::SliderFloat(loc(TXT_hardness), hardness, 1.0f, k_max_hardness);
388                 }
389 
390             }
391         }
392 
393         // Important to place this before ImGui::End(), position and size will be invalid after it.
394         ImVec2 pos  = ImGui::GetWindowPos();
395         ImVec2 size = ImGui::GetWindowSize();
396         // Remember the current window layout for the next time the program runs.
397         prefs->brush_window_left   = pos.x;
398         prefs->brush_window_top    = pos.y;
399         prefs->brush_window_width  = size.x;
400         prefs->brush_window_height = size.y;
401         ImGui::End();  // Brushes
402         if (( milton->gui->flags & MiltonGuiFlags_SHOWING_PREVIEW )) {
403             milton->gui->preview_pos = {
404                 (i32)(pos.x + size.x + milton_get_brush_radius(milton)),
405                 (i32)(pos.y),
406             };
407         }
408     }
409 
410     return height;
411 }
412 
413 void
gui_menu(MiltonInput * input,PlatformState * platform,Milton * milton,b32 & show_settings,b32 * reset_gui)414 gui_menu(MiltonInput* input, PlatformState* platform, Milton* milton, b32& show_settings, b32* reset_gui)
415 {
416     // Menu ----
417     int menu_style_stack = 0;
418 
419     MiltonGui* gui = milton->gui;
420     CanvasState* canvas = milton->canvas;
421 
422     // TODO: translate
423 
424     if ( gui->menu_visible ) {
425         if ( ImGui::BeginMainMenuBar() ) {
426             if ( ImGui::BeginMenu(loc(TXT_file)) ) {
427                 if ( ImGui::MenuItem(loc(TXT_new_milton_canvas)) ) {
428                     b32 save_file = false;
429                     if ( layer::count_strokes(milton->canvas->root_layer) > 0 ) {
430                         if ( milton->flags & MiltonStateFlags_DEFAULT_CANVAS ) {
431                             save_file = platform_dialog_yesno(loc(TXT_default_will_be_cleared), "Save?");
432                         }
433                     }
434                     if ( save_file ) {
435                         PATH_CHAR* name = platform_save_dialog(FileKind_MILTON_CANVAS);
436                         if ( name ) {
437                             milton_log("Saving to %s\n", name);
438                             milton_set_canvas_file(milton, name);
439                             milton_save(milton);
440                             b32 del = platform_delete_file_at_config(TO_PATH_STR("MiltonPersist.mlt"), DeleteErrorTolerance_OK_NOT_EXIST);
441                             if ( del == false ) {
442                                 platform_dialog("Could not delete contents. The work will be still be there even though you saved it to a file.",
443                                                 "Info");
444                             }
445                         }
446                     }
447 
448                     // New Canvas
449                     milton_reset_canvas_and_set_default(milton);
450                     canvas = milton->canvas;
451                     input->flags |= MiltonInputFlags_FULL_REFRESH;
452                     milton->flags |= MiltonStateFlags_DEFAULT_CANVAS;
453                 }
454                 b32 save_requested = false;
455                 if ( ImGui::MenuItem(loc(TXT_open_milton_canvas)) ) {
456                     // If current canvas is MiltonPersist, then prompt to save
457                     if ( ( milton->flags & MiltonStateFlags_DEFAULT_CANVAS ) ) {
458                         b32 save_file = false;
459                         if ( layer::count_strokes(milton->canvas->root_layer) > 0 ) {
460                             save_file = platform_dialog_yesno(loc(TXT_default_will_be_cleared), "Save?");
461                         }
462                         if ( save_file ) {
463                             PATH_CHAR* name = platform_save_dialog(FileKind_MILTON_CANVAS);
464                             if ( name ) {
465                                 milton_log("Saving to %s\n", name);
466                                 milton_set_canvas_file(milton, name);
467                                 milton_save(milton);
468                                 b32 del = platform_delete_file_at_config(TO_PATH_STR("MiltonPersist.mlt"),
469                                                                          DeleteErrorTolerance_OK_NOT_EXIST);
470                                 if ( del == false ) {
471                                     platform_dialog("Could not delete default canvas. Contents will be still there when you create a new canvas.",
472                                                     "Info");
473                                 }
474                             }
475                         }
476                     }
477                     PATH_CHAR* fname = platform_open_dialog(FileKind_MILTON_CANVAS);
478                     if ( fname ) {
479                         milton_set_canvas_file(milton, fname);
480                         input->flags |= MiltonInputFlags_OPEN_FILE;
481                     }
482                 }
483                 if ( ImGui::MenuItem(loc(TXT_save_milton_canvas_as_DOTS)) || save_requested ) {
484                     // NOTE(possible refactor): There is a copy of this at milton.c end of file
485                     PATH_CHAR* name = platform_save_dialog(FileKind_MILTON_CANVAS);
486                     if ( name ) {
487                         milton_log("Saving to %s\n", name);
488                         milton_set_canvas_file(milton, name);
489                         input->flags |= MiltonInputFlags_SAVE_FILE;
490                         b32 del = platform_delete_file_at_config(TO_PATH_STR("MiltonPersist.mlt"),
491                                                                  DeleteErrorTolerance_OK_NOT_EXIST);
492                         if ( del == false ) {
493                             platform_dialog(loc(TXT_could_not_delete_default_canvas),
494                                             "Info");
495                         }
496                     }
497                 }
498                 if ( ImGui::MenuItem(loc(TXT_export_to_image_DOTS)) ) {
499                     milton_enter_mode(milton, MiltonMode::EXPORTING);
500                 }
501                 if ( ImGui::MenuItem(loc(TXT_settings)) && !show_settings ) {
502                     milton_set_gui_visibility(milton, true);
503                     show_settings = true;
504                     *gui->original_settings = *milton->settings;
505                     for (sz i = Action_FIRST; i < Action_COUNT; ++i) {
506                         gui->scratch_binding_key[i][0] = gui->original_settings->bindings.bindings[i].bound_key;
507                     }
508                 }
509                 if ( ImGui::MenuItem(loc(TXT_quit)) ) {
510                     milton_try_quit(milton);
511                 }
512                 ImGui::EndMenu();
513             } // file menu
514             if ( ImGui::BeginMenu(loc(TXT_edit)) ) {
515                 if ( ImGui::MenuItem(loc(TXT_undo) ) ) {
516                     input->flags |= MiltonInputFlags_UNDO;
517                 }
518                 if ( ImGui::MenuItem(loc(TXT_redo)) ) {
519                     input->flags |= MiltonInputFlags_REDO;
520                 }
521                 ImGui::EndMenu();
522             }
523 
524             if ( ImGui::BeginMenu(loc(TXT_canvas), /*enabled=*/true) ) {
525                 if ( ImGui::BeginMenu(loc(TXT_set_background_color)) ) {
526                     v3f bg = milton->view->background_color;
527                     if (ImGui::ColorEdit3(loc(TXT_color), bg.d))
528                     {
529                         milton_set_background_color(milton, clamp_01(bg));
530                         input->flags |= (i32)MiltonInputFlags_FULL_REFRESH;
531                     }
532                     ImGui::EndMenu();
533                 }
534                 if ( ImGui::MenuItem(loc(TXT_zoom_in)) ) {
535                     input->scale++;
536                     milton_set_zoom_at_screen_center(milton);
537                 }
538                 if ( ImGui::MenuItem(loc(TXT_zoom_out)) ) {
539                     input->scale--;
540                     milton_set_zoom_at_screen_center(milton);
541                 }
542                 ImGui::EndMenu();
543             }
544             if ( ImGui::BeginMenu(loc(TXT_tools)) ) {
545                 // Brush
546                 if ( ImGui::MenuItem(loc(TXT_brush)) ) {
547                     input->mode_to_set = MiltonMode::PEN;
548                 }
549                 if ( ImGui::BeginMenu(loc(TXT_brush_options)) ) {
550 
551                     // Toggling brush smoothing is barely noticeable...
552 
553                     // b32 smoothing_enabled = milton_brush_smoothing_enabled(milton);
554                     // char* entry_str = smoothing_enabled? loc(TXT_disable_stroke_smoothing) : loc(TXT_enable_stroke_smoothing);
555 
556                     // if ( ImGui::MenuItem(entry_str) ) {
557                     //     milton_toggle_brush_smoothing(milton);
558                     // }
559 
560                     // Decrease / increase brush size
561                     if ( ImGui::MenuItem(loc(TXT_decrease_brush_size)) ) {
562                         for (int i=0;i<5;++i) milton_decrease_brush_size(milton);
563                     }
564                     if ( ImGui::MenuItem(loc(TXT_increase_brush_size)) ) {
565                         for (int i=0;i<5;++i) milton_increase_brush_size(milton);
566                     }
567                     // Opacity shortcuts
568 
569                     f32 opacities[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f };
570                     for ( i32 i = 0; i < array_count(opacities); ++i ) {
571                         char entry[128] = {};
572                         snprintf(entry, array_count(entry), "%s %d%% - [%d]",
573                                  loc(TXT_set_opacity_to), (int)(100 * opacities[i]), i == 9 ? 0 : i+1);
574                         if ( ImGui::MenuItem(entry) ) {
575                             milton_set_brush_alpha(milton, opacities[i]);
576                         }
577                     }
578                     ImGui::EndMenu();
579                 }
580                 // Eraser
581                 if ( ImGui::MenuItem(loc(TXT_eraser)) ) {
582                     input->mode_to_set = MiltonMode::ERASER;
583                 }
584                 // Panning
585                 char* move_str = platform->is_panning==false? loc(TXT_move_canvas) : loc(TXT_stop_moving_canvas);
586                 if ( ImGui::MenuItem(move_str) ) {
587                     platform->waiting_for_pan_input = true;
588                 }
589                 // Eye Dropper
590                 if ( ImGui::MenuItem(loc(TXT_eye_dropper)) ) {
591                     input->mode_to_set = MiltonMode::EYEDROPPER;
592                 }
593                 ImGui::EndMenu();
594             }
595             if ( ImGui::BeginMenu(loc(TXT_view)) ) {
596                 if ( ImGui::MenuItem(loc(TXT_toggle_gui_visibility)) ) {
597                     milton_toggle_gui_visibility(milton);
598                 }
599 
600                 if ( ImGui::MenuItem(loc(TXT_peek_out)) ) {
601                     peek_out_trigger_start(milton, PeekOut_CLICK_TO_EXIT);
602                 }
603 
604                 if ( ImGui::MenuItem(loc(TXT_reset_view_at_origin)) ) {
605                     reset_transform_at_origin(
606                         &milton->view->pan_center,
607                         &milton->view->scale,
608                         &milton->view->angle);
609                     gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
610                 }
611 
612                 if ( ImGui::MenuItem(loc(TXT_reset_GUI)) ) {
613                     *reset_gui = true;
614                 }
615 
616 #if MILTON_ENABLE_PROFILING
617                 if ( ImGui::MenuItem("Toggle Debug Data [BACKQUOTE]") ) {
618                     milton->viz_window_visible = !milton->viz_window_visible;
619                 }
620 #endif
621                 ImGui::EndMenu();
622             }
623             if ( ImGui::BeginMenu(loc(TXT_help)) ) {
624                 if ( ImGui::MenuItem(loc(TXT_help_me)) ) {
625                     //platform_open_link("https://www.youtube.com/watch?v=g27gHio2Ohk");
626                     platform_open_link("http://www.miltonpaint.com/help/");
627                 }
628                 if ( ImGui::MenuItem(loc(TXT_milton_version)) ) {
629                     char buffer[1024];
630                     snprintf(buffer, array_count(buffer),
631                              "Milton version %d.%d.%d",
632                              MILTON_MAJOR_VERSION, MILTON_MINOR_VERSION, MILTON_MICRO_VERSION);
633                     platform_dialog(buffer, "Milton Version");
634                 }
635                 if (  ImGui::MenuItem(loc(TXT_website))  ) {
636                     platform_open_link("http://miltonpaint.com");
637                 }
638                 ImGui::EndMenu();
639             }
640             PATH_CHAR* utf16_name = str_trim_to_last_slash(milton->persist->mlt_file_path);
641 
642             char file_name[MAX_PATH] = {};
643             utf16_to_utf8_simple(utf16_name, file_name);
644 
645             char msg[1024];
646             WallTime lst = milton->persist->last_save_time;
647 
648             // TODO: Translate!
649             snprintf(msg, 1024, "\t%s -- Last saved: %.2d:%.2d:%.2d\t\tZoom level %.2f",
650                      (milton->flags & MiltonStateFlags_DEFAULT_CANVAS) ? loc(TXT_OPENBRACKET_default_canvas_CLOSE_BRACKET):
651                      file_name,
652                      lst.hours, lst.minutes, lst.seconds,
653                      // We divide by MILTON_DEFAULT_SCALE to give a frame of
654                      // reference to the user, where 1.0 is the default. For
655                      // our calculations in other places, we don't do the
656                      // divide.
657                      log2(1 + milton->view->scale / (double)MILTON_DEFAULT_SCALE) / log2(SCALE_FACTOR));
658 
659             if ( ImGui::BeginMenu(msg, /*bool enabled = */false) ) {
660                 ImGui::EndMenu();
661             }
662             ImGui::EndMainMenuBar();
663         }
664         ImGui::PopStyleColor(menu_style_stack);
665     }
666 }
667 
668 
669 void
milton_imgui_tick(MiltonInput * input,PlatformState * platform,Milton * milton,PlatformSettings * prefs)670 milton_imgui_tick(MiltonInput* input, PlatformState* platform,  Milton* milton, PlatformSettings* prefs)
671 {
672     CanvasState* canvas = milton->canvas;
673     MiltonGui* gui = milton->gui;
674     // ImGui Section
675 
676     // Spawn below the picker
677     const Rect pbounds = get_bounds_for_picker_and_colors(&gui->picker);
678 
679     int color_stack = 0;
680 
681     static auto color_window_background = ImVec4{.929f, .949f, .957f, 1};
682     //static auto color_title_bg        = ImVec4{.957f,.353f, .286f,1};
683     static auto color_title_bg          = color_window_background;
684     static auto color_title_fg          = ImVec4{151/255.f, 184/255.f, 210/255.f, 1};
685 
686     static auto color_buttons         = ImVec4{.686f, .796f, 1.0f, 1};
687     static auto color_buttons_active  = ImVec4{.886f, .796f, 1.0f, 1};
688     static auto color_buttons_hovered = ImVec4{.706f, .816f, 1.0f, 1};
689 
690     static auto color_menu_bg        = ImVec4{.784f, .392f, .784f, 1};
691     static auto color_text           = ImVec4{.2f,.2f,.2f,1};
692     static auto color_slider         = ImVec4{ 148/255.f, 182/255.f, 182/255.f,1};
693     static auto frame_background     = ImVec4{ 0.862745f, 0.862745f, 0.862745f,1};
694     static auto color_text_selected  = ImVec4{ 0.509804f, 0.627451f, 0.823529f,1};
695     static auto color_header_hovered = color_buttons;
696 
697     // Helper Imgui code to select color scheme
698 #if 0
699     ImGui::ColorEdit3("Window Background", (float*)&color_window_background);
700     ImGui::ColorEdit3("Title background", (float*)&color_title_bg);
701     ImGui::ColorEdit3("Title background active", (float*)&color_title_fg);
702 
703     ImGui::ColorEdit3("Buttons", (float*)&color_buttons);
704     ImGui::ColorEdit3("Menu BG", (float*)&color_menu_bg);
705     ImGui::ColorEdit3("text", (float*)&color_text);
706     ImGui::ColorEdit3("frame background", (float*)&frame_background);
707     ImGui::ColorEdit3("selected", (float*)&color_text_selected);
708     if ( ImGui::Button("Print out") ) {
709         auto print_color = [&](char* label, ImVec4 c) {
710             milton_log("%s : %f, %f, %f \n", label, c.x, c.y, c.z);
711         };
712         print_color("window bg", color_window_background);
713         print_color("title bg", color_title_bg);
714         print_color("buttons", color_buttons);
715         print_color("menu bg", color_menu_bg);
716         print_color("text", color_text);
717         print_color("selected", frame_background);
718         print_color("selected", color_text_selected);
719     }
720 #endif
721 
722     ImGui::PushStyleColor(ImGuiCol_WindowBg,        color_window_background); ++color_stack;
723     ImGui::PushStyleColor(ImGuiCol_PopupBg,         color_window_background); ++color_stack;
724     ImGui::PushStyleColor(ImGuiCol_TitleBg,         color_title_bg); ++color_stack;
725     ImGui::PushStyleColor(ImGuiCol_TitleBgActive,   color_title_fg); ++color_stack;
726     ImGui::PushStyleColor(ImGuiCol_Text,            color_text); ++color_stack;
727     ImGui::PushStyleColor(ImGuiCol_MenuBarBg,       color_title_bg); ++color_stack;
728     ImGui::PushStyleColor(ImGuiCol_TextSelectedBg,  color_buttons_active); ++color_stack;
729     ImGui::PushStyleColor(ImGuiCol_FrameBg,      frame_background); ++color_stack;
730 
731     ImGui::PushStyleColor(ImGuiCol_SliderGrab,      color_buttons); ++color_stack;
732     ImGui::PushStyleColor(ImGuiCol_SliderGrabActive,      color_buttons_active); ++color_stack;
733 
734     ImGui::PushStyleColor(ImGuiCol_Button,          color_buttons); ++color_stack;
735     ImGui::PushStyleColor(ImGuiCol_ButtonHovered,      color_buttons_hovered); ++color_stack;
736     ImGui::PushStyleColor(ImGuiCol_ButtonActive,          color_buttons_active); ++color_stack;
737 
738     ImGui::PushStyleColor(ImGuiCol_Header,          color_buttons); ++color_stack;
739     ImGui::PushStyleColor(ImGuiCol_HeaderHovered,   color_buttons_hovered); ++color_stack;
740     ImGui::PushStyleColor(ImGuiCol_HeaderActive,    color_buttons_active); ++color_stack;
741 
742     ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab,   color_buttons); ++color_stack;
743     ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered,      color_buttons_hovered); ++color_stack;
744     ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive,      color_buttons_active); ++color_stack;
745 
746     ImGui::PushStyleColor(ImGuiCol_CheckMark,      color_slider); ++color_stack;
747 
748 
749     static b32 show_settings = false;
750     b32 reset_gui = false;
751 
752     gui_menu(input, platform, milton, show_settings, &reset_gui);
753 
754     float ui_scale = milton->gui->scale;
755 
756     // GUI Windows ----
757 
758     b32 should_show_windows = milton->current_mode != MiltonMode::EYEDROPPER
759                                 && milton->current_mode != MiltonMode::EXPORTING
760                                 && milton->current_mode != MiltonMode::HISTORY;
761 
762     if ( gui->visible && should_show_windows ) {
763         i32 brush_window_height = gui_brush_window(input, platform, milton, prefs, reset_gui);
764 
765         gui_layer_window(input, platform, milton, brush_window_height, prefs, reset_gui);
766 
767         // Settings window
768         if ( show_settings ) {
769             ImGui::SetNextWindowSize(ImVec2(ui_scale*400, ui_scale*400),
770                                      ImGuiSetCond_FirstUseEver);
771             if ( ImGui::Begin(loc(TXT_settings)) ) {
772                 if (ImGui::Button(loc(TXT_ok))) {
773                     milton_settings_save(milton->settings);
774                     show_settings = false;
775                 }
776 
777                 ImGui::SameLine();
778                 if (ImGui::Button(loc(TXT_cancel))) {
779                     show_settings = false;
780                     *milton->settings = *gui->original_settings;
781                 }
782 
783                 ImGui::Separator();
784                 ImGui::BeginChild("ScrollRegion");
785 
786                 ImGui::Text(loc(TXT_default_background_color));
787 
788                 v3f* bg = &milton->settings->background_color;
789                 if (ImGui::ColorEdit3(loc(TXT_color), bg->d)) {
790                     // TODO: Let milton know that we need to save the settings
791                 }
792 
793                 if ( ImGui::Button(loc(TXT_set_current_background_color_as_default)) ) {
794                     milton->settings->background_color = milton->view->background_color;
795                 }
796 
797                 const float peek_range = 20;
798                 int peek_out_percent = 100 * (milton->settings->peek_out_increment / peek_range);
799                 if (ImGui::SliderInt(loc(TXT_peek_out_increment_percent), &peek_out_percent, 0, 100)) {
800                     milton->settings->peek_out_increment = (peek_out_percent / 100.0f) * peek_range;
801                 }
802 
803                 ImGui::Separator();
804 
805                 MiltonBindings* bs = &milton->settings->bindings;
806 
807                 for (sz i = Action_FIRST; i < Action_COUNT; ++i ) {
808                     Binding* b = bs->bindings + i;
809 
810                     char control_lbl[64] = {};
811                     snprintf(control_lbl, array_count(control_lbl), "control##%d", (int)i);
812 
813                     char alt_lbl[64] = {};
814                     snprintf(alt_lbl, array_count(alt_lbl), "alt##%d", (int)i);
815 
816                     char win_lbl[64] = {};
817                     snprintf(win_lbl, array_count(win_lbl), "win##%d", (int)i);
818 
819 
820                     ImGui::CheckboxFlags(control_lbl, (unsigned int*)&b->modifiers, Modifier_CTRL);
821                     ImGui::SameLine();
822                     ImGui::CheckboxFlags(alt_lbl, (unsigned int*)&b->modifiers, Modifier_ALT);
823                     ImGui::SameLine();
824                     ImGui::CheckboxFlags(win_lbl, (unsigned int*)&b->modifiers, Modifier_WIN);
825                     ImGui::SameLine();
826 
827                     ImGui::PushItemWidth(60);
828                     // mlt_assert(TXT_Action_FIRST + (int)i < TXT_Count);
829                     char* action_str = (char*)loc((Texts)(TXT_Action_FIRST + (int)i - Action_FIRST));
830                     if (ImGui::InputText(action_str,
831                                          (char*)gui->scratch_binding_key[i],
832                                          sizeof(gui->scratch_binding_key[i]),
833                                          ImGuiInputTextFlags_AllowTabInput)) {
834                         b->bound_key = gui->scratch_binding_key[i][0];
835                     }
836                     ImGui::PopItemWidth();
837                 }
838 
839                 ImGui::EndChild();
840             } ImGui::End();
841         }
842 
843         // History window
844         if ( milton->current_mode == MiltonMode::HISTORY ) {
845             {
846                 Rect pb = picker_get_bounds(&gui->picker);
847                 auto width = 20 + pb.right - pb.left;
848                 ImGui::SetNextWindowPos(ImVec2(width, 30), ImGuiSetCond_FirstUseEver);
849             }
850             ImGui::SetNextWindowSize(ImVec2(ui_scale*500, ui_scale*100), ImGuiSetCond_FirstUseEver);
851             if ( ImGui::Begin("History Slider") ) {
852                 ImGui::SliderInt("History", &gui->history, 0,
853                                  layer::count_strokes(milton->canvas->root_layer));
854             } ImGui::End();
855 
856         }
857     } // Windows
858 
859     // Note: The export window is drawn regardless of gui visibility.
860     if ( milton->current_mode == MiltonMode::EXPORTING ) {
861         bool opened = true;
862         b32 reset = false;
863 
864         ImGui::SetNextWindowPos(ImVec2(100, 30), ImGuiSetCond_FirstUseEver);
865         ImGui::SetNextWindowSize({ui_scale*350, ui_scale*235}, ImGuiSetCond_FirstUseEver);  // We don't want to set it *every* time, the user might have preferences
866 
867         // Export window
868         if ( ImGui::Begin(loc(TXT_export_DOTS), &opened, ImGuiWindowFlags_NoCollapse) ) {
869             ImGui::Text(loc(TXT_MSG_click_and_drag_instruction));
870 
871             Exporter* exporter = &milton->gui->exporter;
872             if ( exporter->state == ExporterState_SELECTED ) {
873                 i32 x = min( exporter->needle.x, exporter->pivot.x );
874                 i32 y = min( exporter->needle.y, exporter->pivot.y );
875                 int raster_w = MLT_ABS(exporter->needle.x - exporter->pivot.x);
876                 int raster_h = MLT_ABS(exporter->needle.y - exporter->pivot.y);
877 
878                 ImGui::Text("%s: %dx%d\n",
879                             loc(TXT_current_selection),
880                             raster_w, raster_h);
881                 if (ImGui::InputInt(loc(TXT_scale_up), &exporter->scale, 1, /*step_fast=*/2)) {}
882                 if ( exporter->scale <= 0 ) {
883                     exporter->scale = 1;
884                 }
885                 float viewport_limits[2] = {};
886                 gpu_get_viewport_limits(milton->renderer, viewport_limits);
887 
888                 while ( exporter->scale*raster_w > viewport_limits[0]
889                         || exporter->scale*raster_h > viewport_limits[1] ) {
890                     --exporter->scale;
891                 }
892                 i32 max_scale = milton->view->scale / 2;
893                 if ( exporter->scale > max_scale) {
894                     exporter->scale = max_scale;
895                 }
896                 ImGui::Text("%s: %dx%d\n", loc(TXT_final_image_resolution), raster_w*exporter->scale, raster_h*exporter->scale);
897 
898                 ImGui::Text(loc(TXT_background_COLON));
899                 static int radio_v = 0;
900                 ImGui::RadioButton(loc(TXT_background_color), &radio_v, 0);
901                 ImGui::RadioButton(loc(TXT_transparent_background), &radio_v, 1);
902                 bool transparent_background = radio_v == 1;
903 
904                 if ( ImGui::Button(loc(TXT_export_selection_to_image_DOTS)) ) {
905                     // Render to buffer
906                     int bpp = 4;  // bytes per pixel
907                     i32 w = raster_w * exporter->scale;
908                     i32 h = raster_h * exporter->scale;
909                     size_t size = (size_t)w * h * bpp;
910                     u8* buffer = (u8*)mlt_calloc(1, size, "Bitmap");
911                     if ( buffer ) {
912                         opened = false;
913                         gpu_render_to_buffer(milton, buffer, exporter->scale,
914                                              x,y, raster_w, raster_h, transparent_background ? 0.0f : 1.0f);
915                         //milton_render_to_buffer(milton, buffer, x,y, raster_w, raster_h, exporter->scale);
916                         PATH_CHAR* fname = platform_save_dialog(FileKind_IMAGE);
917                         if ( fname ) {
918                             milton_save_buffer_to_file(fname, buffer, w, h);
919                         }
920                         mlt_free (buffer, "Bitmap");
921                     } else {
922                         platform_dialog(loc(TXT_MSG_memerr_did_not_write), loc(TXT_error));
923                     }
924                 }
925             }
926         }
927 
928         if ( ImGui::Button(loc(TXT_cancel)) ) {
929             reset = true;
930             milton_leave_mode(milton);
931         }
932         ImGui::End(); // Export...
933         if ( !opened ) {
934             reset = true;
935             milton_leave_mode(milton);
936         }
937         if ( reset ) {
938             exporter_init(&milton->gui->exporter);
939         }
940     } // exporting
941 
942 #if MILTON_ENABLE_PROFILING
943     ImGui::SetNextWindowPos(ImVec2(ui_scale*300, ui_scale*205), ImGuiSetCond_FirstUseEver);
944     ImGui::SetNextWindowSize({ui_scale*350, ui_scale*285}, ImGuiSetCond_FirstUseEver);  // We don't want to set it *every* time, the user might have preferences
945     if ( milton->viz_window_visible ) {
946         bool opened = true;
947         if ( ImGui::Begin("Debug Data ([BACKQUOTE] to toggle)", &opened, ImGuiWindowFlags_NoCollapse) ) {
948             float graph_height = 20;
949             char msg[512] = {};
950 
951             float poll     = perf_count_to_sec(milton->graph_frame.polling) * 1000.0f;
952             float update   = perf_count_to_sec(milton->graph_frame.update) * 1000.0f;
953             float clipping = perf_count_to_sec(milton->graph_frame.clipping) * 1000.0f;
954             float raster   = perf_count_to_sec(milton->graph_frame.raster) * 1000.0f;
955             float GL       = perf_count_to_sec(milton->graph_frame.GL) * 1000.0f;
956             float system   = perf_count_to_sec(milton->graph_frame.system) * 1000.0f;
957 
958             float sum = poll + update + raster + GL + system;
959 
960             snprintf(msg, array_count(msg),
961                      "Input Polling %f ms\n",
962                      poll);
963             ImGui::Text(msg);
964 
965             snprintf(msg, array_count(msg),
966                      "Milton Update %f ms\n",
967                      update);
968             ImGui::Text(msg);
969 
970             snprintf(msg, array_count(msg),
971                      "Clipping & Update %f ms\n",
972                      clipping);
973             ImGui::Text(msg);
974 
975             snprintf(msg, array_count(msg),
976                      "OpenGL commands %f ms\n",
977                      GL);
978             ImGui::Text(msg);
979 
980             snprintf(msg, array_count(msg),
981                      "System %f ms \n",
982                      system);
983             ImGui::Text(msg);
984 
985             snprintf(msg, array_count(msg),
986                      "Number of strokes in GPU memory: %d\n",
987                      gpu_get_num_clipped_strokes(milton->canvas->root_layer));
988             ImGui::Text(msg);
989 
990             float hist[] = { poll, update, raster, GL, system };
991             ImGui::PlotHistogram("Graph",
992                             (const float*)hist, array_count(hist));
993 
994             {
995                 static const int window_size = 100;
996                 static float moving_window[window_size] = {};
997                 static int window_i = 0;
998 
999                 moving_window[window_i++] = sum;
1000                 window_i %= window_size;
1001 
1002                 float mavg = 0.0f;
1003                 for ( int i =0; i < window_size; ++i ) {
1004                     mavg += moving_window[i];
1005                 }
1006                 mavg /= window_size;
1007 
1008                 snprintf(msg, array_count(msg),
1009                          "Total %f ms (%f ms m. avg)\n",
1010                          sum,
1011                          mavg);
1012 
1013             }
1014             ImGui::Text(msg);
1015 
1016             ImGui::Dummy({0,30});
1017 
1018             i64 stroke_count = layer::count_strokes(milton->canvas->root_layer);
1019 
1020             auto* view = milton->view;
1021             int screen_height = view->screen_size.h * view->scale;
1022             int screen_width = view->screen_size.w * view->scale;
1023 
1024             if ( screen_height>0 && screen_height>0 ) {
1025                 v2l pan = view->pan_center;
1026 
1027                 i64 radius = ((i64)((1ull)<<63ull)-1);
1028 
1029                 {
1030                     if ( pan.y > 0 ) {
1031                         i64 n_screens_below = ((i64)(radius) - (i64)pan.y)/(i64)screen_height;
1032                         snprintf(msg, array_count(msg),
1033                                  "Screens below: %I64d\n", n_screens_below);
1034                         ImGui::Text(msg);
1035                     } else {
1036                         i64 n_screens_above = ((i64)(radius) + (i64)pan.y)/(i64)screen_height;
1037                         snprintf(msg, array_count(msg),
1038                                  "Screens above: %I64d\n", n_screens_above);
1039                         ImGui::Text(msg);
1040                     }
1041                 }
1042                 {
1043                     if ( pan.x > 0 ) {
1044                         i64 n_screens_right = ((i64)(radius) - (i64)pan.x)/(i64)screen_width;
1045                         snprintf(msg, array_count(msg),
1046                                  "Screens to the right: %I64d\n", n_screens_right);
1047                         ImGui::Text(msg);
1048                     } else {
1049                         i64 n_screens_left = ((i64)(radius) + (i64)pan.x)/(i64)screen_width;
1050                         snprintf(msg, array_count(msg),
1051                                  "Screens to the left: %I64d\n", n_screens_left);
1052                         ImGui::Text(msg);
1053                     }
1054                 }
1055             }
1056             snprintf(msg, array_count(msg),
1057                      "Scale: %lld", view->scale);
1058             ImGui::Text(msg);
1059 
1060             // Average stroke size.
1061             i64 avg = 0;
1062             {
1063                 for ( Layer* l = milton->canvas->root_layer;
1064                       l != NULL;
1065                       l = l->next ) {
1066                     for ( i64 i = 0; i < l->strokes.count; ++i ) {
1067                         Stroke* s = get(&l->strokes, i);
1068                         avg += s->num_points;
1069                     }
1070                 }
1071                 if ( stroke_count > 0 ) {
1072                     avg /= stroke_count;
1073                 }
1074             }
1075             snprintf(msg, array_count(msg),
1076                      "Average stroke size: %" PRIi64, avg);
1077             ImGui::Text(msg);
1078 
1079         } ImGui::End();
1080     } // profiling
1081 #endif
1082     ImGui::PopStyleColor(color_stack);
1083 }
1084 
1085 static Rect
color_button_as_rect(const ColorButton * button)1086 color_button_as_rect(const ColorButton* button)
1087 {
1088     Rect rect = {};
1089     rect.left = button->x;
1090     rect.right = button->x + button->w;
1091     rect.top = button->y;
1092     rect.bottom = button->y + button->h;
1093     return rect;
1094 }
1095 
1096 static void
picker_update_points(ColorPicker * picker,float angle)1097 picker_update_points(ColorPicker* picker, float angle)
1098 {
1099     picker->data.hsv.h = radians_to_degrees(angle);
1100     // Update the triangle
1101     float radius = 0.9f * (picker->wheel_radius - picker->wheel_half_width);
1102     v2f center = v2l_to_v2f(VEC2L(picker->center));
1103     {
1104         v2f point = polar_to_cartesian(-angle, radius);
1105         point = point + center;
1106         picker->data.c = point;
1107     }
1108     {
1109         v2f point = polar_to_cartesian(-angle + 2 * kPi / 3.0f, radius);
1110         point = point + center;
1111         picker->data.b = point;
1112     }
1113     {
1114         v2f point = polar_to_cartesian(-angle + 4 * kPi / 3.0f, radius);
1115         point = point + center;
1116         picker->data.a = point;
1117     }
1118 }
1119 
1120 
1121 static void
picker_update_wheel(ColorPicker * picker,v2f polar_point)1122 picker_update_wheel(ColorPicker* picker, v2f polar_point)
1123 {
1124     float angle = picker_wheel_get_angle(picker, polar_point);
1125     picker_update_points(picker, angle);
1126 }
1127 
1128 static b32
picker_hits_triangle(ColorPicker * picker,v2f fpoint)1129 picker_hits_triangle(ColorPicker* picker, v2f fpoint)
1130 {
1131     b32 result = is_inside_triangle(fpoint, picker->data.a, picker->data.b, picker->data.c);
1132     return result;
1133 }
1134 
1135 static void
picker_deactivate(ColorPicker * picker)1136 picker_deactivate(ColorPicker* picker)
1137 {
1138     picker->flags = ColorPickerFlags_NOTHING;
1139 }
1140 
1141 static b32
is_inside_picker_rect(ColorPicker * picker,v2i point)1142 is_inside_picker_rect(ColorPicker* picker, v2i point)
1143 {
1144     return is_inside_rect(picker->bounds, point);
1145 }
1146 
1147 static Rect
picker_color_buttons_bounds(const ColorPicker * picker)1148 picker_color_buttons_bounds(const ColorPicker* picker)
1149 {
1150     Rect bounds = {};
1151     bounds.right = INT_MIN;
1152     bounds.left = INT_MAX;
1153     bounds.top = INT_MAX;
1154     bounds.bottom = INT_MIN;
1155     const ColorButton* button = picker->color_buttons;
1156     while ( button ) {
1157         bounds = rect_union(bounds, color_button_as_rect(button));
1158         button = button->next;
1159     }
1160     return bounds;
1161 }
1162 
1163 static b32
is_inside_picker_button_area(ColorPicker * picker,v2i point)1164 is_inside_picker_button_area(ColorPicker* picker, v2i point)
1165 {
1166     Rect button_rect = picker_color_buttons_bounds(picker);
1167     b32 is_inside = is_inside_rect(button_rect, point);
1168     return is_inside;
1169 }
1170 
1171 b32
gui_point_hovers(MiltonGui * gui,v2i point)1172 gui_point_hovers(MiltonGui* gui, v2i point)
1173 {
1174     b32 hovers = gui->visible &&
1175                     (is_inside_picker_rect(&gui->picker, point) ||
1176                      is_inside_picker_button_area(&gui->picker, point));
1177     return hovers;
1178 }
1179 
1180 void
gui_picker_from_rgb(ColorPicker * picker,v3f rgb)1181 gui_picker_from_rgb(ColorPicker* picker, v3f rgb)
1182 {
1183     v3f hsv = rgb_to_hsv(rgb);
1184     picker->data.hsv = hsv;
1185     float angle = hsv.h * 2*kPi;
1186     picker_update_points(picker, angle);
1187 }
1188 
1189 static void
update_button_bounds(ColorPicker * picker,f32 ui_scale)1190 update_button_bounds(ColorPicker* picker, f32 ui_scale)
1191 {
1192     i32 bounds_radius_px = ui_scale*BOUNDS_RADIUS_PX;
1193 
1194     i32 spacing = 4*ui_scale;
1195     i32 num_buttons = NUM_BUTTONS;
1196 
1197     i32 button_size = (2*bounds_radius_px - (num_buttons - 1) * spacing) / num_buttons;
1198     i32 current_x = ui_scale*40 - button_size / 2;
1199 
1200     for ( ColorButton* cur_button = picker->color_buttons;
1201           cur_button != NULL;
1202           cur_button = cur_button->next ) {
1203         cur_button->x = current_x;
1204         cur_button->y = picker->center.y + bounds_radius_px + spacing;
1205         cur_button->w = button_size;
1206         cur_button->h = button_size;
1207 
1208         current_x += spacing + button_size;
1209     }
1210 }
1211 
1212 static b32
picker_hit_history_buttons(ColorPicker * picker,f32 ui_scale,v2i point)1213 picker_hit_history_buttons(ColorPicker* picker, f32 ui_scale, v2i point)
1214 {
1215     b32 hits = false;
1216     ColorButton* first = picker->color_buttons;
1217     ColorButton* button = first;
1218     ColorButton* prev = NULL;
1219     while ( button ) {
1220         if ( button->rgba.a != 0 &&
1221              is_inside_rect(color_button_as_rect(button), point) ) {
1222             hits = true;
1223 
1224             gui_picker_from_rgb(picker, button->rgba.rgb);
1225 
1226             if ( prev ) {
1227                 prev->next = button->next;
1228             }
1229             if ( button != picker->color_buttons ) {
1230                 button->next = picker->color_buttons;
1231             }
1232             picker->color_buttons = button;
1233 
1234             update_button_bounds(picker, ui_scale);
1235             break;
1236         }
1237         prev = button;
1238         button = button->next;
1239     }
1240     return hits;
1241 }
1242 
1243 static ColorPickResult
picker_update(ColorPicker * picker,v2i point)1244 picker_update(ColorPicker* picker, v2i point)
1245 {
1246     ColorPickResult result = ColorPickResult_NOTHING;
1247     v2f fpoint = v2i_to_v2f(point);
1248     if ( picker->flags == ColorPickerFlags_NOTHING ) {
1249         if ( picker_hits_wheel(picker, fpoint) ) {
1250             picker->flags |= ColorPickerFlags_WHEEL_ACTIVE;
1251         }
1252         else if ( picker_hits_triangle(picker, fpoint) ) {
1253             picker->flags |= ColorPickerFlags_TRIANGLE_ACTIVE;
1254         }
1255     }
1256     if (( picker->flags & ColorPickerFlags_WHEEL_ACTIVE )) {
1257         if (!(picker->flags & ColorPickerFlags_TRIANGLE_ACTIVE))
1258         {
1259             picker_update_wheel(picker, fpoint);
1260             result = ColorPickResult_CHANGE_COLOR;
1261         }
1262     }
1263     if (( picker->flags & ColorPickerFlags_TRIANGLE_ACTIVE )) {
1264         PickerData& d = picker->data;  // Just shortening the identifier.
1265 
1266         // We don't want the chooser to "stick" if it goes outside the triangle
1267         // (i.e. picking black should be easy)
1268         f32 abp = orientation(d.a, d.b, fpoint);
1269         f32 bcp = orientation(d.b, d.c, fpoint);
1270         f32 cap = orientation(d.c, d.a, fpoint);
1271         int insideness = (abp < 0.0f) + (bcp < 0.0f) + (cap < 0.0f);
1272 
1273         // inside the triangle
1274         if ( insideness == 3 ) {
1275             float inv_area = 1.0f / orientation(d.a, d.b, d.c);
1276             d.hsv.v = 1.0f - (cap * inv_area);
1277 
1278             d.hsv.s = abp * inv_area / d.hsv.v;
1279             d.hsv.s = abp  / (orientation(d.a, d.b, d.c) - cap);
1280             d.hsv.s = 1 - (bcp * inv_area) / d.hsv.v;
1281         }
1282         // near a corner
1283         else if ( insideness < 2 ) {
1284             if (abp < 0.0f) {
1285                 d.hsv.s = 1.0f;
1286                 d.hsv.v = 1.0f;
1287             }
1288             else if (bcp < 0.0f) {
1289                 d.hsv.s = 0.0f;
1290                 d.hsv.v = 1.0f;
1291             }
1292             else {
1293                 d.hsv.s = 0.5f;
1294                 d.hsv.v = 0.0f;
1295             }
1296         }
1297         // near an edge
1298         else {
1299 #define GET_T(A, B, C)                            \
1300     v2f perp = perpendicular(fpoint - C); \
1301     f32 t = DOT(C - A, perp) / DOT(B - A, perp);
1302             if (abp >= 0.0f) {
1303                 GET_T(d.b, d.a, d.c)
1304                 d.hsv.s = 0.0f;
1305                 d.hsv.v = t;
1306             }
1307             else if (bcp >= 0.0f) {
1308                 GET_T(d.b, d.c, d.a)
1309                 d.hsv.s = 1.0f;
1310                 d.hsv.v = t;
1311             }
1312             else {
1313                 GET_T(d.a, d.c, d.b)
1314                 d.hsv.s = t;
1315                 d.hsv.v = 1.0f;
1316             }
1317         }
1318 #undef GET_T
1319         result = ColorPickResult_CHANGE_COLOR;
1320     }
1321 
1322     return result;
1323 }
1324 
1325 
1326 b32
picker_hits_wheel(ColorPicker * picker,v2f point)1327 picker_hits_wheel(ColorPicker* picker, v2f point)
1328 {
1329     v2f center = v2i_to_v2f(picker->center);
1330     v2f arrow = point - center;
1331     float dist = magnitude(arrow);
1332 
1333     b32 hits = (dist <= picker->wheel_radius + picker->wheel_half_width ) &&
1334                (dist >= picker->wheel_radius - picker->wheel_half_width );
1335 
1336     return hits;
1337 }
1338 
1339 float
picker_wheel_get_angle(ColorPicker * picker,v2f point)1340 picker_wheel_get_angle(ColorPicker* picker, v2f point)
1341 {
1342     v2f direction = point - v2i_to_v2f(picker->center);
1343     f32 angle = atan2f(direction.y, -direction.x) + kPi;
1344     return angle;
1345 }
1346 
1347 void
picker_init(ColorPicker * picker)1348 picker_init(ColorPicker* picker)
1349 {
1350     v2f fpoint = {
1351         (f32)picker->center.x + (int)(picker->wheel_radius),
1352         (f32)picker->center.y
1353     };
1354     picker_update_wheel(picker, fpoint);
1355     picker->data.hsv = v3f{ 0, 1, 0 };
1356 }
1357 
1358 Rect
picker_get_bounds(ColorPicker * picker)1359 picker_get_bounds(ColorPicker* picker)
1360 {
1361     Rect picker_rect;
1362     {
1363         picker_rect.left   = picker->center.x - picker->bounds_radius_px;
1364         picker_rect.right  = picker->center.x + picker->bounds_radius_px;
1365         picker_rect.bottom = picker->center.y + picker->bounds_radius_px;
1366         picker_rect.top    = picker->center.y - picker->bounds_radius_px;
1367     }
1368     mlt_assert (picker_rect.left >= 0);
1369     mlt_assert (picker_rect.top >= 0);
1370 
1371     return picker_rect;
1372 }
1373 
1374 void
exporter_init(Exporter * exporter)1375 exporter_init(Exporter* exporter)
1376 {
1377     *exporter = Exporter{};
1378     exporter->scale = 1;
1379 }
1380 
1381 b32
exporter_input(Exporter * exporter,MiltonInput const * input)1382 exporter_input(Exporter* exporter, MiltonInput const* input)
1383 {
1384     b32 changed = false;
1385     if ( input->input_count > 0 ) {
1386         changed = true;
1387         v2i point = VEC2I(input->points[input->input_count - 1]);
1388         if ( exporter->state == ExporterState_EMPTY ||
1389              exporter->state == ExporterState_SELECTED ) {
1390             exporter->pivot = point;
1391             exporter->needle = point;
1392             exporter->state = ExporterState_GROWING_RECT;
1393         }
1394         else if ( exporter->state == ExporterState_GROWING_RECT ) {
1395             exporter->needle = point;
1396         }
1397     }
1398     if ( (input->flags & MiltonInputFlags_END_STROKE) && exporter->state != ExporterState_EMPTY ) {
1399         exporter->state = ExporterState_SELECTED;
1400         changed = true;
1401     }
1402     return changed;
1403 }
1404 
1405 void
gui_imgui_set_ungrabbed(MiltonGui * gui)1406 gui_imgui_set_ungrabbed(MiltonGui* gui)
1407 {
1408     gui->flags &= ~MiltonGuiFlags_SHOWING_PREVIEW;
1409 }
1410 
1411 Rect
get_bounds_for_picker_and_colors(ColorPicker * picker)1412 get_bounds_for_picker_and_colors(ColorPicker* picker)
1413 {
1414     Rect result = rect_union(picker_get_bounds(picker), picker_color_buttons_bounds(picker));
1415     return result;
1416 }
1417 
1418 static b32
picker_is_active(ColorPicker * picker)1419 picker_is_active(ColorPicker* picker)
1420 {
1421     b32 active = (picker->flags & ColorPickerFlags_TRIANGLE_ACTIVE) ||
1422             (picker->flags & ColorPickerFlags_WHEEL_ACTIVE);
1423     return active;
1424 }
1425 
1426 v3f
picker_hsv_from_point(ColorPicker * picker,v2f point)1427 picker_hsv_from_point(ColorPicker* picker, v2f point)
1428 {
1429     float area = orientation(picker->data.a, picker->data.b, picker->data.c);
1430     v3f hsv = {};
1431     if ( area != 0 ) {
1432         float inv_area = 1.0f / area;
1433         float v = 1 - (orientation(point, picker->data.c, picker->data.a) * inv_area);
1434         float s = orientation(picker->data.b, point, picker->data.a) * inv_area / v;
1435 
1436         hsv = v3f
1437         {
1438             picker->data.hsv.h,
1439             s,
1440             v,
1441         };
1442     }
1443     return hsv;
1444 }
1445 
1446 v3f
gui_get_picker_rgb(MiltonGui * gui)1447 gui_get_picker_rgb(MiltonGui* gui)
1448 {
1449     v3f rgb = hsv_to_rgb(gui->picker.data.hsv);
1450     return rgb;
1451 }
1452 
1453 // Returns true if the Picker consumed input. False if the GUI wasn't affected
1454 b32
gui_consume_input(MiltonGui * gui,MiltonInput const * input)1455 gui_consume_input(MiltonGui* gui, MiltonInput const* input)
1456 {
1457     b32 accepts = false;
1458     v2i point = VEC2I(input->points[0]);
1459     if ( gui->visible ) {
1460         accepts = gui_point_hovers(gui, point);
1461         if ( !picker_is_active(&gui->picker) &&
1462              !gui->did_hit_button &&
1463              picker_hit_history_buttons(&gui->picker, gui->scale, point) ) {
1464             accepts = true;
1465             gui->did_hit_button = true;
1466             gui->owns_user_input = true;
1467         }
1468         if ( accepts ) {
1469             ColorPickResult pick_result = picker_update(&gui->picker, point);
1470             if ( pick_result == ColorPickResult_CHANGE_COLOR ) {
1471                 gui->owns_user_input = true;
1472             }
1473         }
1474     }
1475     return accepts;
1476 }
1477 
1478 void
gui_toggle_menu_visibility(MiltonGui * gui)1479 gui_toggle_menu_visibility(MiltonGui* gui)
1480 {
1481     gui->menu_visible = !gui->menu_visible;
1482 }
1483 
1484 void
gui_toggle_help(MiltonGui * gui)1485 gui_toggle_help(MiltonGui* gui)
1486 {
1487     gui->show_help_widget = !gui->show_help_widget;
1488 }
1489 
1490 void
gui_init(Arena * root_arena,MiltonGui * gui,f32 ui_scale)1491 gui_init(Arena* root_arena, MiltonGui* gui, f32 ui_scale)
1492 {
1493     gui->scale = ui_scale;
1494     i32 bounds_radius_px = ui_scale*BOUNDS_RADIUS_PX;
1495     f32 wheel_half_width = ui_scale*12;
1496     gui->picker.center = v2i{ bounds_radius_px + int(ui_scale*20), bounds_radius_px + (int)(ui_scale*30) };
1497     gui->picker.bounds_radius_px = bounds_radius_px;
1498     gui->picker.wheel_half_width = wheel_half_width;
1499     gui->picker.wheel_radius = (f32)bounds_radius_px - ui_scale*5.0f - wheel_half_width;
1500     gui->picker.data.hsv = v3f{ 0.0f, 1.0f, 0.7f };
1501     Rect bounds;
1502     bounds.left = gui->picker.center.x - bounds_radius_px;
1503     bounds.right = gui->picker.center.x + bounds_radius_px;
1504     bounds.top = gui->picker.center.y - bounds_radius_px;
1505     bounds.bottom = gui->picker.center.y + bounds_radius_px;
1506     gui->picker.bounds = bounds;
1507     gui->picker.pixels = arena_alloc_array(root_arena, (4 * bounds_radius_px * bounds_radius_px), u32);
1508     gui->visible = true;
1509     gui->picker.color_buttons = arena_alloc_elem(root_arena, ColorButton);
1510     gui->original_settings = arena_alloc_elem(root_arena, MiltonSettings);
1511 
1512     picker_init(&gui->picker);
1513 
1514     i32 num_buttons = NUM_BUTTONS;
1515     auto* cur_button = gui->picker.color_buttons;
1516     for ( i64 i = 0; i != (num_buttons - 1); ++i ) {
1517         cur_button->next = arena_alloc_elem(root_arena, ColorButton);
1518         cur_button = cur_button->next;
1519     }
1520     update_button_bounds(&gui->picker, gui->scale);
1521 
1522     gui->preview_pos      = v2i{-1, -1};
1523     gui->preview_pos_prev = v2i{-1, -1};
1524 
1525 
1526     exporter_init(&gui->exporter);
1527 }
1528 
1529 // When a selected color is used in a stroke, call this to update the color
1530 // button list.
1531 b32
gui_mark_color_used(MiltonGui * gui)1532 gui_mark_color_used(MiltonGui* gui)
1533 {
1534     b32 changed = false;
1535     ColorButton* start = gui->picker.color_buttons;
1536     v3f picker_color  = hsv_to_rgb(gui->picker.data.hsv);
1537     // Search for a color that is already in the list
1538     ColorButton* button = start;
1539     while ( button ) {
1540         if ( button->rgba.a != 0 ) {
1541             v3f diff =
1542             {
1543                 fabsf(button->rgba.r - picker_color.r),
1544                 fabsf(button->rgba.g - picker_color.g),
1545                 fabsf(button->rgba.b - picker_color.b),
1546             };
1547             float epsilon = 0.000001f;
1548             if ( diff.r < epsilon && diff.g < epsilon && diff.b < epsilon ) {
1549                 // Move this button to the start and return.
1550                 changed = true;
1551                 v4f tmp_color = button->rgba;
1552                 button->rgba = start->rgba;
1553                 start->rgba = tmp_color;
1554             }
1555         }
1556         button = button->next;
1557     }
1558     button = start;
1559 
1560     // If not found, add to list.
1561     if ( !changed ) {
1562         changed = true;
1563         v4f button_color = color_rgb_to_rgba(picker_color,1);
1564         // Pass data to the next one.
1565         while ( button ) {
1566             v4f tmp_color = button->rgba;
1567             button->rgba = button_color;
1568             button_color = tmp_color;
1569             button = button->next;
1570         }
1571     }
1572 
1573     return changed;
1574 }
1575 
1576 void
gui_deactivate(MiltonGui * gui)1577 gui_deactivate(MiltonGui* gui)
1578 {
1579     picker_deactivate(&gui->picker);
1580 
1581     // Reset transient values
1582     gui->owns_user_input = false;
1583     gui->did_hit_button = false;
1584 }
1585