1 // Copyright (c) 2015 Sergio Gonzalez. All rights reserved.
2 // License: https://github.com/serge-rgb/milton#license
3
4 #include "milton.h"
5
6 #include "common.h"
7 #include "color.h"
8 #include "canvas.h"
9 #include "gui.h"
10 #include "renderer.h"
11 #include "localization.h"
12 #include "persist.h"
13 #include "platform.h"
14 #include "vector.h"
15 #include "bindings.h"
16
17 // Defined below.
18 static void milton_validate(Milton* milton);
19
20 void
milton_set_background_color(Milton * milton,v3f background_color)21 milton_set_background_color(Milton* milton, v3f background_color)
22 {
23 milton->view->background_color = background_color;
24 gpu_update_background(milton->renderer, milton->view->background_color);
25 }
26
27 static void
init_view(CanvasView * view,v3f background_color,i32 width,i32 height)28 init_view(CanvasView* view, v3f background_color, i32 width, i32 height)
29 {
30 milton_log("Setting default view\n");
31
32 auto size = view->screen_size;
33
34 *view = CanvasView{};
35
36 view->size = sizeof(CanvasView);
37 view->background_color = background_color;
38 view->screen_size = size;
39 view->zoom_center = size / 2;
40 view->scale = MILTON_DEFAULT_SCALE;
41 view->angle = 0.0f;
42 view->screen_size = { width, height };
43 }
44
45 int
milton_get_brush_enum(Milton const * milton)46 milton_get_brush_enum(Milton const* milton)
47 {
48 int brush_enum = BrushEnum_NOBRUSH;
49 switch ( milton->current_mode ) {
50 case MiltonMode::PEN: {
51 brush_enum = BrushEnum_PEN;
52 } break;
53 case MiltonMode::ERASER: {
54 brush_enum = BrushEnum_ERASER;
55 } break;
56 case MiltonMode::PRIMITIVE: {
57 brush_enum = BrushEnum_PRIMITIVE;
58 } break;
59 case MiltonMode::DRAG_BRUSH_SIZE: {
60 brush_enum = milton->drag_brush->brush_idx;
61 } break;
62 case MiltonMode::EXPORTING:
63 case MiltonMode::EYEDROPPER:
64 case MiltonMode::HISTORY:
65 default: {
66 } break;
67 }
68 return brush_enum;
69 }
70
71 static void
milton_update_brushes(Milton * milton)72 milton_update_brushes(Milton* milton)
73 {
74 for ( int i = 0; i < BrushEnum_COUNT; ++i ) {
75 Brush* brush = &milton->brushes[i];
76 i32 size = milton->brush_sizes[i];
77
78 brush->radius = size * milton->view->scale;
79 mlt_assert(brush->radius < FLT_MAX);
80 if ( i == BrushEnum_PEN ) {
81 // Alpha is set by the UI
82 brush->color = to_premultiplied(gui_get_picker_rgb(milton->gui), brush->alpha);
83 }
84 else if ( i == BrushEnum_ERASER ) {
85 // Nothing
86 }
87 else if ( i == BrushEnum_PRIMITIVE ) {
88 brush->color = to_premultiplied(gui_get_picker_rgb(milton->gui), brush->alpha);
89 }
90 }
91 }
92
93
94 // Eyedropper
95 void
eyedropper_init(Milton * milton)96 eyedropper_init(Milton* milton)
97 {
98 size_t bpp = 4;
99 i32 w = milton->view->screen_size.w;
100 i32 h = milton->view->screen_size.h;
101
102 Eyedropper* e = milton->eyedropper;
103 if (!e->buffer)
104 {
105 e->buffer = (u8*)mlt_calloc(w*h*bpp, 1, "Bitmap");
106 gpu_render_to_buffer(milton, e->buffer, 1, 0, 0, w, h, 1.0f);
107 }
108 }
109
110 void
eyedropper_input(Eyedropper * e,MiltonGui * gui,i32 w,i32 h,v2i point)111 eyedropper_input(Eyedropper* e, MiltonGui* gui, i32 w, i32 h, v2i point)
112 {
113
114 u32* pixels = (u32*)e->buffer;
115 if ( point.y > 0 && point.y <= h && point.x > 0 && point.x <= w ) {
116 v4f color = color_u32_to_v4f(pixels[point.y * w + point.x]);
117 gui_picker_from_rgb(&gui->picker, color.rgb);
118 }
119 }
120
121 void
eyedropper_deinit(Eyedropper * e)122 eyedropper_deinit(Eyedropper* e)
123 {
124 if ( e->buffer ) {
125 mlt_free(e->buffer, "Bitmap");
126 e->buffer = NULL;
127 }
128 }
129
130 static Brush
milton_get_brush(Milton const * milton)131 milton_get_brush(Milton const* milton)
132 {
133 int brush_enum = milton_get_brush_enum(milton);
134
135 Brush brush = milton->brushes[brush_enum];
136
137 return brush;
138 }
139
140 static i32*
pointer_to_brush_size(Milton * milton)141 pointer_to_brush_size(Milton* milton)
142 {
143 int brush_enum = milton_get_brush_enum(milton);
144 i32* ptr = &milton->brush_sizes[brush_enum];
145 return ptr;
146 }
147
148 static b32
is_user_drawing(Milton const * milton)149 is_user_drawing(Milton const* milton)
150 {
151 b32 result = milton->working_stroke.num_points > 0;
152 return result;
153 }
154
155 static b32
mode_is_for_drawing(MiltonMode mode)156 mode_is_for_drawing(MiltonMode mode)
157 {
158 b32 result = mode == MiltonMode::PEN ||
159 mode == MiltonMode::ERASER ||
160 mode == MiltonMode::PRIMITIVE ||
161 mode == MiltonMode::DRAG_BRUSH_SIZE;
162 return result;
163 }
164
165 static size_t
get_gui_visibility_index(Milton * milton)166 get_gui_visibility_index(Milton* milton)
167 {
168 size_t idx = Milton::GuiVisibleCategory_OTHER;
169 if (current_mode_is_for_drawing(milton)) {
170 idx = Milton::GuiVisibleCategory_DRAWING;
171 }
172 else if (milton->current_mode == MiltonMode::EXPORTING) {
173 idx = Milton::GuiVisibleCategory_EXPORTING;
174 }
175 return idx;
176 }
177
178 void
milton_toggle_gui_visibility(Milton * milton)179 milton_toggle_gui_visibility(Milton* milton)
180 {
181 size_t idx = get_gui_visibility_index(milton);
182 milton->mode_gui_visibility[idx] = !milton->mode_gui_visibility[idx];
183 }
184
185 void
milton_set_gui_visibility(Milton * milton,b32 visible)186 milton_set_gui_visibility(Milton* milton, b32 visible)
187 {
188 size_t idx = get_gui_visibility_index(milton);
189 milton->mode_gui_visibility[idx] = visible;
190 }
191
192 b32
current_mode_is_for_drawing(Milton const * milton)193 current_mode_is_for_drawing(Milton const* milton)
194 {
195 return mode_is_for_drawing(milton->current_mode);
196 }
197
198 static void
clear_stroke_redo(Milton * milton)199 clear_stroke_redo(Milton* milton)
200 {
201 while ( milton->canvas->stroke_graveyard.count > 0 ) {
202 Stroke s = pop(&milton->canvas->stroke_graveyard);
203 }
204 for ( i64 i = 0; i < milton->canvas->redo_stack.count; ++i ) {
205 HistoryElement h = milton->canvas->redo_stack.data[i];
206 if ( h.type == HistoryElement_STROKE_ADD ) {
207 for ( i64 j = i; j < milton->canvas->redo_stack.count-1; ++j ) {
208 milton->canvas->redo_stack.data[j] = milton->canvas->redo_stack.data[j+1];
209 }
210 pop(&milton->canvas->redo_stack);
211 }
212 }
213 }
214
215 static void
milton_primitive_input(Milton * milton,MiltonInput const * input,b32 end_stroke)216 milton_primitive_input(Milton* milton, MiltonInput const* input, b32 end_stroke)
217 {
218 if ( end_stroke && milton->primitive_fsm == Primitive_DRAWING) {
219 milton->primitive_fsm = Primitive_WAITING;
220 }
221 else if (input->input_count > 0) {
222 v2l point = raster_to_canvas(milton->view, input->points[input->input_count - 1]);
223 Stroke* ws = &milton->working_stroke;
224 if ( milton->primitive_fsm == Primitive_WAITING ) {
225 milton->primitive_fsm = Primitive_DRAWING;
226 ws->points[0] = ws->points[1] = point;
227 ws->pressures[0] = ws->pressures[1] = 1.0f;
228 milton->working_stroke.num_points = 2;
229 ws->brush = milton_get_brush(milton);
230 ws->layer_id = milton->view->working_layer_id;
231 }
232 else if ( milton->primitive_fsm == Primitive_DRAWING ) {
233 milton->working_stroke.points[1] = point;
234 }
235 }
236 }
237
238 void
stroke_append_point(Stroke * stroke,v2l canvas_point,f32 pressure)239 stroke_append_point(Stroke* stroke, v2l canvas_point, f32 pressure)
240 {
241 b32 not_the_first = stroke->num_points >= 1;
242
243 // A point passes inspection if:
244 // Add to current stroke.
245 if ( stroke->num_points < STROKE_MAX_POINTS ) {
246 int index = stroke->num_points++;
247 stroke->points[index] = canvas_point;
248 stroke->pressures[index] = pressure;
249 }
250 }
251
252 static v2l
smooth_filter(SmoothFilter * filter,v2l input)253 smooth_filter(SmoothFilter* filter, v2l input)
254 {
255 v2f point = v2l_to_v2f(input - filter->center);
256
257 if (filter->first)
258 {
259 filter->prediction = point;
260 filter->first = false;
261 }
262 else
263 {
264 f32 alpha = 0.5;
265
266 filter->prediction = alpha * point + (1 - alpha) * filter->prediction;
267 }
268 v2l result = v2f_to_v2l(filter->prediction) + filter->center;
269 return result;
270 }
271
272 static void
clear_smooth_filter(SmoothFilter * filter,v2l center)273 clear_smooth_filter(SmoothFilter* filter, v2l center)
274 {
275 filter->first = true;
276 filter->center = center;
277 }
278
279 static u64 peek_out_duration_ms(Milton* milton); // forward decl
280
281 static float
peek_out_interpolation(Milton * milton)282 peek_out_interpolation(Milton* milton)
283 {
284 float interp = 0.0f;
285
286 WallTime time = platform_get_walltime();
287 u64 ms = difference_in_ms(milton->peek_out->begin_anim_time, time);
288
289 if (ms < peek_out_duration_ms(milton)) {
290 interp = ms / (float)peek_out_duration_ms(milton);
291 }
292 else {
293 interp = 1.0f;
294 }
295
296 if (milton->peek_out->peek_out_ended) {
297 interp = 1 - interp;
298 }
299 return interp;
300 }
301
302 i64
milton_render_scale_with_interpolation(Milton * milton,float interp)303 milton_render_scale_with_interpolation(Milton* milton, float interp)
304 {
305 if ( milton->current_mode == MiltonMode::PEEK_OUT ) {
306 return lerp(milton->peek_out->low_scale, milton->peek_out->high_scale, interp);
307 }
308 else {
309 return milton->view->scale;
310 }
311 }
312
313 i64
milton_render_scale(Milton * milton)314 milton_render_scale(Milton* milton)
315 {
316 float interp = 1.0f;
317 if (milton->current_mode == MiltonMode::PEEK_OUT) {
318 interp = peek_out_interpolation(milton);
319 }
320 return milton_render_scale_with_interpolation(milton, interp);
321 }
322
323 static void
milton_stroke_input(Milton * milton,MiltonInput const * input)324 milton_stroke_input(Milton* milton, MiltonInput const* input)
325 {
326 if ( input->input_count == 0 ) {
327 return;
328 }
329
330 if ( milton->view->scale != milton_render_scale(milton) ) {
331 return; // We can't draw while peeking.
332 }
333
334 Stroke* ws = &milton->working_stroke;
335
336 if ((milton->flags & MiltonStateFlags_BRUSH_SMOOTHING) && ws->num_points == 0) {
337 clear_smooth_filter(milton->smooth_filter, input->points[0]);
338 }
339
340 //milton_log("Stroke input with %d packets\n", input->input_count);
341 ws->brush = milton_get_brush(milton);
342 ws->layer_id = milton->view->working_layer_id;
343
344 for ( int input_i = 0; input_i < input->input_count; ++input_i ) {
345
346 v2l in_point = input->points[input_i];
347 if (milton->flags & MiltonStateFlags_BRUSH_SMOOTHING) {
348 in_point = smooth_filter(milton->smooth_filter, in_point);
349 }
350
351 v2l canvas_point = raster_to_canvas(milton->view, in_point);
352
353 f32 pressure = NO_PRESSURE_INFO;
354
355 if ( input->pressures[input_i] != NO_PRESSURE_INFO ) {
356 f32 pressure_min = 0.01f;
357 pressure = pressure_min + input->pressures[input_i] * (1.0f - pressure_min);
358 } else {
359 pressure = 1.0f;
360 }
361
362 stroke_append_point(ws, canvas_point, pressure);
363 }
364 }
365
366 void
milton_set_zoom_at_point_with_scale(Milton * milton,v2i new_zoom_center,i64 scale)367 milton_set_zoom_at_point_with_scale(Milton* milton, v2i new_zoom_center, i64 scale)
368 {
369 milton->view->pan_center = raster_to_canvas_with_scale(milton->view, v2i_to_v2l(new_zoom_center), scale);
370 milton->view->zoom_center = new_zoom_center;
371 gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
372 }
373
374 void
milton_set_zoom_at_point(Milton * milton,v2i new_zoom_center)375 milton_set_zoom_at_point(Milton* milton, v2i new_zoom_center)
376 {
377 milton_set_zoom_at_point_with_scale(milton, new_zoom_center, milton_render_scale(milton));
378 }
379
380 void
milton_set_zoom_at_screen_center(Milton * milton)381 milton_set_zoom_at_screen_center(Milton* milton)
382 {
383 milton_set_zoom_at_point(milton, milton->view->screen_size / 2);
384 }
385
386 void
milton_set_canvas_file_(Milton * milton,PATH_CHAR * fname,b32 is_default)387 milton_set_canvas_file_(Milton* milton, PATH_CHAR* fname, b32 is_default)
388 {
389 milton_log("Set milton file: %s\n", fname);
390 if ( is_default ) {
391 milton->flags |= MiltonStateFlags_DEFAULT_CANVAS;
392 } else {
393 milton->flags &= ~MiltonStateFlags_DEFAULT_CANVAS;
394 }
395
396 u64 len = PATH_STRLEN(fname);
397 if ( len > MAX_PATH ) {
398 milton_log("milton_set_canvas_file: fname was too long %lu\n", len);
399 fname = TO_PATH_STR("MiltonPersist.mlt");
400 }
401 milton->persist->mlt_file_path = fname;
402
403 if ( !is_default ) {
404 milton_set_last_canvas_fname(fname);
405 } else {
406 milton_unset_last_canvas_fname();
407 }
408 }
409
410 void
milton_set_canvas_file(Milton * milton,PATH_CHAR * fname)411 milton_set_canvas_file(Milton* milton, PATH_CHAR* fname)
412 {
413 milton_set_canvas_file_(milton, fname, false);
414 }
415
416 // Helper function
417 void
milton_set_default_canvas_file(Milton * milton)418 milton_set_default_canvas_file(Milton* milton)
419 {
420 PATH_CHAR* f = (PATH_CHAR*)mlt_calloc(MAX_PATH, sizeof(PATH_CHAR), "Strings");
421
422 PATH_STRNCPY(f, TO_PATH_STR("MiltonPersist.mlt"), MAX_PATH);
423 platform_fname_at_config(f, MAX_PATH);
424 milton_set_canvas_file_(milton, f, true);
425 milton->flags |= MiltonStateFlags_DEFAULT_CANVAS;
426 }
427
428 static i32
milton_get_brush_radius_for_enum(Milton const * milton,int brush_enum)429 milton_get_brush_radius_for_enum(Milton const* milton, int brush_enum)
430 {
431 i32 brush_size = milton->brush_sizes[brush_enum];
432 if ( brush_size <= 0 ) {
433 brush_size = 1;
434 }
435 return brush_size;
436 }
437
438 i32
milton_get_brush_radius(Milton const * milton)439 milton_get_brush_radius(Milton const* milton)
440 {
441 i32 radius = milton_get_brush_radius_for_enum(milton, milton_get_brush_enum(milton));
442 return radius;
443 }
444
445 void
milton_set_brush_size_for_enum(Milton * milton,i32 size,int brush_idx)446 milton_set_brush_size_for_enum(Milton* milton, i32 size, int brush_idx)
447 {
448 if ( current_mode_is_for_drawing(milton) ) {
449 (*pointer_to_brush_size(milton)) = size;
450 milton_update_brushes(milton);
451 }
452 }
453
454 void
milton_set_brush_size(Milton * milton,i32 size)455 milton_set_brush_size(Milton* milton, i32 size)
456 {
457 milton_set_brush_size_for_enum(milton, size, milton_get_brush_enum(milton));
458 }
459
460
461 // For keyboard shortcut.
462 void
milton_increase_brush_size(Milton * milton)463 milton_increase_brush_size(Milton* milton)
464 {
465 if ( current_mode_is_for_drawing(milton) ) {
466 i32 brush_size = milton_get_brush_radius(milton);
467 if ( brush_size < MILTON_MAX_BRUSH_SIZE && brush_size > 0 ) {
468 milton_set_brush_size(milton, brush_size + 1);
469 }
470 milton_update_brushes(milton);
471 }
472 }
473
474 // For keyboard shortcut.
475 void
milton_decrease_brush_size(Milton * milton)476 milton_decrease_brush_size(Milton* milton)
477 {
478 if ( current_mode_is_for_drawing(milton) ) {
479 i32 brush_size = milton_get_brush_radius(milton);
480 if ( brush_size > 1 ) {
481 milton_set_brush_size(milton, brush_size - 1);
482 }
483 milton_update_brushes(milton);
484 }
485 }
486
487 void
milton_set_brush_alpha(Milton * milton,float alpha)488 milton_set_brush_alpha(Milton* milton, float alpha)
489 {
490 int brush_enum = milton_get_brush_enum(milton);
491
492 milton->brushes[brush_enum].alpha = alpha;
493 milton->brushes[brush_enum].pressure_opacity_min = min(alpha, milton->brushes[brush_enum].pressure_opacity_min);
494 milton_update_brushes(milton);
495 }
496
497 float
milton_get_brush_alpha(Milton const * milton)498 milton_get_brush_alpha(Milton const* milton)
499 {
500 int brush_enum = milton_get_brush_enum(milton);
501 const float alpha = milton->brushes[brush_enum].alpha;
502 return alpha;
503 }
504
505 void
settings_init(MiltonSettings * s)506 settings_init(MiltonSettings* s)
507 {
508 s->background_color = v3f{1,1,1};
509 s->peek_out_increment = DEFAULT_PEEK_OUT_INCREMENT_LOG;
510 }
511
512 int milton_save_thread(void* state_); // forward
513
514 void
reset_working_stroke(Milton * milton)515 reset_working_stroke(Milton* milton)
516 {
517 milton->working_stroke.num_points = 0;
518 gpu_reset_stroke(milton->renderer, milton->working_stroke.render_handle);
519 milton->working_stroke.bounding_rect = rect_without_size();
520 }
521
522
523 void
milton_init(Milton * milton,i32 width,i32 height,f32 ui_scale,PATH_CHAR * file_to_open,MiltonInitFlags init_flags)524 milton_init(Milton* milton, i32 width, i32 height, f32 ui_scale, PATH_CHAR* file_to_open, MiltonInitFlags init_flags)
525 {
526 b32 init_graphics = !(init_flags & MiltonInit_FOR_TEST);
527 b32 read_from_disk = !(init_flags & MiltonInit_FOR_TEST);
528
529 init_localization();
530
531 milton->canvas = arena_bootstrap(CanvasState, arena, 1024*1024);
532 milton->working_stroke.points = arena_alloc_array(&milton->root_arena, STROKE_MAX_POINTS, v2l);
533 milton->working_stroke.pressures = arena_alloc_array(&milton->root_arena, STROKE_MAX_POINTS, f32);
534 #if STROKE_DEBUG_VIZ
535 milton->working_stroke.debug_flags = arena_alloc_array(&milton->root_arena, STROKE_MAX_POINTS, int);
536 #endif
537
538 reset_working_stroke(milton);
539
540 milton->current_mode = MiltonMode::PEN;
541
542 milton->renderer = gpu_allocate_render_backend(&milton->root_arena);
543
544 milton->smooth_filter = arena_alloc_elem(&milton->root_arena, SmoothFilter);
545
546 if (init_graphics) { milton->gl = arena_alloc_elem(&milton->root_arena, MiltonGLState); }
547 milton->gui = arena_alloc_elem(&milton->root_arena, MiltonGui);
548 milton->settings = arena_alloc_elem(&milton->root_arena, MiltonSettings);
549 milton->eyedropper = arena_alloc_elem(&milton->root_arena, Eyedropper);
550 milton->persist = arena_alloc_elem(&milton->root_arena, MiltonPersist);
551 milton->drag_brush = arena_alloc_elem(&milton->root_arena, MiltonDragBrush);
552 milton->transform = arena_alloc_elem(&milton->root_arena, TransformMode);
553
554 milton->persist->target_MB_per_sec = 0.2f;
555
556 gui_init(&milton->root_arena, milton->gui, ui_scale);
557 settings_init(milton->settings);
558
559 milton->peek_out = arena_alloc_elem(&milton->root_arena, PeekOut);
560
561 b32 loaded_settings = false;
562 if (read_from_disk) {
563 loaded_settings = milton_settings_load(milton->settings);
564 }
565
566 if (!loaded_settings) {
567 set_default_bindings(&milton->settings->bindings);
568 }
569
570 milton->view = arena_alloc_elem(&milton->root_arena, CanvasView);
571
572 init_view(milton->view, milton->settings->background_color, width, height);
573 if (init_graphics) { gpu_init(milton->renderer, milton->view, &milton->gui->picker); }
574
575 if (init_graphics) { gpu_update_background(milton->renderer, milton->view->background_color); }
576
577 { // Get/Set Milton Canvas (.mlt) file
578 if ( file_to_open == NULL ) {
579 PATH_CHAR* last_fname = milton_get_last_canvas_fname();
580
581 if ( last_fname != NULL ) {
582 milton_set_canvas_file(milton, last_fname);
583 } else {
584 milton_set_default_canvas_file(milton);
585 }
586 }
587 else {
588 milton_set_canvas_file(milton, file_to_open);
589 }
590 }
591
592 // Set default brush.
593 {
594 for ( int i = 0; i < BrushEnum_COUNT; ++i ) {
595
596 milton->brushes[i].alpha = 1.0f;
597 milton->brushes[i].hardness = k_max_hardness;
598
599 switch ( i ) {
600 case BrushEnum_PEN: {
601 milton->brush_sizes[i] = 30;
602 } break;
603 case BrushEnum_ERASER: {
604 milton->brush_sizes[i] = 40;
605 } break;
606 case BrushEnum_PRIMITIVE: {
607 milton->brush_sizes[i] = 10;
608 } break;
609 case BrushEnum_NOBRUSH: { {
610 milton->brush_sizes[i] = 1;
611 } } break;
612 default: {
613 INVALID_CODE_PATH;
614 } break;
615 }
616 mlt_assert(milton->brush_sizes[i] > 0 && milton->brush_sizes[i] <= MILTON_MAX_BRUSH_SIZE);
617 }
618 }
619
620 milton->persist->last_save_time = {};
621 // Note: This will fill out uninitialized data like default layers.
622 if (read_from_disk) { milton_load(milton); }
623
624 milton_validate(milton);
625
626 // Enable brush smoothing by default
627 if ( !milton_brush_smoothing_enabled(milton) ) {
628 milton_toggle_brush_smoothing(milton);
629 }
630
631 // Default mode GUI visibility
632 milton->mode_gui_visibility[Milton::GuiVisibleCategory_DRAWING] = true;
633
634 #if MILTON_ENABLE_PROFILING
635 milton->viz_window_visible = false; // hidden by default
636 #endif
637
638 milton->flags |= MiltonStateFlags_RUNNING;
639
640 #if MILTON_ENABLE_PROFILING
641 profiler_init();
642 #endif
643
644 #if MILTON_SAVE_ASYNC
645 milton->save_mutex = SDL_CreateMutex();
646 milton->save_cond = SDL_CreateCond();
647 milton->save_thread = SDL_CreateThread(milton_save_thread, "Save thread", (void*)milton);
648 #endif
649 }
650
651 void
upload_gui(Milton * milton)652 upload_gui(Milton* milton)
653 {
654 if (milton->gl)
655 {
656 gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
657 gpu_resize(milton->renderer, milton->view);
658 gpu_update_picker(milton->renderer, &milton->gui->picker);
659 }
660 }
661
662 // Returns false if the pan_delta moves the pan vector outside of the canvas.
663 void
milton_resize_and_pan(Milton * milton,v2l pan_delta,v2i new_screen_size)664 milton_resize_and_pan(Milton* milton, v2l pan_delta, v2i new_screen_size)
665 {
666 if ( milton->max_width <= new_screen_size.w ) {
667 milton->max_width = new_screen_size.w + 256;
668 }
669 if ( milton->max_height <= new_screen_size.h ) {
670 milton->max_height = new_screen_size.h + 256;
671 }
672
673 if ( new_screen_size.w < milton->max_width && new_screen_size.h < milton->max_height ) {
674 milton->view->screen_size = new_screen_size;
675
676 f32 x = pan_delta.x;
677 f32 y = pan_delta.y;
678
679 f32 cos_angle = cosf(milton->view->angle);
680 f32 sin_angle = sinf(milton->view->angle);
681
682 v2f deltaf = v2f{x * cos_angle - y * sin_angle, y * cos_angle + x * sin_angle };
683 // Add delta to pan vector
684 v2l pan_center = milton->view->pan_center - (v2f_to_v2l(deltaf) * milton->view->scale);
685
686 milton->view->pan_center = pan_center;
687
688 upload_gui(milton);
689 } else {
690 milton_die_gracefully("Fatal error. Screen size is more than Milton can handle.");
691 }
692 }
693
694 void
milton_reset_canvas(Milton * milton)695 milton_reset_canvas(Milton* milton)
696 {
697 CanvasState* canvas = milton->canvas;
698
699 gpu_free_strokes(milton->renderer, milton->canvas);
700 milton->persist->mlt_binary_version = MILTON_MINOR_VERSION;
701 milton->persist->last_save_time = {};
702
703 // Clear history
704 release(&canvas->history);
705 release(&canvas->redo_stack);
706 release(&canvas->stroke_graveyard);
707
708 size_t size = canvas->arena.min_block_size;
709 arena_free(&canvas->arena); // Note: This destroys the canvas
710 milton->canvas = arena_bootstrap(CanvasState, arena, size);
711
712 mlt_assert(milton->canvas->history.count == 0);
713 }
714
715 void
milton_reset_canvas_and_set_default(Milton * milton)716 milton_reset_canvas_and_set_default(Milton* milton)
717 {
718 milton_reset_canvas(milton);
719
720 // New Root
721 milton_new_layer(milton);
722
723 // New View
724 init_view(milton->view,
725 milton->settings->background_color,
726 milton->view->screen_size.x,
727 milton->view->screen_size.y);
728 milton->view->background_color = milton->settings->background_color;
729 gpu_update_background(milton->renderer, milton->view->background_color);
730
731 // Reset color buttons
732 for ( ColorButton* b = milton->gui->picker.color_buttons; b!=NULL; b=b->next ) {
733 b->rgba = {};
734 }
735
736 // gui init
737 {
738 MiltonGui* gui = milton->gui;
739 gui->picker.data.hsv = { 0.0f, 1.0f, 0.7f };
740 gui->visible = true;
741
742 picker_init(&gui->picker);
743
744 gui->preview_pos = v2i{-1, -1};
745 gui->preview_pos_prev = v2i{-1, -1};
746
747 exporter_init(&gui->exporter);
748 }
749
750 milton_update_brushes(milton);
751
752 milton_set_default_canvas_file(milton);
753 upload_gui(milton);
754 }
755
756 static void
push_mode(Milton * milton,MiltonMode mode)757 push_mode(Milton* milton, MiltonMode mode)
758 {
759 if (milton->n_mode_stack < MODE_STACK_MAX) {
760 milton->mode_stack[ milton->n_mode_stack++ ] = mode;
761 }
762 }
763
764 static MiltonMode
pop_mode(Milton * milton)765 pop_mode(Milton* milton)
766 {
767 MiltonMode result = MiltonMode::PEN;
768 if (milton->n_mode_stack) {
769 result = milton->mode_stack[ --milton->n_mode_stack ];
770 }
771 return result;
772 }
773
774 MiltonMode
milton_leave_mode(Milton * milton)775 milton_leave_mode(Milton* milton)
776 {
777 if (milton->current_mode == MiltonMode::EYEDROPPER) {
778 eyedropper_deinit(milton->eyedropper);
779 }
780 MiltonMode leaving = milton->current_mode;
781 milton->current_mode = pop_mode(milton);
782 return leaving;
783 }
784
785 void
milton_enter_mode(Milton * milton,MiltonMode mode)786 milton_enter_mode(Milton* milton, MiltonMode mode)
787 {
788 if ( mode != milton->current_mode ) {
789 if (mode == MiltonMode::EYEDROPPER) {
790 eyedropper_init(milton);
791 }
792 push_mode(milton, milton->current_mode);
793 milton->current_mode = mode;
794
795 if (is_user_drawing(milton)) {
796 milton->flags |= MiltonStateFlags_FINISH_CURRENT_STROKE;
797 }
798 }
799 }
800
801 void
milton_try_quit(Milton * milton)802 milton_try_quit(Milton* milton)
803 {
804 milton->flags &= ~MiltonStateFlags_RUNNING;
805 }
806
807 void
milton_save_postlude(Milton * milton)808 milton_save_postlude(Milton* milton)
809 {
810 MiltonPersist* p = milton->persist;
811 p->last_save_time = platform_get_walltime();
812 p->last_save_stroke_count = layer::count_strokes(milton->canvas->root_layer);
813
814 milton->flags &= ~MiltonStateFlags_LAST_SAVE_FAILED;
815 }
816
817 #if MILTON_SAVE_ASYNC
818 void
trigger_async_save(Milton * milton)819 trigger_async_save(Milton* milton)
820 {
821 SDL_LockMutex(milton->save_mutex);
822 {
823 milton->save_flag = SaveEnum_SAVE_REQUESTED;
824 }
825 SDL_UnlockMutex(milton->save_mutex);
826 }
827
828 void
milton_kill_save_thread(Milton * milton)829 milton_kill_save_thread(Milton* milton)
830 {
831 SDL_LockMutex(milton->save_mutex);
832 milton->save_flag = SaveEnum_KILL;
833 SDL_UnlockMutex(milton->save_mutex);
834
835 // Do a save tick.
836 SDL_LockMutex(milton->save_mutex);
837 SDL_CondSignal(milton->save_cond);
838 SDL_UnlockMutex(milton->save_mutex);
839
840 SDL_WaitThread(milton->save_thread, NULL);
841 }
842
843 int
milton_save_thread(void * state_)844 milton_save_thread(void* state_)
845 {
846 Milton* milton = (Milton*)state_;
847 MiltonPersist* p = milton->persist;
848
849 b32 running = true;
850 float time_to_wait_s = 0.0f;
851 u64 wait_begin_us = perf_counter();
852
853 while ( running ) {
854 bool do_save = false;
855 SDL_LockMutex(milton->save_mutex);
856
857 SDL_CondWait(milton->save_cond, milton->save_mutex); // Wait for a frame tick.
858
859 if ( milton->save_flag == SaveEnum_KILL ) {
860 running = false;
861 }
862 else {
863 float time_waited_s = perf_count_to_sec(perf_counter() - wait_begin_us);
864 if (time_waited_s <= time_to_wait_s) {
865 time_to_wait_s -= time_waited_s;
866 }
867 else {
868 if ( milton->save_flag == SaveEnum_SAVE_REQUESTED ) {
869 do_save = true;
870 milton->save_flag = SaveEnum_WAITING;
871 }
872 }
873 wait_begin_us = perf_counter();
874 }
875 SDL_UnlockMutex(milton->save_mutex);
876
877 if ( do_save ) {
878 // Wait. Either one frame, or the time to stay below bandwidth.
879 u64 begin_us = perf_counter();
880 u64 bytes_written = milton_save(milton);
881 u64 duration_us = perf_counter() - begin_us;
882
883 // Sleep, if necessary.
884 float duration_s = duration_us / 1000000.0f;
885
886 float MB_written = bytes_written / (1024.0f * 1024.0f);
887 float MB_per_sec = MB_written / duration_s;
888
889 if (MB_per_sec > p->target_MB_per_sec) {
890 time_to_wait_s = MB_written / p->target_MB_per_sec - duration_s;
891 wait_begin_us = perf_counter();
892 }
893 }
894 }
895 return 0;
896 }
897 #endif
898
899 void
milton_new_layer(Milton * milton)900 milton_new_layer(Milton* milton)
901 {
902 CanvasState* canvas = milton->canvas;
903 i32 id = canvas->layer_guid++;
904 milton_log("Increased guid to %d\n", canvas->layer_guid);
905 milton_new_layer_with_id(milton, id);
906 }
907
908 void
milton_new_layer_with_id(Milton * milton,i32 new_id)909 milton_new_layer_with_id(Milton* milton, i32 new_id)
910 {
911 CanvasState* canvas = milton->canvas;
912 Layer* layer = arena_alloc_elem(&canvas->arena, Layer);
913 {
914 layer->id = new_id;
915 layer->flags = LayerFlags_VISIBLE;
916 layer->strokes.arena = &canvas->arena;
917 layer->alpha = 1.0f;
918 strokelist_init_bucket(&layer->strokes.root);
919 }
920 snprintf(layer->name, MAX_LAYER_NAME_LEN, "Layer %d", layer->id);
921
922 if ( canvas->root_layer != NULL ) {
923 Layer* top = layer::get_topmost(canvas->root_layer);
924 top->next = layer;
925 layer->prev = top;
926 milton_set_working_layer(milton, top->next);
927 } else {
928 canvas->root_layer = layer;
929 milton_set_working_layer(milton, layer);
930 }
931 }
932
933 void
milton_set_working_layer(Milton * milton,Layer * layer)934 milton_set_working_layer(Milton* milton, Layer* layer)
935 {
936 milton->canvas->working_layer = layer;
937 milton->view->working_layer_id = layer->id;
938 }
939
940 void
milton_delete_working_layer(Milton * milton)941 milton_delete_working_layer(Milton* milton)
942 {
943 Layer* layer = milton->canvas->working_layer;
944 if ( layer->next || layer->prev ) {
945 if (layer->next) layer->next->prev = layer->prev;
946 if (layer->prev) layer->prev->next = layer->next;
947
948 Layer* wl = NULL;
949 if (layer->next) wl = layer->next;
950 else wl = layer->prev;
951 milton_set_working_layer(milton, wl);
952 }
953 if ( layer == milton->canvas->root_layer ) {
954 milton->canvas->root_layer = milton->canvas->working_layer;
955 }
956 }
957
958 b32
milton_brush_smoothing_enabled(Milton * milton)959 milton_brush_smoothing_enabled(Milton* milton)
960 {
961 b32 enabled = (milton->flags & MiltonStateFlags_BRUSH_SMOOTHING);
962 return enabled;
963 }
964
965 void
milton_toggle_brush_smoothing(Milton * milton)966 milton_toggle_brush_smoothing(Milton* milton)
967 {
968 if ( milton_brush_smoothing_enabled(milton) ) {
969 milton->flags &= ~MiltonStateFlags_BRUSH_SMOOTHING;
970 } else {
971 milton->flags |= MiltonStateFlags_BRUSH_SMOOTHING;
972 }
973 }
974
975 static void
milton_validate(Milton * milton)976 milton_validate(Milton* milton)
977 {
978 // Make sure that the history reflects the strokes that exist
979 i64 num_layers=0;
980 for ( Layer* l = milton->canvas->root_layer; l != NULL; l = l->next ) {
981 ++num_layers;
982 }
983 i32* layer_ids = (i32*)mlt_calloc((size_t)num_layers, sizeof(i32), "Validate");
984 i64 i = 0;
985 for ( Layer* l = milton->canvas->root_layer; l != NULL; l = l->next ) {
986 layer_ids[i] = l->id;
987 ++i;
988 }
989
990 i64 history_count = 0;
991 for ( i64 hi = 0; hi < milton->canvas->history.count; ++hi ) {
992 i32 id = milton->canvas->history.data[hi].layer_id;
993 for ( i64 li = 0; li < num_layers; ++li ) {
994 if ( id == layer_ids[li] ) {
995 ++history_count;
996 }
997 }
998 }
999
1000 i64 stroke_count = layer::count_strokes(milton->canvas->root_layer);
1001 if ( history_count != stroke_count ) {
1002 milton_log("WARNING: Recreating history. File says History: %d(max %d) Actual strokes: %d\n",
1003 history_count, milton->canvas->history.count,
1004 stroke_count);
1005 reset(&milton->canvas->history);
1006 i32 id = 0;
1007 for ( Layer *l = milton->canvas->root_layer;
1008 l != NULL;
1009 l = l->next ) {
1010 for ( i64 si = 0; si < l->strokes.count; ++si ) {
1011 Stroke* s = get(&l->strokes, si);
1012 HistoryElement he = { HistoryElement_STROKE_ADD, s->layer_id };
1013 push(&milton->canvas->history, he);
1014 }
1015 }
1016 }
1017
1018 mlt_free(layer_ids, "Validate");
1019 }
1020
1021
1022 // Copy points from in_stroke to out_stroke, but do interpolation to smooth it out.
1023 static void
copy_stroke(Arena * arena,CanvasView * view,Stroke * in_stroke,Stroke * out_stroke)1024 copy_stroke(Arena* arena, CanvasView* view, Stroke* in_stroke, Stroke* out_stroke)
1025 {
1026 i32 num_points = in_stroke->num_points;
1027 // Shallow copy
1028 *out_stroke = *in_stroke;
1029
1030 // Deep copy
1031 out_stroke->points = arena_alloc_array(arena, num_points, v2l);
1032 out_stroke->pressures = arena_alloc_array(arena, num_points, f32);
1033
1034 memcpy(out_stroke->points, in_stroke->points, num_points * sizeof(v2l));
1035 memcpy(out_stroke->pressures, in_stroke->pressures, num_points * sizeof(f32));
1036
1037 #if STROKE_DEBUG_VIZ
1038 out_stroke->debug_flags = arena_alloc_array(arena, num_points * sizeof(int), int);
1039 memcpy(out_stroke->debug_flags, in_stroke->debug_flags, num_points*sizeof(int));
1040 #endif
1041
1042 out_stroke->render_handle = 0;
1043 }
1044
1045 static i64
peek_out_target_scale(Milton * milton)1046 peek_out_target_scale(Milton* milton)
1047 {
1048 double log_scale = log2(1 + milton->view->scale) / log2(SCALE_FACTOR);
1049 i64 target = min(pow(SCALE_FACTOR, log_scale + milton->settings->peek_out_increment), VIEW_SCALE_LIMIT);
1050 return target;
1051 }
1052
1053 static u64
peek_out_duration_ms(Milton * milton)1054 peek_out_duration_ms(Milton* milton)
1055 {
1056 u64 duration = milton->settings->peek_out_increment * PEEK_OUT_SPEED;
1057 return duration;
1058 }
1059
1060 void
peek_out_trigger_start(Milton * milton,int peek_out_flags)1061 peek_out_trigger_start(Milton* milton, int peek_out_flags)
1062 {
1063 milton_set_zoom_at_screen_center(milton);
1064 milton->peek_out->begin_pan = milton->view->pan_center;
1065
1066 milton->peek_out->low_scale = milton_render_scale(milton);
1067 milton->peek_out->high_scale = peek_out_target_scale(milton);
1068 milton->peek_out->peek_out_ended = false;
1069 milton->peek_out->begin_anim_time = platform_get_walltime();
1070 milton->peek_out->flags = peek_out_flags;
1071
1072 if (milton->current_mode != MiltonMode::PEEK_OUT) {
1073 milton_enter_mode(milton, MiltonMode::PEEK_OUT);
1074 }
1075 }
1076
1077 void
peek_out_trigger_stop(Milton * milton)1078 peek_out_trigger_stop(Milton* milton)
1079 {
1080 if (milton->current_mode == MiltonMode::PEEK_OUT && !milton->peek_out->peek_out_ended) {
1081 milton_set_zoom_at_point(milton, milton->platform->pointer);
1082
1083 i64 scale = milton_render_scale(milton);
1084 milton->peek_out->high_scale = scale;
1085 milton->peek_out->low_scale = milton->view->scale;
1086 milton->peek_out->begin_anim_time = platform_get_walltime();
1087 milton->peek_out->peek_out_ended = true;
1088 milton->peek_out->end_pan = raster_to_canvas_with_scale(milton->view, v2i_to_v2l(milton->platform->pointer), scale);
1089 }
1090 }
1091
1092 static void
peek_out_tick(Milton * milton,MiltonInput const * input)1093 peek_out_tick(Milton* milton, MiltonInput const* input)
1094 {
1095 PeekOut* peek = milton->peek_out;
1096
1097 if (milton->current_mode == MiltonMode::PEEK_OUT) {
1098
1099 float interp = peek_out_interpolation(milton);
1100
1101 if ((peek->flags & PeekOut_CLICK_TO_EXIT) && input->input_count > 0)
1102 {
1103 peek_out_trigger_stop(milton);
1104 }
1105
1106 if ( milton->peek_out->peek_out_ended ) {
1107 i64 panx = lerp(peek->end_pan.x, peek->begin_pan.x, interp);
1108 i64 pany = lerp(peek->end_pan.y, peek->begin_pan.y, interp);
1109
1110 v2l pan = { panx, pany };
1111
1112 milton->view->pan_center = pan;
1113 milton->view->zoom_center = milton->view->screen_size / 2;
1114 gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
1115
1116 if ( difference_in_ms(peek->begin_anim_time, platform_get_walltime()) > peek_out_duration_ms(milton) ) {
1117 milton_leave_mode(milton);
1118 }
1119 }
1120 gpu_update_scale(milton->renderer, milton_render_scale(milton));
1121
1122 {
1123 i32 width = 100;
1124 i32 height = 50;
1125 i32 line_width = 2;
1126
1127 // TODO: Interpolate pan vector
1128 f32 scale = (f32)milton_render_scale_with_interpolation(milton, interp);
1129
1130 f32 cx = 2 * milton->platform->pointer.x / (f32)milton->view->screen_size.w - 1;
1131 f32 cy = (2 * milton->platform->pointer.y / (f32)milton->view->screen_size.h - 1)*-1;
1132 f32 left = cx - milton->view->scale / scale;
1133 f32 right = cx + milton->view->scale / scale;
1134 f32 top = cy - milton->view->scale / scale;
1135 f32 bottom = cy + milton->view->scale / scale;
1136
1137 imm_rect(milton->renderer, left, right, top, bottom, line_width);
1138 }
1139 }
1140 }
1141
1142 void
drag_brush_size_start(Milton * milton,v2i pointer)1143 drag_brush_size_start(Milton* milton, v2i pointer)
1144 {
1145 if ( milton->current_mode != MiltonMode::DRAG_BRUSH_SIZE &&
1146 current_mode_is_for_drawing(milton) ) {
1147 milton->drag_brush->brush_idx = milton_get_brush_enum(milton);
1148 i32 size = milton_get_brush_radius(milton);
1149 milton->drag_brush->start_point = platform_cursor_get_position(milton->platform);
1150 milton->drag_brush->start_size = size;
1151 milton_enter_mode(milton, MiltonMode::DRAG_BRUSH_SIZE);
1152 }
1153 }
1154
1155 void
drag_brush_size_stop(Milton * milton)1156 drag_brush_size_stop(Milton* milton)
1157 {
1158 if (milton->current_mode == MiltonMode::DRAG_BRUSH_SIZE) {
1159 v2i point = milton->drag_brush->start_point;
1160 platform_cursor_set_position(milton->platform, point);
1161 milton_leave_mode(milton);
1162 }
1163 }
1164
1165 static void
drag_brush_size_tick(Milton * milton,MiltonInput const * input)1166 drag_brush_size_tick(Milton* milton, MiltonInput const* input)
1167 {
1168 MiltonDragBrush* drag = milton->drag_brush;
1169 f32 drag_factor = 0.5f;
1170 i64 mouse_x = platform_cursor_get_position(milton->platform).x;
1171
1172 f32 new_size = drag->start_size + drag_factor * (mouse_x - drag->start_point.x);
1173 if ( new_size < 1 )
1174 new_size = 1;
1175 if ( new_size > 300 )
1176 new_size = 300;
1177 milton_set_brush_size_for_enum(milton, static_cast<i32>(new_size), drag->brush_idx);
1178 milton_update_brushes(milton);
1179 }
1180
1181 void
transform_start(Milton * milton,v2i pointer)1182 transform_start(Milton* milton, v2i pointer)
1183 {
1184 if (milton->current_mode != MiltonMode::TRANSFORM) {
1185 milton_enter_mode(milton, MiltonMode::TRANSFORM);
1186 milton_set_zoom_at_screen_center(milton);
1187 }
1188 }
1189
1190 void
transform_stop(Milton * milton)1191 transform_stop(Milton* milton)
1192 {
1193 if (milton->current_mode == MiltonMode::TRANSFORM) {
1194 milton_leave_mode(milton);
1195 }
1196 }
1197
1198 static void
transform_tick(Milton * milton,MiltonInput const * input)1199 transform_tick(Milton* milton, MiltonInput const* input)
1200 {
1201 TransformMode* t = milton->transform;
1202
1203 if (input->input_count > 0) {
1204 v2f point = v2l_to_v2f(input->points[ input->input_count - 1 ]);
1205 if (t->fsm == TransformModeFSM::START) {
1206 t->fsm = TransformModeFSM::ROTATING;
1207 t->last_point = point;
1208 }
1209 else if (t->fsm == TransformModeFSM::ROTATING) {
1210 // Rotate!
1211 const v2f center = v2i_to_v2f(milton->view->screen_size / 2);
1212 const v2f a = point - center;
1213 const v2f b = t->last_point - center;
1214 const f32 lena = magnitude(a);
1215 const f32 lenb = magnitude(b);
1216 if (lena > 0.0f && lenb > 0.0f) {
1217 const f32 cos_angle = clamp(DOT(b, a) / (lena * lenb), 0.0f, 1.0f);
1218 const f32 sign = orientation(center, t->last_point, point) > 0 ? -1.0f : 1.0f;
1219 milton->view->angle += sign * acosf(cos_angle);
1220 t->last_point = point;
1221 gpu_update_canvas(milton->renderer, milton->canvas, milton->view);
1222 }
1223 }
1224 }
1225 if (input->flags & MiltonInputFlags_CLICKUP) {
1226 if (t->fsm == TransformModeFSM::ROTATING) {
1227 t->fsm = TransformModeFSM::START;
1228 }
1229 }
1230 }
1231
1232 void
milton_update_and_render(Milton * milton,MiltonInput const * input)1233 milton_update_and_render(Milton* milton, MiltonInput const* input)
1234 {
1235 imm_begin_frame(milton->renderer);
1236
1237 PROFILE_GRAPH_BEGIN(update);
1238
1239 b32 end_stroke = (input->flags & MiltonInputFlags_END_STROKE) || (milton->flags & MiltonStateFlags_FINISH_CURRENT_STROKE);
1240
1241 milton->flags &= ~MiltonStateFlags_FINISH_CURRENT_STROKE;
1242
1243 milton->render_settings.do_full_redraw = false;
1244
1245 b32 brush_outline_should_draw = false;
1246 int render_flags = RenderBackendFlags_NONE;
1247
1248 b32 draw_custom_rectangle = false; // Custom rectangle used for new strokes, undo/redo.
1249
1250 b32 should_save =
1251 ((input->flags & MiltonInputFlags_OPEN_FILE)) ||
1252 ((input->flags & MiltonInputFlags_SAVE_FILE)) ||
1253 ((input->flags & MiltonInputFlags_END_STROKE)) ||
1254 ((input->flags & MiltonInputFlags_UNDO)) ||
1255 ((input->flags & MiltonInputFlags_REDO));
1256
1257 if ( input->flags & MiltonInputFlags_OPEN_FILE ) {
1258 milton_load(milton);
1259 upload_gui(milton);
1260 milton->render_settings.do_full_redraw = true;
1261 }
1262
1263 i32 now = (i32)SDL_GetTicks();
1264
1265 // Set GUI visibility
1266 {
1267 size_t idx = get_gui_visibility_index(milton);
1268
1269 milton->gui->visible = milton->mode_gui_visibility[idx];
1270 }
1271
1272 if ( input->flags & MiltonInputFlags_FULL_REFRESH ) {
1273 milton->render_settings.do_full_redraw = true;
1274 }
1275
1276 if ( input->scale ) {
1277 milton->render_settings.do_full_redraw = true;
1278
1279 f32 scale_factor = SCALE_FACTOR;
1280 i32 view_scale_limit = VIEW_SCALE_LIMIT;
1281
1282 i32 min_scale = MINIMUM_SCALE;
1283
1284 // Update the current brush if it's canvas-relative
1285 if (milton->working_stroke.flags & StrokeFlag_RELATIVE_TO_CANVAS) {
1286
1287 i32* psize = pointer_to_brush_size(milton);
1288
1289 if ( input->scale > 0 && milton->view->scale >= min_scale ) {
1290 (*psize) = (i32)((*psize) * scale_factor) + 1;
1291 }
1292 else if ( input->scale < 0 && (*psize) < view_scale_limit ) {
1293 (*psize) = (i32)(ceilf((*psize) / scale_factor));
1294 }
1295 }
1296
1297 if ( input->scale > 0 && milton->view->scale >= min_scale ) {
1298 milton->view->scale = (i32)(ceilf(milton->view->scale / scale_factor));
1299 }
1300 else if ( input->scale < 0 && milton->view->scale < view_scale_limit ) {
1301 milton->view->scale = (i32)(milton->view->scale * scale_factor) + 1;
1302 }
1303
1304 milton_update_brushes(milton);
1305 gpu_update_scale(milton->renderer, milton->view->scale);
1306 }
1307 else if ( (input->flags & MiltonInputFlags_PANNING) ) {
1308 // If we are *not* zooming and we are panning, we can copy most of the
1309 // framebuffer
1310 if ( !(input->pan_delta == v2l{}) ) {
1311 milton->render_settings.do_full_redraw = true;
1312 }
1313 }
1314
1315 if ( input->mode_to_set != milton->current_mode
1316 && mode_is_for_drawing(input->mode_to_set)) {
1317 end_stroke = true;
1318 }
1319
1320 { // Undo / Redo
1321 if ( (input->flags & MiltonInputFlags_UNDO) ) {
1322 // Grab undo elements. They might be from deleted layers, so discard dead results.
1323 while ( milton->canvas->history.count > 0 ) {
1324 HistoryElement h = pop(&milton->canvas->history);
1325 Layer* l = layer::get_by_id(milton->canvas->root_layer, h.layer_id);
1326 // found a thing to undo.
1327 if ( l ) {
1328 if ( l->strokes.count > 0 ) {
1329 Stroke* stroke_ptr = peek(&l->strokes);
1330 Stroke stroke = pop(&l->strokes);
1331 push(&milton->canvas->stroke_graveyard, stroke);
1332 push(&milton->canvas->redo_stack, h);
1333
1334 milton->render_settings.do_full_redraw = true;
1335 }
1336 break;
1337 }
1338 }
1339 }
1340 else if ( (input->flags & MiltonInputFlags_REDO) ) {
1341 if ( milton->canvas->redo_stack.count > 0 ) {
1342 HistoryElement h = pop(&milton->canvas->redo_stack);
1343 switch ( h.type ) {
1344 case HistoryElement_STROKE_ADD: {
1345 Layer* l = layer::get_by_id(milton->canvas->root_layer, h.layer_id);
1346 if ( l && count(&milton->canvas->stroke_graveyard) > 0 ) {
1347 Stroke stroke = pop(&milton->canvas->stroke_graveyard);
1348 if ( stroke.layer_id == h.layer_id ) {
1349 push(&l->strokes, stroke);
1350 push(&milton->canvas->history, h);
1351
1352 milton->render_settings.do_full_redraw = true;
1353
1354 break;
1355 }
1356
1357 stroke = pop(&milton->canvas->stroke_graveyard); // Keep popping in case the graveyard has info from deleted layers
1358 }
1359
1360 } break;
1361 /* case HistoryElement_LAYER_DELETE: { */
1362 /* } break; */
1363 }
1364 }
1365 }
1366 }
1367
1368 // If the current mode is Pen or Eraser, we show the hover. It can be unset under various conditions later.
1369 if ( current_mode_is_for_drawing(milton) ) {
1370 brush_outline_should_draw = true;
1371 }
1372
1373
1374 if ( gui_point_hovers(milton->gui, milton->platform->pointer) ) {
1375 brush_outline_should_draw = false;
1376 }
1377
1378 if ( (input->flags & MiltonInputFlags_IMGUI_GRABBED_INPUT) ) {
1379 // Start drawing the preview if we just grabbed a slider.
1380 brush_outline_should_draw = false;
1381
1382 if ( (milton->gui->flags & MiltonGuiFlags_SHOWING_PREVIEW) ) {
1383 auto preview_pos = milton->gui->preview_pos;
1384 mlt_assert(preview_pos.x >= 0);
1385 mlt_assert(preview_pos.y >= 0);
1386 v4f color = {};
1387 color.rgb = milton->view->background_color;
1388 color.a = 1;
1389 if ( milton->current_mode == MiltonMode::PEN ) {
1390 color = to_premultiplied(hsv_to_rgb(milton->gui->picker.data.hsv),
1391 milton_get_brush_alpha(milton));
1392 }
1393 gpu_update_brush_outline(milton->renderer,
1394 preview_pos.x, preview_pos.y,
1395 milton_get_brush_radius(milton), BrushOutline_FILL, color);
1396 }
1397 } else {
1398 gui_imgui_set_ungrabbed(milton->gui);
1399 }
1400
1401 if ( milton->gui->visible ) {
1402 render_flags |= RenderBackendFlags_GUI_VISIBLE;
1403 } else {
1404 render_flags &= ~RenderBackendFlags_GUI_VISIBLE;
1405 }
1406
1407 // Mode tick
1408 if (milton->current_mode == MiltonMode::ERASER) {
1409 milton->working_stroke.flags |= StrokeFlag_ERASER;
1410 }
1411 else {
1412 milton->working_stroke.flags &= ~StrokeFlag_ERASER;
1413 }
1414 if ( current_mode_is_for_drawing(milton) &&
1415 (input->input_count > 0 || end_stroke) ) {
1416 if ( !is_user_drawing(milton)
1417 && gui_consume_input(milton->gui, input) ) {
1418 milton_update_brushes(milton);
1419 gpu_update_picker(milton->renderer, &milton->gui->picker);
1420 }
1421 else if ( !milton->gui->owns_user_input
1422 && (milton->canvas->working_layer->flags & LayerFlags_VISIBLE) ) {
1423 if ( milton->current_mode == MiltonMode::PRIMITIVE ) {
1424 // Input for primitive.
1425 milton_primitive_input(milton, input, end_stroke);
1426 }
1427 else if ( milton->current_mode != MiltonMode::DRAG_BRUSH_SIZE ) { // Input for eraser and pen
1428 Stroke* ws = &milton->working_stroke;
1429 auto prev_num_points = ws->num_points;
1430 milton_stroke_input(milton, input);
1431 if ( prev_num_points == 0 && ws->num_points > 0 ) {
1432 // New stroke. Clear screen without blur.
1433 milton->render_settings.do_full_redraw = true;
1434 }
1435 }
1436 }
1437 }
1438
1439 if ( milton->current_mode == MiltonMode::EXPORTING ) {
1440 Exporter* exporter = &milton->gui->exporter;
1441 b32 changed = exporter_input(exporter, input);
1442
1443 {
1444 i32 x = min(exporter->pivot.x, exporter->needle.x);
1445 i32 y = min(exporter->pivot.y, exporter->needle.y);
1446 i32 w = MLT_ABS(exporter->pivot.x - exporter->needle.x);
1447 i32 h = MLT_ABS(exporter->pivot.y - exporter->needle.y);
1448
1449 float left = 2*((float) x / (milton->view->screen_size.w))-1;
1450 float right = 2*((GLfloat)(x+w) / (milton->view->screen_size.w))-1;
1451 float top = -(2*((GLfloat)y / (milton->view->screen_size.h))-1);
1452 float bottom = -(2*((GLfloat)(y+h) / (milton->view->screen_size.h))-1);
1453
1454 imm_rect(milton->renderer, left, right, top, bottom, 2.0);
1455 }
1456
1457 milton->gui->flags &= ~(MiltonGuiFlags_SHOWING_PREVIEW);
1458 }
1459 else if ( milton->current_mode == MiltonMode::EYEDROPPER ) {
1460 v2i point = milton->platform->pointer;
1461
1462 eyedropper_input(milton->eyedropper, milton->gui,
1463 milton->view->screen_size.w,
1464 milton->view->screen_size.h,
1465 point);
1466 gpu_update_picker(milton->renderer, &milton->gui->picker);
1467 if( input->flags & MiltonInputFlags_CLICKUP ) {
1468 milton_update_brushes(milton);
1469 milton_leave_mode(milton);
1470 }
1471 render_flags |= RenderBackendFlags_GUI_VISIBLE;
1472 }
1473 else if (milton->current_mode == MiltonMode::PEEK_OUT) {
1474 peek_out_tick(milton, input);
1475 }
1476 else if (milton->current_mode == MiltonMode::DRAG_BRUSH_SIZE) {
1477 drag_brush_size_tick(milton, input);
1478 }
1479 else if (milton->current_mode == MiltonMode::TRANSFORM) {
1480 transform_tick(milton, input);
1481 }
1482
1483 // ---- End stroke
1484 if ( end_stroke ) {
1485 if ( milton->gui->owns_user_input ) {
1486 gui_deactivate(milton->gui);
1487 brush_outline_should_draw = false;
1488 } else {
1489 if ( milton->working_stroke.num_points > 0 ) {
1490 // We used the selected color to draw something. Push.
1491 if ( (milton->current_mode == MiltonMode::PEN ||
1492 milton->current_mode == MiltonMode::PRIMITIVE)
1493 && gui_mark_color_used(milton->gui) ) {
1494 // Tell the renderer to update the picker
1495 gpu_update_picker(milton->renderer, &milton->gui->picker);
1496 }
1497 // Copy current stroke.
1498 Stroke new_stroke = {};
1499 CanvasState* canvas = milton->canvas;
1500 copy_stroke(&canvas->arena, milton->view, &milton->working_stroke, &new_stroke);
1501 {
1502 new_stroke.layer_id = milton->view->working_layer_id;
1503 new_stroke.bounding_rect = bounding_box_for_stroke(&new_stroke);
1504
1505 new_stroke.id = milton->canvas->stroke_id_count++;
1506
1507 Rect bounds = new_stroke.bounding_rect;
1508 bounds.top_left = canvas_to_raster(milton->view, bounds.top_left);
1509 bounds.bot_right = canvas_to_raster(milton->view, bounds.bot_right);
1510 }
1511
1512 mlt_assert(new_stroke.num_points > 0);
1513 mlt_assert(new_stroke.num_points <= STROKE_MAX_POINTS);
1514 auto* stroke = layer::layer_push_stroke(milton->canvas->working_layer, new_stroke);
1515
1516 // Invalidate working stroke render element
1517
1518 HistoryElement h = { HistoryElement_STROKE_ADD, milton->canvas->working_layer->id };
1519 push(&milton->canvas->history, h);
1520
1521 reset_working_stroke(milton);
1522
1523 clear_stroke_redo(milton);
1524
1525 // Make sure we show blurred layers when finishing a stroke.
1526 milton->render_settings.do_full_redraw = true;
1527 }
1528 }
1529 }
1530 else if ( is_user_drawing(milton) ) {
1531 Rect previous_bounds = milton->working_stroke.bounding_rect;
1532 Rect new_bounds = bounding_box_for_stroke(&milton->working_stroke);
1533
1534 new_bounds.left = min(new_bounds.left, previous_bounds.left);
1535 new_bounds.top = min(new_bounds.top, previous_bounds.top);
1536 new_bounds.right = max(new_bounds.right, previous_bounds.right);
1537 new_bounds.bottom = max(new_bounds.bottom, previous_bounds.bottom);
1538
1539 milton->working_stroke.bounding_rect = new_bounds;
1540 }
1541
1542 MiltonMode current_mode = milton->current_mode;
1543 if ( input->mode_to_set < MiltonMode::MODE_COUNT ) {
1544 if ( current_mode == input->mode_to_set ) {
1545 // Modes we can toggle
1546 MiltonMode toggleable_modes[] = {
1547 MiltonMode::EYEDROPPER,
1548 MiltonMode::PRIMITIVE,
1549 };
1550
1551 for ( size_t i = 0; i < array_count(toggleable_modes); ++i ) {
1552 MiltonMode toggle = toggleable_modes[i];
1553 if ( current_mode == toggle ) {
1554 if ( milton->n_mode_stack > 0 && milton->mode_stack[milton->n_mode_stack - 1] != toggle ) {
1555 milton_leave_mode(milton);
1556 }
1557 else {
1558 // This is not supposed to happen but if we get here we won't crash and burn.
1559 milton_enter_mode(milton, MiltonMode::PEN);
1560 milton_log("Warning: Unexpected code path: Toggling modes. Toggleable mode was set twice. Switching to pen.\n");
1561 }
1562 }
1563 }
1564 }
1565 // Change the current mode if it's different from the mode to set
1566 else {
1567 if ( mode_is_for_drawing(input->mode_to_set) ) {
1568 milton_update_brushes(milton);
1569
1570 // There is no way of leaving a drawing mode to some previous
1571 // mode. Therefore, When entering a drawing mode we clear the
1572 // mode stack.
1573 while ( milton->n_mode_stack ) {
1574 milton_leave_mode(milton);
1575 }
1576 }
1577 milton_enter_mode(milton, input->mode_to_set);
1578 }
1579 }
1580
1581 // Disable hover if panning.
1582 if ( input->flags & MiltonInputFlags_PANNING ) {
1583 brush_outline_should_draw = false;
1584 }
1585
1586 if ( !(milton->gui->flags & MiltonGuiFlags_SHOWING_PREVIEW) ) {
1587 float radius = -1;
1588 if (brush_outline_should_draw && current_mode_is_for_drawing(milton)) {
1589 radius = (float)milton_get_brush_radius(milton);
1590 }
1591
1592 v2i brush_point = milton->platform->pointer;
1593 if ( milton->current_mode == MiltonMode::DRAG_BRUSH_SIZE ) {
1594 brush_point = milton->drag_brush->start_point;
1595 }
1596
1597 gpu_update_brush_outline(milton->renderer,
1598 brush_point.x, brush_point.y,
1599 radius);
1600 }
1601
1602 PROFILE_GRAPH_END(update);
1603
1604 if ( !(milton->flags & MiltonStateFlags_RUNNING) ) {
1605 // Someone tried to kill milton from outside the update. Make sure we save.
1606 should_save = true;
1607 // Don't want to leave the system with the cursor hidden.
1608 platform_cursor_show();
1609 }
1610
1611 if ( should_save ) {
1612 if ( !(milton->flags & MiltonStateFlags_RUNNING) ) {
1613 // Always save synchronously when exiting.
1614 milton_save(milton);
1615 } else {
1616 #if MILTON_SAVE_ASYNC
1617 trigger_async_save(milton);
1618 #else
1619 milton_save(milton);
1620 #endif
1621 }
1622 // We're about to close and the last save failed and the drawing changed.
1623 if ( !(milton->flags & MiltonStateFlags_RUNNING)
1624 && (milton->flags & MiltonStateFlags_LAST_SAVE_FAILED)
1625 && (milton->flags & MiltonStateFlags_MOVE_FILE_FAILED)
1626 && milton->persist->last_save_stroke_count != layer::count_strokes(milton->canvas->root_layer) ) {
1627 // TODO: Stop using MoveFileEx?
1628 // Why does MoveFileEx fail? Ask someone who knows this stuff.
1629 // Wait a moment and try again. If this fails, prompt to save somewhere else.
1630 SDL_Delay(3000);
1631 milton_save(milton);
1632
1633 if ( (milton->flags & MiltonStateFlags_LAST_SAVE_FAILED)
1634 && (milton->flags & MiltonStateFlags_MOVE_FILE_FAILED) ) {
1635 char msg[1024];
1636 WallTime lst = milton->persist->last_save_time;
1637 snprintf(msg, 1024, "Milton failed to save this canvas. The last successful save was at %.2d:%.2d:%.2d. Try saving to another file?",
1638 lst.hours, lst.minutes, lst.seconds);
1639 b32 another = platform_dialog_yesno(msg, "Try another file?");
1640 if ( another ) {
1641 // NOTE(possible refactor): There is similar code. Guipp.cpp save_milton_canvas
1642 PATH_CHAR* name = platform_save_dialog(FileKind_MILTON_CANVAS);
1643 if ( name ) {
1644 milton_log("Saving to %s\n", name);
1645 milton_set_canvas_file(milton, name);
1646 milton_save(milton);
1647 if ( (milton->flags & MiltonStateFlags_LAST_SAVE_FAILED) ) {
1648 platform_dialog("Still can't save. Please contact us for help. miltonpaint.com", "Info");
1649 } else {
1650 platform_dialog("Success.", "Info");
1651 }
1652 b32 del = platform_delete_file_at_config(TO_PATH_STR("MiltonPersist.mlt"), DeleteErrorTolerance_OK_NOT_EXIST);
1653 if ( del == false ) {
1654 platform_dialog("Could not delete default canvas."
1655 " Contents will be still there when you create a new canvas.", "Info");
1656 }
1657 }
1658 }
1659 }
1660 }
1661
1662
1663 // About to quit.
1664 if ( !(milton->flags & MiltonStateFlags_RUNNING) ) {
1665 milton_kill_save_thread(milton);
1666 // Release resources
1667 milton_reset_canvas(milton);
1668 gpu_release_data(milton->renderer);
1669
1670 debug_memory_dump_allocations();
1671 }
1672 }
1673
1674 #if MILTON_SAVE_ASYNC
1675 SDL_LockMutex(milton->save_mutex);
1676 SDL_CondSignal(milton->save_cond);
1677 SDL_UnlockMutex(milton->save_mutex);
1678 #endif
1679
1680 // Update render resources after loading
1681 if (milton->flags & MiltonStateFlags_JUST_SAVED) {
1682 milton_set_background_color(milton, milton->view->background_color);
1683 gpu_update_picker(milton->renderer, &milton->gui->picker);
1684 milton->flags &= ~MiltonStateFlags_JUST_SAVED;
1685 }
1686
1687 i32 view_x = 0;
1688 i32 view_y = 0;
1689 i32 view_width = 0;
1690 i32 view_height = 0;
1691
1692 gpu_reset_render_flags(milton->renderer, render_flags);
1693
1694 ClipFlags clip_flags = ClipFlags_JUST_CLIP;
1695
1696 #if REDRAW_EVERY_FRAME
1697 milton->render_settings.do_full_redraw = true;
1698 #endif
1699
1700 b32 has_working_stroke = milton->working_stroke.num_points > 0;
1701
1702 if (has_working_stroke) {
1703 b32 has_blur = false;
1704
1705 Layer* layer = milton->canvas->root_layer;
1706 while (layer) {
1707 if (layer->flags & LayerFlags_VISIBLE) {
1708 LayerEffect* e = layer->effects;
1709 while (e) {
1710 if (e->enabled && e->type == LayerEffectType_BLUR) {
1711 has_blur = true;
1712 break;
1713 }
1714 e = e->next;
1715 }
1716 }
1717 if (has_blur) { break; }
1718 layer = layer->next;
1719 }
1720
1721 if (has_blur) {
1722 milton->render_settings.do_full_redraw = true;
1723 }
1724 }
1725
1726 static u64 scale_of_last_full_redraw = 0;
1727 static f32 angle_of_last_full_redraw = 0.0f;
1728
1729 if (scale_of_last_full_redraw != milton_render_scale(milton) ||
1730 angle_of_last_full_redraw != milton->view->angle ) {
1731 // We want to draw everything when we're scaling up and down.
1732 milton->render_settings.do_full_redraw = true;
1733 }
1734
1735 // Note: We flip the rectangles. GL is bottom-left by default.
1736 if ( milton->render_settings.do_full_redraw ) {
1737 view_width = milton->view->screen_size.w;
1738 view_height = milton->view->screen_size.h;
1739 // Only update GPU data if we are redrawing the full screen. This means
1740 // that the size of the screen will be used to determine if each stroke
1741 // should be freed from GPU memory.
1742 clip_flags = ClipFlags_UPDATE_GPU_DATA;
1743 scale_of_last_full_redraw = milton_render_scale(milton);
1744 angle_of_last_full_redraw = milton->view->angle;
1745 }
1746 else if (has_working_stroke) {
1747 Rect bounds = canvas_to_raster_bounding_rect(milton->view, milton->working_stroke.bounding_rect);
1748
1749 view_x = bounds.left;
1750 view_y = bounds.top;
1751
1752 view_width = bounds.right - bounds.left;
1753 view_height = bounds.bottom - bounds.top;
1754 }
1755
1756 PROFILE_GRAPH_BEGIN(clipping);
1757
1758 i64 render_scale = milton_render_scale(milton);
1759
1760 gpu_clip_strokes_and_update(&milton->root_arena, milton->renderer, milton->view, render_scale,
1761 milton->canvas->root_layer, &milton->working_stroke,
1762 view_x, view_y, view_width, view_height, clip_flags);
1763 PROFILE_GRAPH_END(clipping);
1764
1765 gpu_render(milton->renderer, view_x, view_y, view_width, view_height);
1766
1767 ARENA_VALIDATE(&milton->root_arena);
1768 }
1769