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 | |
40 | static 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 | |
48 | static 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 | |
53 | static 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 | |
67 | static 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 | |
95 | static 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 | |
120 | static inline bool isIdle(Map* map) |
121 | { |
122 | return map->anim.movie == &map->anim.idle; |
123 | } |
124 | |
125 | static inline bool sheetVisible(Map* map) |
126 | { |
127 | return map->anim.pos.sheet >= 0; |
128 | } |
129 | |
130 | static 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 | |
157 | static 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 | |
183 | static 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 | |
190 | static 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 | |
195 | static 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 | |
200 | static 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 | |
205 | static 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 | |
246 | static 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 | |
285 | static 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 | |
324 | static 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 | |
369 | static 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 | |
397 | static 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 | |
431 | static 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 | |
438 | static void resetBlitMode(tic_mem* tic) |
439 | { |
440 | tic->ram->vram.blit.segment = TIC_DEFAULT_BLIT_MODE; |
441 | } |
442 | |
443 | static 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 | |
511 | static 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 | |
538 | static inline void ram2map(const tic_ram* ram, tic_map* src) |
539 | { |
540 | memcpy(src, ram->map.data, sizeof ram->map); |
541 | } |
542 | |
543 | static 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 | |
558 | static 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 | |
575 | static 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 | |
594 | static 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 | |
610 | static 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 | |
655 | static 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 | |
685 | static 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 | |
693 | static void resetSelection(Map* map) |
694 | { |
695 | map->select.rect = (tic_rect){0,0,0,0}; |
696 | } |
697 | |
698 | static 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 | |
710 | static 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 | |
766 | static 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 | |
784 | static 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 | |
792 | static 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 | |
838 | typedef struct |
839 | { |
840 | tic_point* data; |
841 | tic_point* head; |
842 | } FillStack; |
843 | |
844 | static 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 | |
867 | static 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 | |
892 | static 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 | |
956 | static 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 |
963 | static 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 | |
1000 | static 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 | |
1027 | static 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 | |
1047 | static 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 | |
1078 | static 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 | |
1113 | static void undo(Map* map) |
1114 | { |
1115 | history_undo(map->history); |
1116 | } |
1117 | |
1118 | static void redo(Map* map) |
1119 | { |
1120 | history_redo(map->history); |
1121 | } |
1122 | |
1123 | static 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 | |
1155 | static void copyToClipboard(Map* map) |
1156 | { |
1157 | copySelectionToClipboard(map); |
1158 | resetSelection(map); |
1159 | } |
1160 | |
1161 | static 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 | |
1181 | static void cutToClipboard(Map* map) |
1182 | { |
1183 | copySelectionToClipboard(map); |
1184 | deleteSelection(map); |
1185 | resetSelection(map); |
1186 | } |
1187 | |
1188 | static 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 | |
1217 | static 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 | |
1286 | static 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 | |
1351 | static 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 | |
1364 | static 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 | |
1371 | static void emptyDone(void* data) {} |
1372 | |
1373 | static void setIdle(void* data) |
1374 | { |
1375 | Map* map = data; |
1376 | map->anim.movie = resetMovie(&map->anim.idle); |
1377 | } |
1378 | |
1379 | static 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 | |
1387 | void 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 | |
1467 | void freeMap(Map* map) |
1468 | { |
1469 | freeAnim(map); |
1470 | history_delete(map->history); |
1471 | free(map); |
1472 | } |
1473 | |