1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "map.h"
24#include "ext/history.h"
25
26#define MAP_WIDTH (TIC80_WIDTH)
27#define MAP_HEIGHT (TIC80_HEIGHT - TOOLBAR_SIZE)
28#define MAP_X (0)
29#define MAP_Y (TOOLBAR_SIZE)
30
31#define MAX_SCROLL_X (TIC_MAP_WIDTH * TIC_SPRITESIZE)
32#define MAX_SCROLL_Y (TIC_MAP_HEIGHT * TIC_SPRITESIZE)
33
34#define ICON_SIZE 7
35
36#define MIN_SCALE 1
37#define MAX_SCALE 4
38#define FILL_STACK_SIZE (TIC_MAP_WIDTH*TIC_MAP_HEIGHT)
39
40static void normalizeMap(s32* x, s32* y)
41{
42 while(*x < 0) *x += MAX_SCROLL_X;
43 while(*y < 0) *y += MAX_SCROLL_Y;
44 while(*x >= MAX_SCROLL_X) *x -= MAX_SCROLL_X;
45 while(*y >= MAX_SCROLL_Y) *y -= MAX_SCROLL_Y;
46}
47
48static tic_point getTileOffset(Map* map)
49{
50 return (tic_point){(map->sheet.rect.w - 1)*TIC_SPRITESIZE / 2, (map->sheet.rect.h - 1)*TIC_SPRITESIZE / 2};
51}
52
53static void getMouseMap(Map* map, s32* x, s32* y)
54{
55 tic_mem* tic = map->tic;
56 tic_point offset = getTileOffset(map);
57
58 s32 mx = tic_api_mouse(tic).x + map->scroll.x - offset.x;
59 s32 my = tic_api_mouse(tic).y + map->scroll.y - offset.y;
60
61 normalizeMap(&mx, &my);
62
63 *x = mx / TIC_SPRITESIZE;
64 *y = my / TIC_SPRITESIZE;
65}
66
67static s32 drawWorldButton(Map* map, s32 x, s32 y)
68{
69 enum{Size = 8};
70
71 x -= Size;
72
73 tic_rect rect = {x, y, Size, ICON_SIZE};
74
75 bool over = false;
76
77 if(checkMousePos(map->studio, &rect))
78 {
79 setCursor(map->studio, tic_cursor_hand);
80
81 over = true;
82
83 showTooltip(map->studio, "WORLD MAP [tab]");
84
85 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
86 setStudioMode(map->studio, TIC_WORLD_MODE);
87 }
88
89 drawBitIcon(map->studio, tic_icon_world, x, y, over ? tic_color_grey : tic_color_light_grey);
90
91 return x;
92
93}
94
95static s32 drawGridButton(Map* map, s32 x, s32 y)
96{
97 x -= ICON_SIZE;
98
99 tic_rect rect = {x, y, ICON_SIZE, ICON_SIZE};
100
101 bool over = false;
102
103 if(checkMousePos(map->studio, &rect))
104 {
105 setCursor(map->studio, tic_cursor_hand);
106
107 over = true;
108
109 showTooltip(map->studio, "SHOW/HIDE GRID [`]");
110
111 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
112 map->canvas.grid = !map->canvas.grid;
113 }
114
115 drawBitIcon(map->studio, tic_icon_grid, x, y, map->canvas.grid ? tic_color_black : over ? tic_color_grey : tic_color_light_grey);
116
117 return x;
118}
119
120static inline bool isIdle(Map* map)
121{
122 return map->anim.movie == &map->anim.idle;
123}
124
125static inline bool sheetVisible(Map* map)
126{
127 return map->anim.pos.sheet >= 0;
128}
129
130static s32 drawSheetButton(Map* map, s32 x, s32 y)
131{
132 x -= ICON_SIZE;
133
134 tic_rect rect = {x, y, ICON_SIZE, ICON_SIZE};
135
136 bool over = false;
137 if(checkMousePos(map->studio, &rect))
138 {
139 setCursor(map->studio, tic_cursor_hand);
140
141 over = true;
142 showTooltip(map->studio, "SHOW TILES [shift]");
143
144 if(isIdle(map) && checkMouseClick(map->studio, &rect, tic_mouse_left))
145 {
146 map->anim.movie = resetMovie(sheetVisible(map) ? &map->anim.hide : &map->anim.show);
147 map->sheet.keep = true;
148 }
149 }
150
151 drawBitIcon(map->studio, sheetVisible(map) ? tic_icon_up : tic_icon_down, rect.x, rect.y,
152 over ? tic_color_grey : tic_color_light_grey);
153
154 return x;
155}
156
157static s32 drawToolButton(Map* map, s32 x, s32 y, u8 icon, s32 width, const char* tip, s32 mode)
158{
159 x -= width;
160
161 tic_rect rect = {x, y, width, ICON_SIZE};
162
163 bool over = false;
164 if(checkMousePos(map->studio, &rect))
165 {
166 setCursor(map->studio, tic_cursor_hand);
167
168 over = true;
169
170 showTooltip(map->studio, tip);
171
172 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
173 {
174 map->mode = mode;
175 }
176 }
177
178 drawBitIcon(map->studio, icon, rect.x, rect.y, map->mode == mode ? tic_color_black : over ? tic_color_grey : tic_color_light_grey);
179
180 return x;
181}
182
183static s32 drawFillButton(Map* map, s32 x, s32 y)
184{
185 enum{Size = 8};
186
187 return drawToolButton(map, x, y, tic_icon_fill, Size, "FILL [4]", MAP_FILL_MODE);
188}
189
190static s32 drawSelectButton(Map* map, s32 x, s32 y)
191{
192 return drawToolButton(map, x, y, tic_icon_select, ICON_SIZE, "SELECT [3]", MAP_SELECT_MODE);
193}
194
195static s32 drawHandButton(Map* map, s32 x, s32 y)
196{
197 return drawToolButton(map, x, y, tic_icon_hand, ICON_SIZE, "DRAG MAP [2]", MAP_DRAG_MODE);
198}
199
200static s32 drawPenButton(Map* map, s32 x, s32 y)
201{
202 return drawToolButton(map, x, y, tic_icon_pen, ICON_SIZE, "DRAW [1]", MAP_DRAW_MODE);
203}
204
205static void drawTileIndex(Map* map, s32 x, s32 y)
206{
207 tic_mem* tic = map->tic;
208 s32 index = -1;
209
210 if(sheetVisible(map))
211 {
212 tic_rect rect = {TIC80_WIDTH - TIC_SPRITESHEET_SIZE - 1, TOOLBAR_SIZE, TIC_SPRITESHEET_SIZE, TIC_SPRITESHEET_SIZE};
213
214 if(checkMousePos(map->studio, &rect))
215 {
216 s32 mx = tic_api_mouse(tic).x - rect.x;
217 s32 my = tic_api_mouse(tic).y - rect.y;
218
219 mx /= TIC_SPRITESIZE;
220 my /= TIC_SPRITESIZE;
221
222 index = my * map->sheet.blit.pages * TIC_SPRITESHEET_COLS + mx + tic_blit_calc_index(&map->sheet.blit);
223 }
224 }
225 else
226 {
227 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
228
229 if(checkMousePos(map->studio, &rect))
230 {
231 s32 tx = 0, ty = 0;
232 getMouseMap(map, &tx, &ty);
233 map2ram(tic->ram, map->src);
234 index = tic_api_mget(map->tic, tx, ty);
235 }
236 }
237
238 if(index >= 0)
239 {
240 char buf[sizeof "#9999"];
241 sprintf(buf, "#%03i", index);
242 tic_api_print(map->tic, buf, x, y, tic_color_light_grey, true, 1, false);
243 }
244}
245
246static void drawBppButtons(Map* map, s32 x, s32 y)
247{
248 tic_mem* tic = map->tic;
249
250 static const char Labels[] = "421";
251
252 for(s32 i = 0; i < sizeof Labels - 1; i++)
253 {
254 tic_rect rect = {x + i * TIC_ALTFONT_WIDTH, y, TIC_ALTFONT_WIDTH, TIC_FONT_HEIGHT};
255 tic_bpp mode = 1 << (2 - i);
256
257 bool hover = false;
258 if(checkMousePos(map->studio, &rect))
259 {
260 setCursor(map->studio, tic_cursor_hand);
261 hover = true;
262
263 if(mode > 1)
264 SHOW_TOOLTIP(map->studio, "%iBITS PER PIXEL", mode);
265 else
266 SHOW_TOOLTIP(map->studio, "%iBIT PER PIXEL", mode);
267
268 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
269 {
270 tic_blit_update_bpp(&map->sheet.blit, mode);
271 }
272 }
273
274 const char* label = (char[]){Labels[i], '\0'};
275 tic_api_print(tic, label, rect.x, rect.y,
276 mode == map->sheet.blit.mode
277 ? tic_color_dark_grey
278 : hover
279 ? tic_color_grey
280 : tic_color_light_grey,
281 true, 1, true);
282 }
283}
284
285static void drawBankButtons(Map* map, s32 x, s32 y)
286{
287 tic_mem* tic = map->tic;
288
289 enum{Size = 6};
290
291 static const u8 Icons[] = {tic_icon_tiles, tic_icon_sprites};
292
293 for(s32 i = 0; i < COUNT_OF(Icons); i++)
294 {
295 tic_rect rect = {x + i * Size, y, Size, Size};
296
297 bool hover = false;
298 if(checkMousePos(map->studio, &rect))
299 {
300 setCursor(map->studio, tic_cursor_hand);
301 hover = true;
302
303 showTooltip(map->studio, i ? "SPRITES" : "TILES");
304
305 if(isIdle(map) && checkMouseClick(map->studio, &rect, tic_mouse_left))
306 {
307 Anim* anim = map->anim.bank.items;
308 anim->start = (i - map->sheet.blit.bank) * TIC_SPRITESHEET_SIZE;
309 map->anim.movie = resetMovie(&map->anim.bank);
310
311 map->sheet.blit.bank = i;
312 }
313 }
314
315 drawBitIcon(map->studio, Icons[i], rect.x, rect.y,
316 i == map->sheet.blit.bank
317 ? tic_color_dark_grey
318 : hover
319 ? tic_color_grey
320 : tic_color_light_grey);
321 }
322}
323
324static void drawPagesButtons(Map* map, s32 x, s32 y)
325{
326 tic_mem* tic = map->tic;
327
328 enum{Width = TIC_ALTFONT_WIDTH + 1, Height = TOOLBAR_SIZE};
329
330 for(s32 i = 0; i < map->sheet.blit.pages; i++)
331 {
332 tic_rect rect = {x + i * Width - 1, y, Width, Height};
333
334 bool hover = false;
335 if(checkMousePos(map->studio, &rect))
336 {
337 setCursor(map->studio, tic_cursor_hand);
338 hover = true;
339
340 SHOW_TOOLTIP(map->studio, "PAGE %i", i);
341
342 if(isIdle(map) && checkMouseClick(map->studio, &rect, tic_mouse_left))
343 {
344 Anim* anim = map->anim.page.items;
345 anim->start = (i - map->sheet.blit.page) * TIC_SPRITESHEET_SIZE;
346 map->anim.movie = resetMovie(&map->anim.page);
347
348 map->sheet.blit.page = i;
349 }
350 }
351
352 bool active = i == map->sheet.blit.page;
353 if(active)
354 {
355 tic_api_rect(tic, rect.x, rect.y, Width, Height, tic_color_black);
356 }
357
358 const char* label = (char[]){i + '1', '\0'};
359 tic_api_print(tic, label, rect.x + 1, rect.y + 1,
360 active
361 ? tic_color_white
362 : hover
363 ? tic_color_grey
364 : tic_color_light_grey,
365 true, 1, true);
366 }
367}
368
369static void drawMapToolbar(Map* map, s32 x, s32 y)
370{
371 tic_api_rect(map->tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE, tic_color_white);
372
373 drawTileIndex(map, TIC80_WIDTH/2 - TIC_FONT_WIDTH, y);
374
375 x = drawSheetButton(map, x, 0);
376
377 if(sheetVisible(map))
378 {
379 drawBankButtons(map, 183, 0);
380 drawBppButtons(map, 199, 1);
381
382 if(map->sheet.blit.pages > 1)
383 drawPagesButtons(map, map->sheet.blit.pages == 4 ? 213 : 222, 0);
384 }
385 else
386 {
387 x = drawFillButton(map, x, 0);
388 x = drawSelectButton(map, x, 0);
389 x = drawHandButton(map, x, 0);
390 x = drawPenButton(map, x, 0);
391
392 x = drawGridButton(map, x - 5, 0);
393 drawWorldButton(map, x, 0);
394 }
395}
396
397static void drawSheetVBank1(Map* map, s32 x, s32 y)
398{
399 tic_mem* tic = map->tic;
400 const tic_blit* blit = &map->sheet.blit;
401
402 tic_rect rect = {x, y, TIC_SPRITESHEET_SIZE, TIC_SPRITESHEET_SIZE};
403
404 tic_api_rectb(map->tic, rect.x - 1, rect.y - 1 + map->anim.pos.sheet, rect.w + 2, rect.h + 2, tic_color_white);
405
406 for(s32 i = 1; i < rect.h; i += 4)
407 {
408 if (blit->page > 0)
409 {
410 tic_api_pix(tic, rect.x-1, rect.y + i, tic_color_black, false);
411 tic_api_pix(tic, rect.x-1, rect.y + i + 1, tic_color_black, false);
412 }
413
414 if (blit->page < blit->pages - 1)
415 {
416 tic_api_pix(tic, rect.x+rect.w, rect.y + i, tic_color_black, false);
417 tic_api_pix(tic, rect.x+rect.w, rect.y + i + 1, tic_color_black, false);
418 }
419 }
420
421 {
422 s32 bx = map->sheet.rect.x * TIC_SPRITESIZE - 1 + x;
423 s32 by = map->sheet.rect.y * TIC_SPRITESIZE - 1 + y;
424 s32 bw = map->sheet.rect.w * TIC_SPRITESIZE + 2;
425 s32 bh = map->sheet.rect.h * TIC_SPRITESIZE + 2;
426
427 tic_api_rectb(map->tic, bx, by + map->anim.pos.sheet, bw, bh, tic_color_white);
428 }
429}
430
431static void initBlitMode(Map* map)
432{
433 tic_mem* tic = map->tic;
434 tiles2ram(tic->ram, getBankTiles(map->studio));
435 tic->ram->vram.blit.segment = tic_blit_calc_segment(&map->sheet.blit);
436}
437
438static void resetBlitMode(tic_mem* tic)
439{
440 tic->ram->vram.blit.segment = TIC_DEFAULT_BLIT_MODE;
441}
442
443static void drawSheetReg(Map* map, s32 x, s32 y)
444{
445 tic_mem* tic = map->tic;
446
447 tic_rect rect = {x, y, TIC_SPRITESHEET_SIZE, TIC_SPRITESHEET_SIZE};
448
449 if(isIdle(map) && sheetVisible(map) && checkMousePos(map->studio, &rect))
450 {
451 setCursor(map->studio, tic_cursor_hand);
452
453 if(checkMouseDown(map->studio, &rect, tic_mouse_left))
454 {
455 s32 mx = tic_api_mouse(tic).x - rect.x;
456 s32 my = tic_api_mouse(tic).y - rect.y;
457
458 mx /= TIC_SPRITESIZE;
459 my /= TIC_SPRITESIZE;
460
461 if(map->sheet.drag)
462 {
463 s32 rl = MIN(mx, map->sheet.start.x);
464 s32 rt = MIN(my, map->sheet.start.y);
465 s32 rr = MAX(mx, map->sheet.start.x);
466 s32 rb = MAX(my, map->sheet.start.y);
467
468 map->sheet.rect = (tic_rect){rl, rt, rr-rl+1, rb-rt+1};
469
470 map->mode = MAP_DRAW_MODE;
471 }
472 else
473 {
474 map->sheet.drag = true;
475 map->sheet.start = (tic_point){mx, my};
476 }
477 }
478 else
479 {
480 if(map->sheet.keep && map->sheet.drag)
481 map->anim.movie = resetMovie(&map->anim.hide);
482
483 map->sheet.drag = false;
484 }
485 }
486
487 tic_api_clip(tic, x, y + map->anim.pos.sheet, TIC_SPRITESHEET_SIZE, TIC_SPRITESHEET_SIZE);
488
489 tiles2ram(tic->ram, getBankTiles(map->studio));
490
491 tic_blit blit = map->sheet.blit;
492 SCOPE(resetBlitMode(map->tic), tic_api_clip(tic, 0, 0, TIC80_WIDTH, TIC80_HEIGHT))
493 {
494 tic_point start =
495 {
496 x - blit.page * TIC_SPRITESHEET_SIZE + map->anim.pos.page,
497 y - blit.bank * TIC_SPRITESHEET_SIZE + map->anim.pos.bank
498 }, pos = start;
499
500 for(blit.bank = 0; blit.bank < TIC_SPRITE_BANKS; ++blit.bank, pos.y += TIC_SPRITESHEET_SIZE, pos.x = start.x)
501 {
502 for(blit.page = 0; blit.page < blit.pages; ++blit.page, pos.x += TIC_SPRITESHEET_SIZE)
503 {
504 tic->ram->vram.blit.segment = tic_blit_calc_segment(&blit);
505 tic_api_spr(tic, 0, pos.x, pos.y + map->anim.pos.sheet, TIC_SPRITESHEET_COLS, TIC_SPRITESHEET_COLS, NULL, 0, 1, tic_no_flip, tic_no_rotate);
506 }
507 }
508 }
509}
510
511static void drawCursorPos(Map* map, s32 x, s32 y)
512{
513 char pos[sizeof "999:999"];
514
515 s32 tx = 0, ty = 0;
516 getMouseMap(map, &tx, &ty);
517
518 sprintf(pos, "%03i:%03i", tx, ty);
519
520 s32 width = tic_api_print(map->tic, pos, TIC80_WIDTH, 0, tic_color_dark_green, true, 1, false);
521
522 s32 px = x + (TIC_SPRITESIZE + 3);
523 if(px + width >= TIC80_WIDTH) px = x - (width + 2);
524
525 s32 py = y - (TIC_FONT_HEIGHT + 2);
526 if(py <= TOOLBAR_SIZE) py = y + (TIC_SPRITESIZE + 3);
527
528 tic_api_rect(map->tic, px - 1, py - 1, width + 1, TIC_FONT_HEIGHT + 1, tic_color_white);
529 tic_api_print(map->tic, pos, px, py, tic_color_light_grey, true, 1, false);
530
531 if(map->mode == MAP_FILL_MODE && tic_api_key(map->tic, tic_key_ctrl))
532 {
533 tic_api_rect(map->tic, px - 1, py - 1 + TIC_FONT_HEIGHT, width + 1, TIC_FONT_HEIGHT + 1, tic_color_white);
534 tic_api_print(map->tic, "replace", px, py + TIC_FONT_HEIGHT, tic_color_dark_blue, true, 1, false);
535 }
536}
537
538static inline void ram2map(const tic_ram* ram, tic_map* src)
539{
540 memcpy(src, ram->map.data, sizeof ram->map);
541}
542
543static void setMapSprite(Map* map, s32 x, s32 y)
544{
545 s32 mx = map->sheet.rect.x;
546 s32 my = map->sheet.rect.y;
547
548
549 for(s32 j = 0; j < map->sheet.rect.h; j++)
550 for(s32 i = 0; i < map->sheet.rect.w; i++)
551 tic_api_mset(map->tic, (x+i)%TIC_MAP_WIDTH, (y+j)%TIC_MAP_HEIGHT, (mx+i) + (my+j) * TIC_SPRITESHEET_COLS);
552
553 ram2map(map->tic->ram, map->src);
554
555 history_add(map->history);
556}
557
558static tic_point getCursorPos(Map* map)
559{
560 tic_mem* tic = map->tic;
561 tic_point offset = getTileOffset(map);
562
563 s32 mx = tic_api_mouse(tic).x + map->scroll.x - offset.x;
564 s32 my = tic_api_mouse(tic).y + map->scroll.y - offset.y;
565
566 mx -= mx % TIC_SPRITESIZE;
567 my -= my % TIC_SPRITESIZE;
568
569 mx += -map->scroll.x;
570 my += -map->scroll.y;
571
572 return (tic_point){mx, my};
573}
574
575static void drawTileCursor(Map* map)
576{
577 tic_mem* tic = map->tic;
578
579 if(map->scroll.active)
580 return;
581
582 tic_point pos = getCursorPos(map);
583
584 {
585 s32 sx = map->sheet.rect.x;
586 s32 sy = map->sheet.rect.y;
587
588 initBlitMode(map);
589 tic_api_spr(tic, sx + map->sheet.blit.pages * sy * TIC_SPRITESHEET_COLS, pos.x, pos.y, map->sheet.rect.w, map->sheet.rect.h, NULL, 0, 1, tic_no_flip, tic_no_rotate);
590 resetBlitMode(map->tic);
591 }
592}
593
594static void drawTileCursorVBank1(Map* map)
595{
596 if(map->scroll.active)
597 return;
598
599 tic_point pos = getCursorPos(map);
600
601 {
602 s32 width = map->sheet.rect.w * TIC_SPRITESIZE + 2;
603 s32 height = map->sheet.rect.h * TIC_SPRITESIZE + 2;
604 tic_api_rectb(map->tic, pos.x - 1, pos.y - 1, width, height, tic_color_white);
605 }
606
607 drawCursorPos(map, pos.x, pos.y);
608}
609
610static void processMouseDrawMode(Map* map)
611{
612 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
613
614 setCursor(map->studio, tic_cursor_hand);
615
616 drawTileCursor(map);
617
618 if(checkMouseDown(map->studio, &rect, tic_mouse_left))
619 {
620 s32 tx = 0, ty = 0;
621 getMouseMap(map, &tx, &ty);
622
623 if(map->canvas.draw)
624 {
625 s32 w = tx - map->canvas.start.x;
626 s32 h = ty - map->canvas.start.y;
627
628 if(w % map->sheet.rect.w == 0 && h % map->sheet.rect.h == 0)
629 setMapSprite(map, tx, ty);
630 }
631 else
632 {
633 map->canvas.draw = true;
634 map->canvas.start = (tic_point){tx, ty};
635 }
636 }
637 else
638 {
639 map->canvas.draw = false;
640 }
641
642 if(checkMouseDown(map->studio, &rect, tic_mouse_middle))
643 {
644 s32 tx = 0, ty = 0;
645 getMouseMap(map, &tx, &ty);
646
647 tic_mem* tic = map->tic;
648 map2ram(tic->ram, map->src);
649 s32 index = tic_api_mget(map->tic, tx, ty);
650
651 map->sheet.rect = (tic_rect){index % TIC_SPRITESHEET_COLS, index / TIC_SPRITESHEET_COLS, 1, 1};
652 }
653}
654
655static void processScrolling(Map* map, bool pressed)
656{
657 tic_mem* tic = map->tic;
658 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
659
660 if(map->scroll.active)
661 {
662 if(pressed)
663 {
664 map->scroll.x = map->scroll.start.x - tic_api_mouse(tic).x;
665 map->scroll.y = map->scroll.start.y - tic_api_mouse(tic).y;
666
667 normalizeMap(&map->scroll.x, &map->scroll.y);
668
669 setCursor(map->studio, tic_cursor_hand);
670 }
671 else map->scroll.active = false;
672 }
673 else if(checkMousePos(map->studio, &rect))
674 {
675 if(pressed)
676 {
677 map->scroll.active = true;
678
679 map->scroll.start.x = tic_api_mouse(tic).x + map->scroll.x;
680 map->scroll.start.y = tic_api_mouse(tic).y + map->scroll.y;
681 }
682 }
683}
684
685static void processMouseDragMode(Map* map)
686{
687 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
688
689 processScrolling(map, checkMouseDown(map->studio, &rect, tic_mouse_left) ||
690 checkMouseDown(map->studio, &rect, tic_mouse_right));
691}
692
693static void resetSelection(Map* map)
694{
695 map->select.rect = (tic_rect){0,0,0,0};
696}
697
698static void drawSelectionRect(Map* map, s32 x, s32 y, s32 w, s32 h)
699{
700 enum{Step = 3};
701 u8 color = tic_color_white;
702
703 s32 index = map->tickCounter / 10;
704 for(s32 i = x; i < (x+w); i++) {tic_api_pix(map->tic, i, y, index++ % Step ? color : 0, false);} index++;
705 for(s32 i = y; i < (y+h); i++) {tic_api_pix(map->tic, x + w-1, i, index++ % Step ? color : 0, false);} index++;
706 for(s32 i = (x+w-1); i >= x; i--) {tic_api_pix(map->tic, i, y + h-1, index++ % Step ? color : 0, false);} index++;
707 for(s32 i = (y+h-1); i >= y; i--) {tic_api_pix(map->tic, x, i, index++ % Step ? color : 0, false);}
708}
709
710static void drawPasteData(Map* map)
711{
712 tic_mem* tic = map->tic;
713
714 s32 w = map->paste[0];
715 s32 h = map->paste[1];
716
717 u8* data = map->paste + 2;
718
719 s32 mx = tic_api_mouse(tic).x + map->scroll.x - (w - 1)*TIC_SPRITESIZE / 2;
720 s32 my = tic_api_mouse(tic).y + map->scroll.y - (h - 1)*TIC_SPRITESIZE / 2;
721
722 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
723
724 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
725 {
726 normalizeMap(&mx, &my);
727
728 mx /= TIC_SPRITESIZE;
729 my /= TIC_SPRITESIZE;
730
731 for(s32 j = 0; j < h; j++)
732 for(s32 i = 0; i < w; i++)
733 tic_api_mset(tic, (mx+i)%TIC_MAP_WIDTH, (my+j)%TIC_MAP_HEIGHT, data[i + j * w]);
734
735 ram2map(tic->ram, map->src);
736
737 history_add(map->history);
738
739 free(map->paste);
740 map->paste = NULL;
741 }
742 else
743 {
744 mx -= mx % TIC_SPRITESIZE;
745 my -= my % TIC_SPRITESIZE;
746
747 mx += -map->scroll.x;
748 my += -map->scroll.y;
749
750 initBlitMode(map);
751
752 for(s32 j = 0; j < h; j++)
753 for(s32 i = 0; i < w; i++)
754 {
755 s32 index = data[i + j * w];
756 s32 sx = index % TIC_SPRITESHEET_COLS;
757 s32 sy = index / TIC_SPRITESHEET_COLS;
758 tic_api_spr(tic, sx + map->sheet.blit.pages * sy * TIC_SPRITESHEET_COLS,
759 mx + i * TIC_SPRITESIZE, my + j * TIC_SPRITESIZE, 1, 1, NULL, 0, 1, tic_no_flip, tic_no_rotate);
760 }
761
762 resetBlitMode(map->tic);
763 }
764}
765
766static void drawPasteDataVBank1(Map* map)
767{
768 tic_mem* tic = map->tic;
769 s32 w = map->paste[0];
770 s32 h = map->paste[1];
771
772 s32 mx = tic_api_mouse(tic).x + map->scroll.x - (w - 1) * TIC_SPRITESIZE / 2;
773 s32 my = tic_api_mouse(tic).y + map->scroll.y - (h - 1) * TIC_SPRITESIZE / 2;
774
775 mx -= mx % TIC_SPRITESIZE;
776 my -= my % TIC_SPRITESIZE;
777
778 mx += -map->scroll.x;
779 my += -map->scroll.y;
780
781 drawSelectionRect(map, mx - 1, my - 1, w * TIC_SPRITESIZE + 2, h * TIC_SPRITESIZE + 2);
782}
783
784static void normalizeMapRect(s32* x, s32* y)
785{
786 while(*x < 0) *x += TIC_MAP_WIDTH;
787 while(*y < 0) *y += TIC_MAP_HEIGHT;
788 while(*x >= TIC_MAP_WIDTH) *x -= TIC_MAP_WIDTH;
789 while(*y >= TIC_MAP_HEIGHT) *y -= TIC_MAP_HEIGHT;
790}
791
792static void processMouseSelectMode(Map* map)
793{
794 tic_mem* tic = map->tic;
795 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
796
797 if(checkMousePos(map->studio, &rect))
798 {
799 if(map->paste)
800 drawPasteData(map);
801 else
802 {
803 if(checkMouseDown(map->studio, &rect, tic_mouse_left))
804 {
805 s32 mx = tic_api_mouse(tic).x + map->scroll.x;
806 s32 my = tic_api_mouse(tic).y + map->scroll.y;
807
808 mx /= TIC_SPRITESIZE;
809 my /= TIC_SPRITESIZE;
810
811 if(map->select.drag)
812 {
813 s32 rl = MIN(mx, map->select.start.x);
814 s32 rt = MIN(my, map->select.start.y);
815 s32 rr = MAX(mx, map->select.start.x);
816 s32 rb = MAX(my, map->select.start.y);
817
818 map->select.rect = (tic_rect){rl, rt, rr - rl + 1, rb - rt + 1};
819 }
820 else
821 {
822 map->select.drag = true;
823 map->select.start = (tic_point){mx, my};
824 map->select.rect = (tic_rect){map->select.start.x, map->select.start.y, 1, 1};
825 }
826 }
827 else if(map->select.drag)
828 {
829 map->select.drag = false;
830
831 if(map->select.rect.w <= 1 && map->select.rect.h <= 1)
832 resetSelection(map);
833 }
834 }
835 }
836}
837
838typedef struct
839{
840 tic_point* data;
841 tic_point* head;
842} FillStack;
843
844static bool push(FillStack* stack, s32 x, s32 y)
845{
846 if(stack->head == NULL)
847 {
848 stack->head = stack->data;
849 stack->head->x = x;
850 stack->head->y = y;
851
852 return true;
853 }
854
855 if(stack->head < (stack->data + FILL_STACK_SIZE-1))
856 {
857 stack->head++;
858 stack->head->x = x;
859 stack->head->y = y;
860
861 return true;
862 }
863
864 return false;
865}
866
867static bool pop(FillStack* stack, s32* x, s32* y)
868{
869 if(stack->head > stack->data)
870 {
871 *x = stack->head->x;
872 *y = stack->head->y;
873
874 stack->head--;
875
876 return true;
877 }
878
879 if(stack->head == stack->data)
880 {
881 *x = stack->head->x;
882 *y = stack->head->y;
883
884 stack->head = NULL;
885
886 return true;
887 }
888
889 return false;
890}
891
892static void fillMap(Map* map, s32 x, s32 y, u8 tile)
893{
894 if(tile == (map->sheet.rect.x + map->sheet.rect.y * TIC_SPRITESHEET_COLS)) return;
895
896 static FillStack stack = {NULL, NULL};
897
898 if(!stack.data)
899 stack.data = (tic_point*)malloc(FILL_STACK_SIZE * sizeof(tic_point));
900
901 stack.head = NULL;
902
903 static const s32 dx[4] = {0, 1, 0, -1};
904 static const s32 dy[4] = {-1, 0, 1, 0};
905
906 if(!push(&stack, x, y)) return;
907
908 s32 mx = map->sheet.rect.x;
909 s32 my = map->sheet.rect.y;
910
911 struct
912 {
913 s32 l;
914 s32 t;
915 s32 r;
916 s32 b;
917 }clip = { 0, 0, TIC_MAP_WIDTH, TIC_MAP_HEIGHT };
918
919 if (map->select.rect.w > 0 && map->select.rect.h > 0)
920 {
921 clip.l = map->select.rect.x;
922 clip.t = map->select.rect.y;
923 clip.r = map->select.rect.x + map->select.rect.w;
924 clip.b = map->select.rect.y + map->select.rect.h;
925 }
926
927
928 while(pop(&stack, &x, &y))
929 {
930 for(s32 j = 0; j < map->sheet.rect.h; j++)
931 for(s32 i = 0; i < map->sheet.rect.w; i++)
932 tic_api_mset(map->tic, x+i, y+j, (mx+i) + (my+j) * TIC_SPRITESHEET_COLS);
933
934 for(s32 i = 0; i < COUNT_OF(dx); i++)
935 {
936 s32 nx = x + dx[i]*map->sheet.rect.w;
937 s32 ny = y + dy[i]*map->sheet.rect.h;
938
939 if(nx >= clip.l && nx < clip.r && ny >= clip.t && ny < clip.b)
940 {
941 bool match = true;
942 for(s32 j = 0; j < map->sheet.rect.h; j++)
943 for(s32 i = 0; i < map->sheet.rect.w; i++)
944 if(tic_api_mget(map->tic, nx+i, ny+j) != tile)
945 match = false;
946
947 if(match)
948 {
949 if(!push(&stack, nx, ny)) return;
950 }
951 }
952 }
953 }
954}
955
956static s32 moduloWrap(s32 x, s32 m)
957{
958 s32 y = x % m;
959 return (y < 0) ? (y + m) : y; // always between 0 and m-1 inclusive
960}
961
962// replace tile with another tile or pattern
963static void replaceTile(Map* map, s32 x, s32 y, u8 tile)
964{
965 if(tile == (map->sheet.rect.x + map->sheet.rect.y * TIC_SPRITESHEET_COLS)) return;
966
967 s32 mx = map->sheet.rect.x;
968 s32 my = map->sheet.rect.y;
969
970 struct
971 {
972 s32 l;
973 s32 t;
974 s32 r;
975 s32 b;
976 } clip = { 0, 0, TIC_MAP_WIDTH, TIC_MAP_HEIGHT };
977
978 if (map->select.rect.w > 0 && map->select.rect.h > 0)
979 {
980 clip.l = map->select.rect.x;
981 clip.t = map->select.rect.y;
982 clip.r = map->select.rect.x + map->select.rect.w;
983 clip.b = map->select.rect.y + map->select.rect.h;
984 }
985
986 // for each tile in selection/full map
987 for(s32 j = clip.t; j < clip.b; j++)
988 for(s32 i = clip.l; i < clip.r; i++)
989 if(tic_api_mget(map->tic, i, j) == tile)
990 {
991 // offset pattern based on click position
992 s32 oy = moduloWrap(j - y, map->sheet.rect.h);
993 s32 ox = moduloWrap(i - x, map->sheet.rect.w);
994
995 u8 newtile = (mx + ox) + (my + oy) * TIC_SPRITESHEET_COLS;
996 tic_api_mset(map->tic, i, j, newtile);
997 }
998}
999
1000static void processMouseFillMode(Map* map)
1001{
1002 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
1003
1004 setCursor(map->studio, tic_cursor_hand);
1005
1006 drawTileCursor(map);
1007
1008 if(checkMouseClick(map->studio, &rect, tic_mouse_left))
1009 {
1010 s32 tx = 0, ty = 0;
1011 getMouseMap(map, &tx, &ty);
1012
1013 {
1014 tic_mem* tic = map->tic;
1015 map2ram(tic->ram, map->src);
1016 if(tic_api_key(tic, tic_key_ctrl))
1017 replaceTile(map, tx, ty, tic_api_mget(map->tic, tx, ty));
1018 else
1019 fillMap(map, tx, ty, tic_api_mget(map->tic, tx, ty));
1020 ram2map(tic->ram, map->src);
1021 }
1022
1023 history_add(map->history);
1024 }
1025}
1026
1027static void drawSelectionVBank1(Map* map)
1028{
1029 tic_rect* sel = &map->select.rect;
1030
1031 if(sel->w > 0 && sel->h > 0)
1032 {
1033 s32 x = sel->x * TIC_SPRITESIZE - map->scroll.x;
1034 s32 y = sel->y * TIC_SPRITESIZE - map->scroll.y;
1035 s32 w = sel->w * TIC_SPRITESIZE;
1036 s32 h = sel->h * TIC_SPRITESIZE;
1037
1038 while(x+w<0)x+=MAX_SCROLL_X;
1039 while(y+h<0)y+=MAX_SCROLL_Y;
1040 while(x+w>=MAX_SCROLL_X)x-=MAX_SCROLL_X;
1041 while(y+h>=MAX_SCROLL_Y)y-=MAX_SCROLL_Y;
1042
1043 drawSelectionRect(map, x-1, y-1, w+2, h+2);
1044 }
1045}
1046
1047static void drawGrid(Map* map)
1048{
1049 tic_mem* tic = map->tic;
1050
1051 s32 scrollX = map->scroll.x % TIC_SPRITESIZE;
1052 s32 scrollY = map->scroll.y % TIC_SPRITESIZE;
1053
1054 for(s32 j = -scrollY; j <= TIC80_HEIGHT-scrollY; j += TIC_SPRITESIZE)
1055 {
1056 if(j >= 0 && j < TIC80_HEIGHT)
1057 for(s32 i = 0; i < TIC80_WIDTH; i++)
1058 {
1059 u8 color = tic_api_pix(tic, i, j, 0, true);
1060 tic_api_pix(tic, i, j, (color+1)%TIC_PALETTE_SIZE, false);
1061 }
1062 }
1063
1064 for(s32 j = -scrollX; j <= TIC80_WIDTH-scrollX; j += TIC_SPRITESIZE)
1065 {
1066 if(j >= 0 && j < TIC80_WIDTH)
1067 for(s32 i = 0; i < TIC80_HEIGHT; i++)
1068 {
1069 if((i+scrollY) % TIC_SPRITESIZE)
1070 {
1071 u8 color = tic_api_pix(tic, j, i, 0, true);
1072 tic_api_pix(tic, j, i, (color+1)%TIC_PALETTE_SIZE, false);
1073 }
1074 }
1075 }
1076}
1077
1078static void drawMapReg(Map* map)
1079{
1080 tic_mem* tic = map->tic;
1081 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
1082
1083 bool handle = !sheetVisible(map) && checkMousePos(map->studio, &rect);
1084 bool space = tic_api_key(tic, tic_key_space);
1085
1086 if(handle)
1087 processScrolling(map,
1088 ((space || map->mode == MAP_DRAG_MODE) && checkMouseDown(map->studio, &rect, tic_mouse_left)) ||
1089 checkMouseDown(map->studio, &rect, tic_mouse_right));
1090
1091 {
1092 s32 scrollX = map->scroll.x % TIC_SPRITESIZE;
1093 s32 scrollY = map->scroll.y % TIC_SPRITESIZE;
1094
1095 map2ram(tic->ram, map->src);
1096
1097 initBlitMode(map);
1098 tic_api_map(tic, map->scroll.x / TIC_SPRITESIZE, map->scroll.y / TIC_SPRITESIZE,
1099 TIC_MAP_SCREEN_WIDTH + 1, TIC_MAP_SCREEN_HEIGHT + 1, -scrollX, -scrollY, 0, 0, 1, NULL, NULL);
1100 resetBlitMode(map->tic);
1101
1102 if (map->canvas.grid)
1103 drawGrid(map);
1104 }
1105
1106 if(handle && !space)
1107 {
1108 static void(*const Handlers[])(Map*) = {processMouseDrawMode, processMouseDragMode, processMouseSelectMode, processMouseFillMode};
1109 Handlers[map->mode](map);
1110 }
1111}
1112
1113static void undo(Map* map)
1114{
1115 history_undo(map->history);
1116}
1117
1118static void redo(Map* map)
1119{
1120 history_redo(map->history);
1121}
1122
1123static void copySelectionToClipboard(Map* map)
1124{
1125 tic_rect* sel = &map->select.rect;
1126
1127 if(sel->w > 0 && sel->h > 0)
1128 {
1129 s32 size = sel->w * sel->h + 2;
1130 u8* buffer = malloc(size);
1131
1132 if(buffer)
1133 {
1134 buffer[0] = sel->w;
1135 buffer[1] = sel->h;
1136
1137 u8* ptr = buffer + 2;
1138
1139 for(s32 j = sel->y; j < sel->y+sel->h; j++)
1140 for(s32 i = sel->x; i < sel->x+sel->w; i++)
1141 {
1142 s32 x = i, y = j;
1143 normalizeMapRect(&x, &y);
1144
1145 s32 index = x + y * TIC_MAP_WIDTH;
1146 *ptr++ = map->src->data[index];
1147 }
1148
1149 toClipboard(buffer, size, true);
1150 free(buffer);
1151 }
1152 }
1153}
1154
1155static void copyToClipboard(Map* map)
1156{
1157 copySelectionToClipboard(map);
1158 resetSelection(map);
1159}
1160
1161static void deleteSelection(Map* map)
1162{
1163 tic_rect* sel = &map->select.rect;
1164
1165 if(sel->w > 0 && sel->h > 0)
1166 {
1167 for(s32 j = sel->y; j < sel->y+sel->h; j++)
1168 for(s32 i = sel->x; i < sel->x+sel->w; i++)
1169 {
1170 s32 x = i, y = j;
1171 normalizeMapRect(&x, &y);
1172
1173 s32 index = x + y * TIC_MAP_WIDTH;
1174 map->src->data[index] = 0;
1175 }
1176
1177 history_add(map->history);
1178 }
1179}
1180
1181static void cutToClipboard(Map* map)
1182{
1183 copySelectionToClipboard(map);
1184 deleteSelection(map);
1185 resetSelection(map);
1186}
1187
1188static void copyFromClipboard(Map* map)
1189{
1190 if(tic_sys_clipboard_has())
1191 {
1192 char* clipboard = tic_sys_clipboard_get();
1193
1194 if(clipboard)
1195 {
1196 s32 size = (s32)strlen(clipboard)/2;
1197
1198 if(size > 2)
1199 {
1200 u8* data = malloc(size);
1201
1202 tic_tool_str2buf(clipboard, (s32)strlen(clipboard), data, true);
1203
1204 if(data[0] * data[1] == size - 2)
1205 {
1206 map->paste = data;
1207 map->mode = MAP_SELECT_MODE;
1208 }
1209 else free(data);
1210 }
1211
1212 tic_sys_clipboard_free(clipboard);
1213 }
1214 }
1215}
1216
1217static void processKeyboard(Map* map)
1218{
1219 tic_mem* tic = map->tic;
1220
1221 if(isIdle(map))
1222 {
1223 if(tic_api_key(tic, tic_key_shift))
1224 {
1225 if(!sheetVisible(map))
1226 {
1227 map->anim.movie = resetMovie(&map->anim.show);
1228 map->sheet.keep = false;
1229 }
1230 }
1231 else
1232 {
1233 if(!map->sheet.keep && sheetVisible(map))
1234 map->anim.movie = resetMovie(&map->anim.hide);
1235 }
1236 }
1237
1238 if(tic->ram->input.keyboard.data == 0) return;
1239
1240 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1241
1242 switch(getClipboardEvent(map->studio))
1243 {
1244 case TIC_CLIPBOARD_CUT: cutToClipboard(map); break;
1245 case TIC_CLIPBOARD_COPY: copyToClipboard(map); break;
1246 case TIC_CLIPBOARD_PASTE: copyFromClipboard(map); break;
1247 default: break;
1248 }
1249
1250 if(tic_api_key(tic, tic_key_alt))
1251 return;
1252
1253 if(ctrl)
1254 {
1255 if(keyWasPressed(map->studio, tic_key_z)) undo(map);
1256 else if(keyWasPressed(map->studio, tic_key_y)) redo(map);
1257 }
1258 else
1259 {
1260 if(keyWasPressed(map->studio, tic_key_tab)) setStudioMode(map->studio, TIC_WORLD_MODE);
1261 else if(keyWasPressed(map->studio, tic_key_1)) map->mode = MAP_DRAW_MODE;
1262 else if(keyWasPressed(map->studio, tic_key_2)) map->mode = MAP_DRAG_MODE;
1263 else if(keyWasPressed(map->studio, tic_key_3)) map->mode = MAP_SELECT_MODE;
1264 else if(keyWasPressed(map->studio, tic_key_4)) map->mode = MAP_FILL_MODE;
1265 else if(keyWasPressed(map->studio, tic_key_delete)) deleteSelection(map);
1266 else if(keyWasPressed(map->studio, tic_key_grave)) map->canvas.grid = !map->canvas.grid;
1267 }
1268
1269 enum{Step = 1};
1270
1271 if(tic_api_key(tic, tic_key_up)) map->scroll.y -= Step;
1272 if(tic_api_key(tic, tic_key_down)) map->scroll.y += Step;
1273 if(tic_api_key(tic, tic_key_left)) map->scroll.x -= Step;
1274 if(tic_api_key(tic, tic_key_right)) map->scroll.x += Step;
1275
1276 static const tic_key Keycodes[] = {tic_key_up, tic_key_down, tic_key_left, tic_key_right};
1277
1278 for(s32 i = 0; i < COUNT_OF(Keycodes); i++)
1279 if(tic_api_key(tic, Keycodes[i]))
1280 {
1281 normalizeMap(&map->scroll.x, &map->scroll.y);
1282 break;
1283 }
1284}
1285
1286static void tick(Map* map)
1287{
1288 tic_mem* tic = map->tic;
1289 map->tickCounter++;
1290
1291 processAnim(map->anim.movie, map);
1292
1293 // process scroll
1294 if(tic->ram->input.mouse.scrolly < 0)
1295 {
1296 setStudioMode(map->studio, TIC_WORLD_MODE);
1297 return;
1298 }
1299
1300 processKeyboard(map);
1301
1302 drawMapReg(map);
1303 drawSheetReg(map, TIC80_WIDTH - TIC_SPRITESHEET_SIZE - 1, TOOLBAR_SIZE);
1304
1305 VBANK(tic, 1)
1306 {
1307 tic_api_cls(tic, tic->ram->vram.vars.clear = tic_color_dark_blue);
1308
1309 memcpy(tic->ram->vram.palette.data, getConfig(map->studio)->cart->bank0.palette.vbank0.data, sizeof(tic_palette));
1310
1311 tic_api_clip(tic, 0, TOOLBAR_SIZE, TIC80_WIDTH - (sheetVisible(map) ? TIC_SPRITESHEET_SIZE+2 : 0), TIC80_HEIGHT - TOOLBAR_SIZE);
1312 {
1313 s32 screenScrollX = map->scroll.x % TIC80_WIDTH;
1314 s32 screenScrollY = map->scroll.y % TIC80_HEIGHT;
1315
1316 tic_api_line(tic, 0, TIC80_HEIGHT - screenScrollY, TIC80_WIDTH, TIC80_HEIGHT - screenScrollY, tic_color_grey);
1317 tic_api_line(tic, TIC80_WIDTH - screenScrollX, 0, TIC80_WIDTH - screenScrollX, TIC80_HEIGHT, tic_color_grey);
1318 }
1319 tic_api_clip(tic, 0, 0, TIC80_WIDTH, TIC80_HEIGHT);
1320
1321 drawSheetVBank1(map, TIC80_WIDTH - TIC_SPRITESHEET_SIZE - 1, TOOLBAR_SIZE);
1322
1323 {
1324 tic_rect rect = {MAP_X, MAP_Y, MAP_WIDTH, MAP_HEIGHT};
1325 if(!sheetVisible(map) && checkMousePos(map->studio, &rect) && !tic_api_key(tic, tic_key_space))
1326 {
1327 switch(map->mode)
1328 {
1329 case MAP_DRAW_MODE:
1330 case MAP_FILL_MODE:
1331 drawTileCursorVBank1(map);
1332 break;
1333 case MAP_SELECT_MODE:
1334 if(map->paste)
1335 drawPasteDataVBank1(map);
1336 break;
1337 default:
1338 break;
1339 }
1340 }
1341 }
1342
1343 if(!sheetVisible(map))
1344 drawSelectionVBank1(map);
1345
1346 drawMapToolbar(map, TIC80_WIDTH, 1);
1347 drawToolbar(map->studio, map->tic, false);
1348 }
1349}
1350
1351static void onStudioEvent(Map* map, StudioEvent event)
1352{
1353 switch(event)
1354 {
1355 case TIC_TOOLBAR_CUT: cutToClipboard(map); break;
1356 case TIC_TOOLBAR_COPY: copyToClipboard(map); break;
1357 case TIC_TOOLBAR_PASTE: copyFromClipboard(map); break;
1358 case TIC_TOOLBAR_UNDO: undo(map); break;
1359 case TIC_TOOLBAR_REDO: redo(map); break;
1360 default: break;
1361 }
1362}
1363
1364static void scanline(tic_mem* tic, s32 row, void* data)
1365{
1366 Map* map = data;
1367 if(row == 0)
1368 memcpy(&tic->ram->vram.palette, getBankPalette(map->studio, false), sizeof(tic_palette));
1369}
1370
1371static void emptyDone(void* data) {}
1372
1373static void setIdle(void* data)
1374{
1375 Map* map = data;
1376 map->anim.movie = resetMovie(&map->anim.idle);
1377}
1378
1379static void freeAnim(Map* map)
1380{
1381 FREE(map->anim.show.items);
1382 FREE(map->anim.hide.items);
1383 FREE(map->anim.bank.items);
1384 FREE(map->anim.page.items);
1385}
1386
1387void initMap(Map* map, Studio* studio, tic_map* src)
1388{
1389 enum {SheetStart = -(TIC_SPRITESHEET_SIZE + TOOLBAR_SIZE)};
1390
1391 if(map->history) history_delete(map->history);
1392 freeAnim(map);
1393
1394 *map = (Map)
1395 {
1396 .studio = studio,
1397 .tic = getMemory(studio),
1398 .tick = tick,
1399 .src = src,
1400 .mode = MAP_DRAW_MODE,
1401 .canvas =
1402 {
1403 .grid = true,
1404 .draw = false,
1405 .start = {0, 0},
1406 },
1407 .sheet =
1408 {
1409 .rect = {0, 0, 1, 1},
1410 .start = {0, 0},
1411 .drag = false,
1412 .blit = {0},
1413 },
1414 .select =
1415 {
1416 .rect = {0, 0, 0, 0},
1417 .start = {0, 0},
1418 .drag = false,
1419 },
1420 .paste = NULL,
1421 .tickCounter = 0,
1422 .scroll =
1423 {
1424 .x = 0,
1425 .y = 0,
1426 .active = false,
1427 .gesture = false,
1428 .start = {0, 0},
1429 },
1430 .history = history_create(src, sizeof(tic_map)),
1431 .anim =
1432 {
1433 .pos.sheet = SheetStart,
1434
1435 .idle = {.done = emptyDone,},
1436
1437 .show = MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
1438 {
1439 {SheetStart, 0, STUDIO_ANIM_TIME, &map->anim.pos.sheet, AnimEaseIn},
1440 }),
1441
1442 .hide = MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
1443 {
1444 {0, SheetStart, STUDIO_ANIM_TIME, &map->anim.pos.sheet, AnimEaseIn},
1445 }),
1446
1447 .bank = MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
1448 {
1449 {0, 0, STUDIO_ANIM_TIME, &map->anim.pos.bank, AnimEaseIn},
1450 }),
1451
1452 .page = MOVIE_DEF(STUDIO_ANIM_TIME, setIdle,
1453 {
1454 {0, 0, STUDIO_ANIM_TIME, &map->anim.pos.page, AnimEaseIn},
1455 }),
1456 },
1457 .event = onStudioEvent,
1458 .scanline = scanline,
1459 };
1460
1461 map->anim.movie = resetMovie(&map->anim.idle);
1462
1463 normalizeMap(&map->scroll.x, &map->scroll.y);
1464 tic_blit_update_bpp(&map->sheet.blit, TIC_DEFAULT_BIT_DEPTH);
1465}
1466
1467void freeMap(Map* map)
1468{
1469 freeAnim(map);
1470 history_delete(map->history);
1471 free(map);
1472}
1473