1 // stb_tilemap_editor.h - v0.41 - Sean Barrett - http://nothings.org/stb
2 // placed in the public domain - not copyrighted - first released 2014-09
3 //
4 // Embeddable tilemap editor for C/C++
5 //
6 //
7 // TABLE OF CONTENTS
8 // FAQ
9 // How to compile/use the library
10 // Additional configuration macros
11 // API documentation
12 // Info on editing multiple levels
13 // Revision history
14 // Todo
15 // Credits
16 // License
17 //
18 //
19 // FAQ
20 //
21 // Q: What counts as a tilemap for this library?
22 //
23 // A: An array of rectangles, where each rectangle contains a small
24 // stack of images.
25 //
26 // Q: What are the limitations?
27 //
28 // A: Maps are limited to 4096x4096 in dimension.
29 // Each map square can only contain a stack of at most 32 images.
30 // A map can only use up to 32768 distinct image tiles.
31 //
32 // Q: How do I compile this?
33 //
34 // A: You need to #define several symbols before #including it, but only
35 // in one file. This will cause all the function definitions to be
36 // generated in that file. See the "HOW TO COMPILE" section.
37 //
38 // Q: What advantages does this have over a standalone editor?
39 //
40 // A: For one, you can integrate the editor into your game so you can
41 // flip between editing and testing without even switching windows.
42 // For another, you don't need an XML parser to get at the map data.
43 //
44 // Q: Can I live-edit my game maps?
45 //
46 // A: Not really, the editor keeps its own map representation.
47 //
48 // Q: How do I save and load maps?
49 //
50 // A: You have to do this yourself. The editor provides serialization
51 // functions (get & set) for reading and writing the map it holds.
52 // You can choose whatever format you want to store the map to on
53 // disk; you just need to provide functions to convert. (For example,
54 // I actually store the editor's map representation to disk basically
55 // as-is; then I have a single function that converts from the editor
56 // map representation to the game representation, which is used both
57 // to go from editor-to-game and from loaded-map-to-game.)
58 //
59 // Q: I want to have tiles change appearance based on what's
60 // adjacent, or other tile-display/substitution trickiness.
61 //
62 // A: You can do this when you convert from the editor's map
63 // representation to the game representation, but there's
64 // no way to show this live in the editor.
65 //
66 // Q: The editor appears to be put map location (0,0) at the top left?
67 // I want to use a different coordinate system in my game (e.g. y
68 // increasing upwards, or origin at the center).
69 //
70 // A: You can do this when you convert from the editor's map
71 // representation to the game representation. (Don't forget to
72 // translate link coordinates as well!)
73 //
74 // Q: The editor appears to put pixel (0,0) at the top left? I want
75 // to use a different coordinate system in my game.
76 //
77 // A: The editor defines an "editor pixel coordinate system" with
78 // (0,0) at the top left and requires you to display things in
79 // that coordinate system. You can freely remap those coordinates
80 // to anything you want on screen.
81 //
82 // Q: How do I scale the user interface?
83 //
84 // A: Since you do all the rendering, you can scale up all the rendering
85 // calls that the library makes to you. If you do, (a) you need
86 // to also scale up the mouse coordinates, and (b) you may want
87 // to scale the map display back down so that you're only scaling
88 // the UI and not everything. See the next question.
89 //
90 // Q: How do I scale the map display?
91 //
92 // A: Use stbte_set_spacing() to change the size that the map is displayed
93 // at. Note that the "callbacks" to draw tiles are used for both drawing
94 // the map and drawing the tile palette, so that callback may need to
95 // draw at two different scales. You should choose the scales to match
96 // You can tell them apart because the
97 // tile palette gets NULL for the property pointer.
98 //
99 // Q: How does object editing work?
100 //
101 // A: One way to think of this is that in the editor, you're placing
102 // spawners, not objects. Each spawner must be tile-aligned, because
103 // it's only a tile editor. Each tile (stack of layers) gets
104 // an associated set of properties, and it's up to you to
105 // determine what properties should appear for a given tile,
106 // based on e.g. the spawners that are in it.
107 //
108 // Q: How are properties themselves handled?
109 //
110 // A: All properties, regardless of UI behavior, are internally floats.
111 // Each tile has an array of floats associated with it, which is
112 // passed back to you when drawing the tiles so you can draw
113 // objects appropriately modified by the properties.
114 //
115 // Q: What if I want to have two different objects/spawners in
116 // one tile, both of which have their own properties?
117 //
118 // A: Make sure STBTE_MAX_PROPERTIES is large enough for the sum of
119 // properties in both objects, and then you have to explicitly
120 // map the property slot #s to the appropriate objects. They'll
121 // still all appear in a single property panel; there's no way
122 // to get multiple panels.
123 //
124 // Q: Can I do one-to-many linking?
125 //
126 // A: The library only supports one link per tile. However, you
127 // can have multiple tiles all link to a single tile. So, you
128 // can fake one-to-many linking by linking in the reverse
129 // direction.
130 //
131 // Q: What if I have two objects in the same tile, and they each
132 // need an independent link? Or I have two kinds of link associated
133 // with a single object?
134 //
135 // A: There is no way to do this. (Unless you can reverse one link.)
136 //
137 // Q: How does cut & paste interact with object properties & links?
138 //
139 // A: Currently the library has no idea which properties or links
140 // are associated with which layers of a tile. So currently, the
141 // library will only copy properties & links if the layer panel
142 // is set to allow all layers to be copied, OR if you set the
143 // "props" in the layer panel to "always". Similarly, you can
144 // set "props" to "none" so it will never copy.
145 //
146 // Q: What happens if the library gets a memory allocation failure
147 // while I'm editing? Will I lose my work?
148 //
149 // A: The library allocates all editor memory when you create
150 // the tilemap. It allocates a maximally-sized map and a
151 // fixed-size undo buffer (and the fixed-size copy buffer
152 // is static), and never allocates memory while it's running.
153 // So it can't fail due to running out of memory.
154 //
155 // Q: What happens if the library crashes while I'm editing? Will
156 // I lose my work?
157 //
158 // A: Yes. Save often.
159 //
160 //
161 // HOW TO COMPILE
162 //
163 // This header file contains both the header file and the
164 // implementation file in one. To create the implementation,
165 // in one source file define a few symbols first and then
166 // include this header:
167 //
168 // #define STB_TILEMAP_EDITOR_IMPLEMENTATION
169 // // this triggers the implementation
170 //
171 // void STBTE_DRAW_RECT(int x0, int y0, int x1, int y1, unsigned int color);
172 // // this must draw a filled rectangle (exclusive on right/bottom)
173 // // color = (r<<16)|(g<<8)|(b)
174 //
175 // void STBTE_DRAW_TILE(int x0, int y0,
176 // unsigned short id, int highlight, float *data);
177 // // this draws the tile image identified by 'id' in one of several
178 // // highlight modes (see STBTE_drawmode_* in the header section);
179 // // if 'data' is NULL, it's drawing the tile in the palette; if 'data'
180 // // is not NULL, it's drawing a tile on the map, and that is the data
181 // // associated with that map tile
182 //
183 // #include "stb_tilemap_editor.h"
184 //
185 // Optionally you can define the following functions before the include;
186 // note these must be macros (but they can just call a function) so
187 // this library can #ifdef to detect if you've defined them:
188 //
189 // #define STBTE_PROP_TYPE(int n, short *tiledata, float *params) ...
190 // // Returns the type of the n'th property of a given tile, which
191 // // controls how it is edited. Legal types are:
192 // // 0 /* no editable property in this slot */
193 // // STBTE_PROP_int /* uses a slider to adjust value */
194 // // STBTE_PROP_float /* uses a weird multi-axis control */
195 // // STBTE_PROP_bool /* uses a checkbox to change value */
196 // // And you can bitwise-OR in the following flags:
197 // // STBTE_PROP_disabled
198 // // Note that all of these are stored as floats in the param array.
199 // // The integer slider is limited in precision based on the space
200 // // available on screen, so for wide-ranged integers you may want
201 // // to use floats instead.
202 // //
203 // // Since the tiledata is passed to you, you can choose which property
204 // // is bound to that slot based on that data.
205 // //
206 // // Changing the type of a parameter does not cause the underlying
207 // // value to be clamped to the type min/max except when the tile is
208 // // explicitly selected.
209 //
210 // #define STBTE_PROP_NAME(int n, short *tiledata, float *params) ...
211 // // these return a string with the name for slot #n in the float
212 // // property list for the tile.
213 //
214 // #define STBTE_PROP_MIN(int n, short *tiledata) ...your code here...
215 // #define STBTE_PROP_MAX(int n, short *tiledata) ...your code here...
216 // // These return the allowable range for the property values for
217 // // the specified slot. It is never called for boolean types.
218 //
219 // #define STBTE_PROP_FLOAT_SCALE(int n, short *tiledata, float *params)
220 // // This rescales the float control for a given property; by default
221 // // left mouse drags add integers, right mouse drags adds fractions,
222 // // but you can rescale this per-property.
223 //
224 // #define STBTE_FLOAT_CONTROL_GRANULARITY ... value ...
225 // // This returns the number of pixels of mouse motion necessary
226 // // to advance the object float control. Default is 4
227 //
228 // #define STBTE_ALLOW_LINK(short *src, float *src_data, \
229 // short *dest, float *dest_data) ...your code...
230 // // this returns true or false depending on whether you allow a link
231 // // to be drawn from a tile 'src' to a tile 'dest'. if you don't
232 // // define this, linking will not be supported
233 //
234 // #define STBTE_LINK_COLOR(short *src, float *src_data, \
235 // short *dest, float *dest_data) ...your code...
236 // // return a color encoded as a 24-bit unsigned integer in the
237 // // form 0xRRGGBB. If you don't define this, default colors will
238 // // be used.
239 //
240 //
241 // [[ support for those below is not implemented yet ]]
242 //
243 // #define STBTE_HITTEST_TILE(x0,y0,id,mx,my) ...your code here...
244 // // this returns true or false depending on whether the mouse
245 // // pointer at mx,my is over (touching) a tile of type 'id'
246 // // displayed at x0,y0. Normally stb_tilemap_editor just does
247 // // this hittest based on the tile geometry, but if you have
248 // // tiles whose images extend out of the tile, you'll need this.
249 //
250 // ADDITIONAL CONFIGURATION
251 //
252 // The following symbols set static limits which determine how much
253 // memory will be allocated for the editor. You can override them
254 // by making similar definitions, but memory usage will increase.
255 //
256 // #define STBTE_MAX_TILEMAP_X 200 // max 4096
257 // #define STBTE_MAX_TILEMAP_Y 200 // max 4096
258 // #define STBTE_MAX_LAYERS 8 // max 32
259 // #define STBTE_MAX_CATEGORIES 100
260 // #define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB
261 // #define STBTE_MAX_COPY 90000 // e.g. 300x300
262 // #define STBTE_MAX_PROPERTIES 10 // max properties per tile
263 //
264 // API
265 //
266 // Further documentation appears in the header-file section below.
267 //
268 // EDITING MULTIPLE LEVELS
269 //
270 // You can only have one active editor instance. To switch between multiple
271 // levels, you can either store the levels in your own format and copy them
272 // in and out of the editor format, or you can create multiple stbte_tilemap
273 // objects and switch between them. The latter has the advantage that each
274 // stbte_tilemap keeps its own undo state. (The clipboard is global, so
275 // either approach allows cut&pasting between levels.)
276 //
277 // REVISION HISTORY
278 // 0.41 fix warnings
279 // 0.40 fix warning
280 // 0.39 fix warning
281 // 0.38 fix warning
282 // 0.37 fix warning
283 // 0.36 minor compiler support
284 // 0.35 layername button changes
285 // - layername buttons grow with the layer panel
286 // - fix stbte_create_map being declared as stbte_create
287 // - fix declaration of stbte_create_map
288 // 0.30 properties release
289 // - properties panel for editing user-defined "object" properties
290 // - can link each tile to one other tile
291 // - keyboard interface
292 // - fix eraser tool bug (worked in complex cases, failed in simple)
293 // - undo/redo tools have visible disabled state
294 // - tiles on higher layers draw on top of adjacent lower-layer tiles
295 // 0.20 erasable release
296 // - eraser tool
297 // - fix bug when pasting into protected layer
298 // - better color scheme
299 // - internal-use color picker
300 // 0.10 initial release
301 //
302 // TODO
303 //
304 // Separate scroll state for each category
305 // Implement paint bucket
306 // Support STBTE_HITTEST_TILE above
307 // ?Cancel drags by clicking other button? - may be fixed
308 // Finish support for toolbar at side
309 //
310 // CREDITS
311 //
312 //
313 // Main editor & features
314 // Sean Barrett
315 // Additional features:
316 // Josh Huelsman
317 // Bugfixes:
318 // Ryan Whitworth
319 // Eugene Opalev
320 //
321 // LICENSE
322 //
323 // See end of file for license information.
324
325
326
327 ///////////////////////////////////////////////////////////////////////
328 //
329 // HEADER SECTION
330
331 #ifndef STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H
332 #define STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H
333
334 #ifdef _WIN32
335 #ifndef _CRT_SECURE_NO_WARNINGS
336 #define _CRT_SECURE_NO_WARNINGS
337 #endif
338 #include <stdlib.h>
339 #include <stdio.h>
340 #endif
341
342 typedef struct stbte_tilemap stbte_tilemap;
343
344 // these are the drawmodes used in STBTE_DRAW_TILE
345 enum
346 {
347 STBTE_drawmode_deemphasize = -1,
348 STBTE_drawmode_normal = 0,
349 STBTE_drawmode_emphasize = 1,
350 };
351
352 // these are the property types
353 #define STBTE_PROP_none 0
354 #define STBTE_PROP_int 1
355 #define STBTE_PROP_float 2
356 #define STBTE_PROP_bool 3
357 #define STBTE_PROP_disabled 4
358
359 ////////
360 //
361 // creation
362 //
363
364 extern stbte_tilemap *stbte_create_map(int map_x, int map_y, int map_layers, int spacing_x, int spacing_y, int max_tiles);
365 // create an editable tilemap
366 // map_x : dimensions of map horizontally (user can change this in editor), <= STBTE_MAX_TILEMAP_X
367 // map_y : dimensions of map vertically (user can change this in editor) <= STBTE_MAX_TILEMAP_Y
368 // map_layers : number of layers to use (fixed), <= STBTE_MAX_LAYERS
369 // spacing_x : initial horizontal distance between left edges of map tiles in stb_tilemap_editor pixels
370 // spacing_y : initial vertical distance between top edges of map tiles in stb_tilemap_editor pixels
371 // max_tiles : maximum number of tiles that can defined
372 //
373 // If insufficient memory, returns NULL
374
375 extern void stbte_define_tile(stbte_tilemap *tm, unsigned short id, unsigned int layermask, const char * category);
376 // call this repeatedly for each tile to install the tile definitions into the editable tilemap
377 // tm : tilemap created by stbte_create_map
378 // id : unique identifier for each tile, 0 <= id < 32768
379 // layermask : bitmask of which layers tile is allowed on: 1 = layer 0, 255 = layers 0..7
380 // (note that onscreen, the editor numbers the layers from 1 not 0)
381 // layer 0 is the furthest back, layer 1 is just in front of layer 0, etc
382 // category : which category this tile is grouped in
383
384 extern void stbte_set_display(int x0, int y0, int x1, int y1);
385 // call this once to set the size; if you resize, call it again
386
387
388 /////////
389 //
390 // every frame
391 //
392
393 extern void stbte_draw(stbte_tilemap *tm);
394
395 extern void stbte_tick(stbte_tilemap *tm, float time_in_seconds_since_last_frame);
396
397 ////////////
398 //
399 // user input
400 //
401
402 // if you're using SDL, call the next function for SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP, SDL_MOUSEWHEEL;
403 // the transformation lets you scale from SDL mouse coords to stb_tilemap_editor coords
404 extern void stbte_mouse_sdl(stbte_tilemap *tm, const void *sdl_event, float xscale, float yscale, int xoffset, int yoffset);
405
406 // otherwise, hook these up explicitly:
407 extern void stbte_mouse_move(stbte_tilemap *tm, int x, int y, int shifted, int scrollkey);
408 extern void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, int shifted, int scrollkey);
409 extern void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll);
410
411 // note: at the moment, mouse wheel events (SDL_MOUSEWHEEL) are ignored.
412
413 // for keyboard, define your own mapping from keys to the following actions.
414 // this is totally optional, as all features are accessible with the mouse
415 enum stbte_action
416 {
417 STBTE_tool_select,
418 STBTE_tool_brush,
419 STBTE_tool_erase,
420 STBTE_tool_rectangle,
421 STBTE_tool_eyedropper,
422 STBTE_tool_link,
423 STBTE_act_toggle_grid,
424 STBTE_act_toggle_links,
425 STBTE_act_undo,
426 STBTE_act_redo,
427 STBTE_act_cut,
428 STBTE_act_copy,
429 STBTE_act_paste,
430 STBTE_scroll_left,
431 STBTE_scroll_right,
432 STBTE_scroll_up,
433 STBTE_scroll_down,
434 };
435 extern void stbte_action(stbte_tilemap *tm, enum stbte_action act);
436
437 ////////////////
438 //
439 // save/load
440 //
441 // There is no editor file format. You have to save and load the data yourself
442 // through the following functions. You can also use these functions to get the
443 // data to generate game-formatted levels directly. (But make sure you save
444 // first! You may also want to autosave to a temp file periodically, etc etc.)
445
446 #define STBTE_EMPTY -1
447
448 extern void stbte_get_dimensions(stbte_tilemap *tm, int *max_x, int *max_y);
449 // get the dimensions of the level, since the user can change them
450
451 extern short* stbte_get_tile(stbte_tilemap *tm, int x, int y);
452 // returns an array of shorts that is 'map_layers' in length. each short is
453 // either one of the tile_id values from define_tile, or STBTE_EMPTY.
454
455 extern float *stbte_get_properties(stbte_tilemap *tm, int x, int y);
456 // get the property array associated with the tile at x,y. this is an
457 // array of floats that is STBTE_MAX_PROPERTIES in length; you have to
458 // interpret the slots according to the semantics you've chosen
459
460 extern void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty);
461 // gets the link associated with the tile at x,y.
462
463 extern void stbte_set_dimensions(stbte_tilemap *tm, int max_x, int max_y);
464 // set the dimensions of the level, overrides previous stbte_create_map()
465 // values or anything the user has changed
466
467 extern void stbte_clear_map(stbte_tilemap *tm);
468 // clears the map, including the region outside the defined region, so if the
469 // user expands the map, they won't see garbage there
470
471 extern void stbte_set_tile(stbte_tilemap *tm, int x, int y, int layer, signed short tile);
472 // tile is your tile_id from define_tile, or STBTE_EMPTY
473
474 extern void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val);
475 // set the value of the n'th slot of the tile at x,y
476
477 extern void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty);
478 // set a link going from x,y to destx,desty. to force no link,
479 // use destx=desty=-1
480
481 ////////
482 //
483 // optional
484 //
485
486 extern void stbte_set_background_tile(stbte_tilemap *tm, short id);
487 // selects the tile to fill the bottom layer with and used to clear bottom tiles to;
488 // should be same ID as
489
490 extern void stbte_set_sidewidths(int left, int right);
491 // call this once to set the left & right side widths. don't call
492 // it again since the user can change it
493
494 extern void stbte_set_spacing(stbte_tilemap *tm, int spacing_x, int spacing_y, int palette_spacing_x, int palette_spacing_y);
495 // call this to set the spacing of map tiles and the spacing of palette tiles.
496 // if you rescale your display, call it again (e.g. you can implement map zooming yourself)
497
498 extern void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layername);
499 // sets a string name for your layer that shows in the layer selector. note that this
500 // makes the layer selector wider. 'layer' is from 0..(map_layers-1)
501
502 #endif
503
504 #ifdef STB_TILEMAP_EDITOR_IMPLEMENTATION
505
506 #ifndef STBTE_ASSERT
507 #define STBTE_ASSERT assert
508 #include <assert.h>
509 #endif
510
511 #ifdef _MSC_VER
512 #define STBTE__NOTUSED(v) (void)(v)
513 #else
514 #define STBTE__NOTUSED(v) (void)sizeof(v)
515 #endif
516
517 #ifndef STBTE_MAX_TILEMAP_X
518 #define STBTE_MAX_TILEMAP_X 200
519 #endif
520
521 #ifndef STBTE_MAX_TILEMAP_Y
522 #define STBTE_MAX_TILEMAP_Y 200
523 #endif
524
525 #ifndef STBTE_MAX_LAYERS
526 #define STBTE_MAX_LAYERS 8
527 #endif
528
529 #ifndef STBTE_MAX_CATEGORIES
530 #define STBTE_MAX_CATEGORIES 100
531 #endif
532
533 #ifndef STBTE_MAX_COPY
534 #define STBTE_MAX_COPY 65536
535 #endif
536
537 #ifndef STBTE_UNDO_BUFFER_BYTES
538 #define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB
539 #endif
540
541 #ifndef STBTE_PROP_TYPE
542 #define STBTE__NO_PROPS
543 #define STBTE_PROP_TYPE(n,td,tp) 0
544 #endif
545
546 #ifndef STBTE_PROP_NAME
547 #define STBTE_PROP_NAME(n,td,tp) ""
548 #endif
549
550 #ifndef STBTE_MAX_PROPERTIES
551 #define STBTE_MAX_PROPERTIES 10
552 #endif
553
554 #ifndef STBTE_PROP_MIN
555 #define STBTE_PROP_MIN(n,td,tp) 0
556 #endif
557
558 #ifndef STBTE_PROP_MAX
559 #define STBTE_PROP_MAX(n,td,tp) 100.0
560 #endif
561
562 #ifndef STBTE_PROP_FLOAT_SCALE
563 #define STBTE_PROP_FLOAT_SCALE(n,td,tp) 1 // default scale size
564 #endif
565
566 #ifndef STBTE_FLOAT_CONTROL_GRANULARITY
567 #define STBTE_FLOAT_CONTROL_GRANULARITY 4
568 #endif
569
570
571 #define STBTE__UNDO_BUFFER_COUNT (STBTE_UNDO_BUFFER_BYTES>>1)
572
573 #if STBTE_MAX_TILEMAP_X > 4096 || STBTE_MAX_TILEMAP_Y > 4096
574 #error "Maximum editable map size is 4096 x 4096"
575 #endif
576 #if STBTE_MAX_LAYERS > 32
577 #error "Maximum layers allowed is 32"
578 #endif
579 #if STBTE_UNDO_BUFFER_COUNT & (STBTE_UNDO_BUFFER_COUNT-1)
580 #error "Undo buffer size must be a power of 2"
581 #endif
582
583 #if STBTE_MAX_PROPERTIES == 0
584 #define STBTE__NO_PROPS
585 #endif
586
587 #ifdef STBTE__NO_PROPS
588 #undef STBTE_MAX_PROPERTIES
589 #define STBTE_MAX_PROPERTIES 1 // so we can declare arrays
590 #endif
591
592 typedef struct
593 {
594 short x,y;
595 } stbte__link;
596
597 enum
598 {
599 STBTE__base,
600 STBTE__outline,
601 STBTE__text,
602
603 STBTE__num_color_aspects,
604 };
605
606 enum
607 {
608 STBTE__idle,
609 STBTE__over,
610 STBTE__down,
611 STBTE__over_down,
612 STBTE__selected,
613 STBTE__selected_over,
614 STBTE__disabled,
615 STBTE__num_color_states,
616 };
617
618 enum
619 {
620 STBTE__cexpander,
621 STBTE__ctoolbar,
622 STBTE__ctoolbar_button,
623 STBTE__cpanel,
624 STBTE__cpanel_sider,
625 STBTE__cpanel_sizer,
626 STBTE__cscrollbar,
627 STBTE__cmapsize,
628 STBTE__clayer_button,
629 STBTE__clayer_hide,
630 STBTE__clayer_lock,
631 STBTE__clayer_solo,
632 STBTE__ccategory_button,
633
634 STBTE__num_color_modes,
635 };
636
637 #ifdef STBTE__COLORPICKER
638 static char *stbte__color_names[] =
639 {
640 "expander", "toolbar", "tool button", "panel",
641 "panel c1", "panel c2", "scollbar", "map button",
642 "layer", "hide", "lock", "solo",
643 "category",
644 };
645 #endif // STBTE__COLORPICKER
646
647 // idle, over, down, over&down, selected, sel&over, disabled
648 static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][STBTE__num_color_states] =
649 {
650 {
651 { 0x000000, 0x84987c, 0xdcdca8, 0xdcdca8, 0x40c040, 0x60d060, 0x505050, },
652 { 0xa4b090, 0xe0ec80, 0xffffc0, 0xffffc0, 0x80ff80, 0x80ff80, 0x606060, },
653 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
654 }, {
655 { 0x808890, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, },
656 { 0x605860, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, },
657 { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, },
658 }, {
659 { 0x3c5068, 0x7088a8, 0x647488, 0x94b4dc, 0x8890c4, 0x9caccc, 0x404040, },
660 { 0x889cb8, 0x889cb8, 0x889cb8, 0x889cb8, 0x84c4e8, 0xacc8ff, 0x0c0c08, },
661 { 0xbcc4cc, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x707074, },
662 }, {
663 { 0x403848, 0x403010, 0x403010, 0x403010, 0x403010, 0x403010, 0x303024, },
664 { 0x68546c, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0x605030, },
665 { 0xf4e4ff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
666 }, {
667 { 0xb4b04c, 0xacac60, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, },
668 { 0xa0a04c, 0xd0d04c, 0xffff80, 0xffff80, 0x80ff80, 0x80ff80, 0x606060, },
669 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
670 }, {
671 { 0x40c440, 0x60d060, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, },
672 { 0x40c040, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x606060, },
673 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
674 }, {
675 { 0x9090ac, 0xa0a0b8, 0xbcb8cc, 0xbcb8cc, 0x909040, 0x909040, 0x909040, },
676 { 0xa0a0b8, 0xb0b4d0, 0xa0a0b8, 0xa0a0b8, 0xa0a050, 0xa0a050, 0xa0a050, },
677 { 0x808088, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, },
678 }, {
679 { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, },
680 { 0x646064, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, },
681 { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
682 }, {
683 { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, },
684 { 0xb09cb4, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, },
685 { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
686 }, {
687 { 0x646494, 0x888cb8, 0xb0b0b0, 0xb0b0cc, 0x9c9cf4, 0x8888b0, 0x50506c, },
688 { 0x9090a4, 0xb0b4d4, 0xb0b0dc, 0xb0b0cc, 0xd0d0fc, 0xd0d4f0, 0x606060, },
689 { 0xb4b4d4, 0xe4e4ff, 0xffffff, 0xffffff, 0xe0e4ff, 0xececff, 0x909090, },
690 }, {
691 { 0x646444, 0x888c64, 0xb0b0b0, 0xb0b088, 0xaca858, 0x88886c, 0x505050, },
692 { 0x88886c, 0xb0b490, 0xb0b0b0, 0xb0b088, 0xd8d898, 0xd0d4b0, 0x606060, },
693 { 0xb4b49c, 0xffffd8, 0xffffff, 0xffffd4, 0xffffdc, 0xffffcc, 0x909090, },
694 }, {
695 { 0x906464, 0xb48c8c, 0xd4b0b0, 0xdcb0b0, 0xff9c9c, 0xc88888, 0x505050, },
696 { 0xb47c80, 0xd4b4b8, 0xc4a8a8, 0xdcb0b0, 0xffc0c0, 0xfce8ec, 0x606060, },
697 { 0xe0b4b4, 0xffdcd8, 0xffd8d4, 0xffe0e4, 0xffece8, 0xffffff, 0x909090, },
698 }, {
699 { 0x403848, 0x403848, 0x403848, 0x886894, 0x7c80c8, 0x7c80c8, 0x302828, },
700 { 0x403848, 0x403848, 0x403848, 0x403848, 0x7c80c8, 0x7c80c8, 0x403838, },
701 { 0xc8c4c8, 0xffffff, 0xffffff, 0xffffff, 0xe8e8ec, 0xffffff, 0x909090, },
702 },
703 };
704
705 #define STBTE_COLOR_TILEMAP_BACKGROUND 0x000000
706 #define STBTE_COLOR_TILEMAP_BORDER 0x203060
707 #define STBTE_COLOR_TILEMAP_HIGHLIGHT 0xffffff
708 #define STBTE_COLOR_GRID 0x404040
709 #define STBTE_COLOR_SELECTION_OUTLINE1 0xdfdfdf
710 #define STBTE_COLOR_SELECTION_OUTLINE2 0x303030
711 #define STBTE_COLOR_TILEPALETTE_OUTLINE 0xffffff
712 #define STBTE_COLOR_TILEPALETTE_BACKGROUND 0x000000
713
714 #ifndef STBTE_LINK_COLOR
715 #define STBTE_LINK_COLOR(src,sp,dest,dp) 0x5030ff
716 #endif
717
718 #ifndef STBTE_LINK_COLOR_DRAWING
719 #define STBTE_LINK_COLOR_DRAWING 0xff40ff
720 #endif
721
722 #ifndef STBTE_LINK_COLOR_DISALLOWED
723 #define STBTE_LINK_COLOR_DISALLOWED 0x602060
724 #endif
725
726
727 // disabled, selected, down, over
728 static unsigned char stbte__state_to_index[2][2][2][2] =
729 {
730 {
731 { { STBTE__idle , STBTE__over }, { STBTE__down , STBTE__over_down }, },
732 { { STBTE__selected, STBTE__selected_over }, { STBTE__down , STBTE__over_down }, },
733 },{
734 { { STBTE__disabled, STBTE__disabled }, { STBTE__disabled, STBTE__disabled }, },
735 { { STBTE__selected, STBTE__selected_over }, { STBTE__disabled, STBTE__disabled }, },
736 }
737 };
738 #define STBTE__INDEX_FOR_STATE(disable,select,down,over) stbte__state_to_index[disable][select][down][over]
739 #define STBTE__INDEX_FOR_ID(id,disable,select) STBTE__INDEX_FOR_STATE(disable,select,STBTE__IS_ACTIVE(id),STBTE__IS_HOT(id))
740
741 #define STBTE__FONT_HEIGHT 9
742 static short stbte__font_offset[95+16];
743 static short stbte__fontdata[769] =
744 {
745 4,9,6,9,9,9,9,8,9,8,4,9,7,7,7,7,4,2,6,8,6,6,7,3,4,4,8,6,3,6,2,6,6,6,6,6,6,
746 6,6,6,6,6,2,3,5,4,5,6,6,6,6,6,6,6,6,6,6,6,6,7,6,7,7,7,6,7,6,6,6,6,7,7,6,6,
747 6,4,6,4,7,7,3,6,6,5,6,6,5,6,6,4,5,6,4,7,6,6,6,6,6,6,6,6,6,7,6,6,6,5,2,5,8,
748 0,0,0,0,2,253,130,456,156,8,72,184,64,2,125,66,64,160,64,146,511,146,146,
749 511,146,146,511,146,511,257,341,297,341,297,341,257,511,16,56,124,16,16,16,
750 124,56,16,96,144,270,261,262,136,80,48,224,192,160,80,40,22,14,15,3,448,496,
751 496,240,232,20,10,5,2,112,232,452,450,225,113,58,28,63,30,60,200,455,257,
752 257,0,0,0,257,257,455,120,204,132,132,159,14,4,4,14,159,132,132,204,120,8,
753 24,56,120,56,24,8,32,48,56,60,56,48,32,0,0,0,0,111,111,7,7,0,0,7,7,34,127,
754 127,34,34,127,127,34,36,46,107,107,58,18,99,51,24,12,102,99,48,122,79,93,
755 55,114,80,4,7,3,62,127,99,65,65,99,127,62,8,42,62,28,28,62,42,8,8,8,62,62,
756 8,8,128,224,96,8,8,8,8,8,8,96,96,96,48,24,12,6,3,62,127,89,77,127,62,64,66,
757 127,127,64,64,98,115,89,77,71,66,33,97,73,93,119,35,24,28,22,127,127,16,39,
758 103,69,69,125,57,62,127,73,73,121,48,1,1,113,121,15,7,54,127,73,73,127,54,
759 6,79,73,105,63,30,54,54,128,246,118,8,28,54,99,65,20,20,20,20,65,99,54,28,
760 8,2,3,105,109,7,2,30,63,33,45,47,46,124,126,19,19,126,124,127,127,73,73,127,
761 54,62,127,65,65,99,34,127,127,65,99,62,28,127,127,73,73,73,65,127,127,9,9,
762 9,1,62,127,65,73,121,121,127,127,8,8,127,127,65,65,127,127,65,65,32,96,64,
763 64,127,63,127,127,8,28,54,99,65,127,127,64,64,64,64,127,127,6,12,6,127,127,
764 127,127,6,12,24,127,127,62,127,65,65,65,127,62,127,127,9,9,15,6,62,127,65,
765 81,49,127,94,127,127,9,25,127,102,70,79,73,73,121,49,1,1,127,127,1,1,63,127,
766 64,64,127,63,15,31,48,96,48,31,15,127,127,48,24,48,127,127,99,119,28,28,119,
767 99,7,15,120,120,15,7,97,113,89,77,71,67,127,127,65,65,3,6,12,24,48,96,65,
768 65,127,127,8,12,6,3,6,12,8,64,64,64,64,64,64,64,3,7,4,32,116,84,84,124,120,
769 127,127,68,68,124,56,56,124,68,68,68,56,124,68,68,127,127,56,124,84,84,92,
770 24,8,124,126,10,10,56,380,324,324,508,252,127,127,4,4,124,120,72,122,122,
771 64,256,256,256,506,250,126,126,16,56,104,64,66,126,126,64,124,124,24,56,28,
772 124,120,124,124,4,4,124,120,56,124,68,68,124,56,508,508,68,68,124,56,56,124,
773 68,68,508,508,124,124,4,4,12,8,72,92,84,84,116,36,4,4,62,126,68,68,60,124,
774 64,64,124,124,28,60,96,96,60,28,28,124,112,56,112,124,28,68,108,56,56,108,
775 68,284,316,352,320,508,252,68,100,116,92,76,68,8,62,119,65,65,127,127,65,
776 65,119,62,8,16,24,12,12,24,24,12,4,
777 };
778
779 typedef struct
780 {
781 short id;
782 unsigned short category_id;
783 char *category;
784 unsigned int layermask;
785 } stbte__tileinfo;
786
787 #define MAX_LAYERMASK (1 << (8*sizeof(unsigned int)))
788
789 typedef short stbte__tiledata;
790
791 #define STBTE__NO_TILE -1
792
793 enum
794 {
795 STBTE__panel_toolbar,
796 STBTE__panel_colorpick,
797 STBTE__panel_info,
798 STBTE__panel_layers,
799 STBTE__panel_props,
800 STBTE__panel_categories,
801 STBTE__panel_tiles,
802
803 STBTE__num_panel,
804 };
805
806 enum
807 {
808 STBTE__side_left,
809 STBTE__side_right,
810 STBTE__side_top,
811 STBTE__side_bottom,
812 };
813
814 enum
815 {
816 STBTE__tool_select,
817 STBTE__tool_brush,
818 STBTE__tool_erase,
819 STBTE__tool_rect,
820 STBTE__tool_eyedrop,
821 STBTE__tool_fill,
822 STBTE__tool_link,
823
824 STBTE__tool_showgrid,
825 STBTE__tool_showlinks,
826
827 STBTE__tool_undo,
828 STBTE__tool_redo,
829 // copy/cut/paste aren't included here because they're displayed differently
830
831 STBTE__num_tool,
832 };
833
834 // icons are stored in the 0-31 range of ASCII in the font
835 static int toolchar[] = { 26,24,25,20,23,22,18, 19,17, 29,28, };
836
837 enum
838 {
839 STBTE__propmode_default,
840 STBTE__propmode_always,
841 STBTE__propmode_never,
842 };
843
844 enum
845 {
846 STBTE__paint,
847
848 // from here down does hittesting
849 STBTE__tick,
850 STBTE__mousemove,
851 STBTE__mousewheel,
852 STBTE__leftdown,
853 STBTE__leftup,
854 STBTE__rightdown,
855 STBTE__rightup,
856 };
857
858 typedef struct
859 {
860 int expanded, mode;
861 int delta_height; // number of rows they've requested for this
862 int side;
863 int width,height;
864 int x0,y0;
865 } stbte__panel;
866
867 typedef struct
868 {
869 int x0,y0,x1,y1,color;
870 } stbte__colorrect;
871
872 #define STBTE__MAX_DELAYRECT 256
873
874 typedef struct
875 {
876 int tool, active_event;
877 int active_id, hot_id, next_hot_id;
878 int event;
879 int mx,my, dx,dy;
880 int ms_time;
881 int shift, scrollkey;
882 int initted;
883 int side_extended[2];
884 stbte__colorrect delayrect[STBTE__MAX_DELAYRECT];
885 int delaycount;
886 int show_grid, show_links;
887 int brush_state; // used to decide which kind of erasing
888 int eyedrop_x, eyedrop_y, eyedrop_last_layer;
889 int pasting, paste_x, paste_y;
890 int scrolling, start_x, start_y;
891 int last_mouse_x, last_mouse_y;
892 int accum_x, accum_y;
893 int linking;
894 int dragging;
895 int drag_x, drag_y, drag_w, drag_h;
896 int drag_offx, drag_offy, drag_dest_x, drag_dest_y;
897 int undoing;
898 int has_selection, select_x0, select_y0, select_x1, select_y1;
899 int sx,sy;
900 int x0,y0,x1,y1, left_width, right_width; // configurable widths
901 float alert_timer;
902 const char *alert_msg;
903 float dt;
904 stbte__panel panel[STBTE__num_panel];
905 short copybuffer[STBTE_MAX_COPY][STBTE_MAX_LAYERS];
906 float copyprops[STBTE_MAX_COPY][STBTE_MAX_PROPERTIES];
907 #ifdef STBTE_ALLOW_LINK
908 stbte__link copylinks[STBTE_MAX_COPY];
909 #endif
910 int copy_src_x, copy_src_y;
911 stbte_tilemap *copy_src;
912 int copy_width,copy_height,has_copy,copy_has_props;
913 } stbte__ui_t;
914
915 // there's only one UI system at a time, so we can globalize this
916 static stbte__ui_t stbte__ui = { STBTE__tool_brush, 0 };
917
918 #define STBTE__INACTIVE() (stbte__ui.active_id == 0)
919 #define STBTE__IS_ACTIVE(id) (stbte__ui.active_id == (id))
920 #define STBTE__IS_HOT(id) (stbte__ui.hot_id == (id))
921
922 #define STBTE__BUTTON_HEIGHT (STBTE__FONT_HEIGHT + 2 * STBTE__BUTTON_INTERNAL_SPACING)
923 #define STBTE__BUTTON_INTERNAL_SPACING (2 + (STBTE__FONT_HEIGHT>>4))
924
925 typedef struct
926 {
927 const char *name;
928 int locked;
929 int hidden;
930 } stbte__layer;
931
932 enum
933 {
934 STBTE__unlocked,
935 STBTE__protected,
936 STBTE__locked,
937 };
938
939 struct stbte_tilemap
940 {
941 stbte__tiledata data[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_LAYERS];
942 float props[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_PROPERTIES];
943 #ifdef STBTE_ALLOW_LINK
944 stbte__link link[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X];
945 int linkcount[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X];
946 #endif
947 int max_x, max_y, num_layers;
948 int spacing_x, spacing_y;
949 int palette_spacing_x, palette_spacing_y;
950 int scroll_x,scroll_y;
951 int cur_category, cur_tile, cur_layer;
952 char *categories[STBTE_MAX_CATEGORIES];
953 int num_categories, category_scroll;
954 stbte__tileinfo *tiles;
955 int num_tiles, max_tiles, digits;
956 unsigned char undo_available_valid;
957 unsigned char undo_available;
958 unsigned char redo_available;
959 unsigned char padding;
960 int cur_palette_count;
961 int palette_scroll;
962 int tileinfo_dirty;
963 stbte__layer layerinfo[STBTE_MAX_LAYERS];
964 int has_layer_names;
965 int layername_width;
966 int layer_scroll;
967 int propmode;
968 int solo_layer;
969 int undo_pos, undo_len, redo_len;
970 short background_tile;
971 unsigned char id_in_use[32768>>3];
972 short *undo_buffer;
973 };
974
975 static char *default_category = (char*) "[unassigned]";
976
stbte__init_gui(void)977 static void stbte__init_gui(void)
978 {
979 int i,n;
980 stbte__ui.initted = 1;
981 // init UI state
982 stbte__ui.show_links = 1;
983 for (i=0; i < STBTE__num_panel; ++i) {
984 stbte__ui.panel[i].expanded = 1; // visible if not autohidden
985 stbte__ui.panel[i].delta_height = 0;
986 stbte__ui.panel[i].side = STBTE__side_left;
987 }
988 stbte__ui.panel[STBTE__panel_toolbar ].side = STBTE__side_top;
989 stbte__ui.panel[STBTE__panel_colorpick].side = STBTE__side_right;
990
991 if (stbte__ui.left_width == 0)
992 stbte__ui.left_width = 80;
993 if (stbte__ui.right_width == 0)
994 stbte__ui.right_width = 80;
995
996 // init font
997 n=95+16;
998 for (i=0; i < 95+16; ++i) {
999 stbte__font_offset[i] = n;
1000 n += stbte__fontdata[i];
1001 }
1002 }
1003
stbte_create_map(int map_x,int map_y,int map_layers,int spacing_x,int spacing_y,int max_tiles)1004 stbte_tilemap *stbte_create_map(int map_x, int map_y, int map_layers, int spacing_x, int spacing_y, int max_tiles)
1005 {
1006 int i;
1007 stbte_tilemap *tm;
1008 STBTE_ASSERT(map_layers >= 0 && map_layers <= STBTE_MAX_LAYERS);
1009 STBTE_ASSERT(map_x >= 0 && map_x <= STBTE_MAX_TILEMAP_X);
1010 STBTE_ASSERT(map_y >= 0 && map_y <= STBTE_MAX_TILEMAP_Y);
1011 if (map_x < 0 || map_y < 0 || map_layers < 0 ||
1012 map_x > STBTE_MAX_TILEMAP_X || map_y > STBTE_MAX_TILEMAP_Y || map_layers > STBTE_MAX_LAYERS)
1013 return NULL;
1014
1015 if (!stbte__ui.initted)
1016 stbte__init_gui();
1017
1018 tm = (stbte_tilemap *) malloc(sizeof(*tm) + sizeof(*tm->tiles) * max_tiles + STBTE_UNDO_BUFFER_BYTES);
1019 if (tm == NULL)
1020 return NULL;
1021
1022 tm->tiles = (stbte__tileinfo *) (tm+1);
1023 tm->undo_buffer = (short *) (tm->tiles + max_tiles);
1024 tm->num_layers = map_layers;
1025 tm->max_x = map_x;
1026 tm->max_y = map_y;
1027 tm->spacing_x = spacing_x;
1028 tm->spacing_y = spacing_y;
1029 tm->scroll_x = 0;
1030 tm->scroll_y = 0;
1031 tm->palette_scroll = 0;
1032 tm->palette_spacing_x = spacing_x+1;
1033 tm->palette_spacing_y = spacing_y+1;
1034 tm->cur_category = -1;
1035 tm->cur_tile = 0;
1036 tm->solo_layer = -1;
1037 tm->undo_len = 0;
1038 tm->redo_len = 0;
1039 tm->undo_pos = 0;
1040 tm->category_scroll = 0;
1041 tm->layer_scroll = 0;
1042 tm->propmode = 0;
1043 tm->has_layer_names = 0;
1044 tm->layername_width = 0;
1045 tm->undo_available_valid = 0;
1046
1047 for (i=0; i < tm->num_layers; ++i) {
1048 tm->layerinfo[i].hidden = 0;
1049 tm->layerinfo[i].locked = STBTE__unlocked;
1050 tm->layerinfo[i].name = 0;
1051 }
1052
1053 tm->background_tile = STBTE__NO_TILE;
1054 stbte_clear_map(tm);
1055
1056 tm->max_tiles = max_tiles;
1057 tm->num_tiles = 0;
1058 for (i=0; i < 32768/8; ++i)
1059 tm->id_in_use[i] = 0;
1060 tm->tileinfo_dirty = 1;
1061 return tm;
1062 }
1063
stbte_set_background_tile(stbte_tilemap * tm,short id)1064 void stbte_set_background_tile(stbte_tilemap *tm, short id)
1065 {
1066 int i;
1067 STBTE_ASSERT(id >= -1);
1068 // STBTE_ASSERT(id < 32768);
1069 if (id < -1)
1070 return;
1071 for (i=0; i < STBTE_MAX_TILEMAP_X * STBTE_MAX_TILEMAP_Y; ++i)
1072 if (tm->data[0][i][0] == -1)
1073 tm->data[0][i][0] = id;
1074 tm->background_tile = id;
1075 }
1076
stbte_set_spacing(stbte_tilemap * tm,int spacing_x,int spacing_y,int palette_spacing_x,int palette_spacing_y)1077 void stbte_set_spacing(stbte_tilemap *tm, int spacing_x, int spacing_y, int palette_spacing_x, int palette_spacing_y)
1078 {
1079 tm->spacing_x = spacing_x;
1080 tm->spacing_y = spacing_y;
1081 tm->palette_spacing_x = palette_spacing_x;
1082 tm->palette_spacing_y = palette_spacing_y;
1083 }
1084
stbte_set_sidewidths(int left,int right)1085 void stbte_set_sidewidths(int left, int right)
1086 {
1087 stbte__ui.left_width = left;
1088 stbte__ui.right_width = right;
1089 }
1090
stbte_set_display(int x0,int y0,int x1,int y1)1091 void stbte_set_display(int x0, int y0, int x1, int y1)
1092 {
1093 stbte__ui.x0 = x0;
1094 stbte__ui.y0 = y0;
1095 stbte__ui.x1 = x1;
1096 stbte__ui.y1 = y1;
1097 }
1098
stbte_define_tile(stbte_tilemap * tm,unsigned short id,unsigned int layermask,const char * category_c)1099 void stbte_define_tile(stbte_tilemap *tm, unsigned short id, unsigned int layermask, const char * category_c)
1100 {
1101 char *category = (char *) category_c;
1102 STBTE_ASSERT(id < 32768);
1103 STBTE_ASSERT(tm->num_tiles < tm->max_tiles);
1104 STBTE_ASSERT((tm->id_in_use[id>>3]&(1<<(id&7))) == 0);
1105 if (id >= 32768 || tm->num_tiles >= tm->max_tiles || (tm->id_in_use[id>>3]&(1<<(id&7))))
1106 return;
1107
1108 if (category == NULL)
1109 category = (char*) default_category;
1110 tm->id_in_use[id>>3] |= 1 << (id&7);
1111 tm->tiles[tm->num_tiles].category = category;
1112 tm->tiles[tm->num_tiles].id = id;
1113 tm->tiles[tm->num_tiles].layermask = layermask;
1114 ++tm->num_tiles;
1115 tm->tileinfo_dirty = 1;
1116 }
1117
1118 static int stbte__text_width(const char *str);
1119
stbte_set_layername(stbte_tilemap * tm,int layer,const char * layername)1120 void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layername)
1121 {
1122 STBTE_ASSERT(layer >= 0 && layer < tm->num_layers);
1123 if (layer >= 0 && layer < tm->num_layers) {
1124 int width;
1125 tm->layerinfo[layer].name = layername;
1126 tm->has_layer_names = 1;
1127 width = stbte__text_width(layername);
1128 tm->layername_width = (width > tm->layername_width ? width : tm->layername_width);
1129 }
1130 }
1131
stbte_get_dimensions(stbte_tilemap * tm,int * max_x,int * max_y)1132 void stbte_get_dimensions(stbte_tilemap *tm, int *max_x, int *max_y)
1133 {
1134 *max_x = tm->max_x;
1135 *max_y = tm->max_y;
1136 }
1137
stbte_get_tile(stbte_tilemap * tm,int x,int y)1138 short* stbte_get_tile(stbte_tilemap *tm, int x, int y)
1139 {
1140 STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y);
1141 if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y)
1142 return NULL;
1143 return tm->data[y][x];
1144 }
1145
stbte_get_properties(stbte_tilemap * tm,int x,int y)1146 float *stbte_get_properties(stbte_tilemap *tm, int x, int y)
1147 {
1148 STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y);
1149 if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y)
1150 return NULL;
1151 return tm->props[y][x];
1152 }
1153
stbte_get_link(stbte_tilemap * tm,int x,int y,int * destx,int * desty)1154 void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty)
1155 {
1156 int gx=-1,gy=-1;
1157 STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y);
1158 #ifdef STBTE_ALLOW_LINK
1159 if (x >= 0 && x < STBTE_MAX_TILEMAP_X && y >= 0 && y < STBTE_MAX_TILEMAP_Y) {
1160 gx = tm->link[y][x].x;
1161 gy = tm->link[y][x].y;
1162 if (gx >= 0)
1163 if (!STBTE_ALLOW_LINK(tm->data[y][x], tm->props[y][x], tm->data[gy][gx], tm->props[gy][gx]))
1164 gx = gy = -1;
1165 }
1166 #endif
1167 *destx = gx;
1168 *desty = gy;
1169 }
1170
stbte_set_property(stbte_tilemap * tm,int x,int y,int n,float val)1171 void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val)
1172 {
1173 tm->props[y][x][n] = val;
1174 }
1175
1176 #ifdef STBTE_ALLOW_LINK
1177 static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode);
1178 #endif
1179
1180 enum
1181 {
1182 STBTE__undo_none,
1183 STBTE__undo_record,
1184 STBTE__undo_block,
1185 };
1186
stbte_set_link(stbte_tilemap * tm,int x,int y,int destx,int desty)1187 void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty)
1188 {
1189 #ifdef STBTE_ALLOW_LINK
1190 stbte__set_link(tm, x, y, destx, desty, STBTE__undo_none);
1191 #else
1192 STBTE_ASSERT(0);
1193 #endif
1194 }
1195
1196
1197 // returns an array of map_layers shorts. each short is either
1198 // one of the tile_id values from define_tile, or STBTE_EMPTY
1199
stbte_set_dimensions(stbte_tilemap * tm,int map_x,int map_y)1200 void stbte_set_dimensions(stbte_tilemap *tm, int map_x, int map_y)
1201 {
1202 STBTE_ASSERT(map_x >= 0 && map_x <= STBTE_MAX_TILEMAP_X);
1203 STBTE_ASSERT(map_y >= 0 && map_y <= STBTE_MAX_TILEMAP_Y);
1204 if (map_x < 0 || map_y < 0 || map_x > STBTE_MAX_TILEMAP_X || map_y > STBTE_MAX_TILEMAP_Y)
1205 return;
1206 tm->max_x = map_x;
1207 tm->max_y = map_y;
1208 }
1209
stbte_clear_map(stbte_tilemap * tm)1210 void stbte_clear_map(stbte_tilemap *tm)
1211 {
1212 int i,j;
1213 for (i=0; i < STBTE_MAX_TILEMAP_X * STBTE_MAX_TILEMAP_Y; ++i) {
1214 tm->data[0][i][0] = tm->background_tile;
1215 for (j=1; j < tm->num_layers; ++j)
1216 tm->data[0][i][j] = STBTE__NO_TILE;
1217 for (j=0; j < STBTE_MAX_PROPERTIES; ++j)
1218 tm->props[0][i][j] = 0;
1219 #ifdef STBTE_ALLOW_LINK
1220 tm->link[0][i].x = -1;
1221 tm->link[0][i].y = -1;
1222 tm->linkcount[0][i] = 0;
1223 #endif
1224 }
1225 }
1226
stbte_set_tile(stbte_tilemap * tm,int x,int y,int layer,signed short tile)1227 void stbte_set_tile(stbte_tilemap *tm, int x, int y, int layer, signed short tile)
1228 {
1229 STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y);
1230 STBTE_ASSERT(layer >= 0 && layer < tm->num_layers);
1231 STBTE_ASSERT(tile >= -1);
1232 //STBTE_ASSERT(tile < 32768);
1233 if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y)
1234 return;
1235 if (layer < 0 || layer >= tm->num_layers || tile < -1)
1236 return;
1237 tm->data[y][x][layer] = tile;
1238 }
1239
stbte__choose_category(stbte_tilemap * tm,int category)1240 static void stbte__choose_category(stbte_tilemap *tm, int category)
1241 {
1242 int i,n=0;
1243 tm->cur_category = category;
1244 for (i=0; i < tm->num_tiles; ++i)
1245 if (tm->tiles[i].category_id == category || category == -1)
1246 ++n;
1247 tm->cur_palette_count = n;
1248 tm->palette_scroll = 0;
1249 }
1250
stbte__strequal(char * p,char * q)1251 static int stbte__strequal(char *p, char *q)
1252 {
1253 while (*p)
1254 if (*p++ != *q++) return 0;
1255 return *q == 0;
1256 }
1257
stbte__compute_tileinfo(stbte_tilemap * tm)1258 static void stbte__compute_tileinfo(stbte_tilemap *tm)
1259 {
1260 int i,j;
1261
1262 tm->num_categories=0;
1263
1264 for (i=0; i < tm->num_tiles; ++i) {
1265 stbte__tileinfo *t = &tm->tiles[i];
1266 // find category
1267 for (j=0; j < tm->num_categories; ++j)
1268 if (stbte__strequal(t->category, tm->categories[j]))
1269 goto found;
1270 tm->categories[j] = t->category;
1271 ++tm->num_categories;
1272 found:
1273 t->category_id = (unsigned short) j;
1274 }
1275
1276 // currently number of categories can never decrease because you
1277 // can't remove tile definitions, but let's get it right anyway
1278 if (tm->cur_category > tm->num_categories) {
1279 tm->cur_category = -1;
1280 }
1281
1282 stbte__choose_category(tm, tm->cur_category);
1283
1284 tm->tileinfo_dirty = 0;
1285 }
1286
stbte__prepare_tileinfo(stbte_tilemap * tm)1287 static void stbte__prepare_tileinfo(stbte_tilemap *tm)
1288 {
1289 if (tm->tileinfo_dirty)
1290 stbte__compute_tileinfo(tm);
1291 }
1292
1293
1294 /////////////////////// undo system ////////////////////////
1295
1296 // the undo system works by storing "commands" into a buffer, and
1297 // then playing back those commands. undo and redo have to store
1298 // the commands in different order.
1299 //
1300 // the commands are:
1301 //
1302 // 1) end_of_undo_record
1303 // -1:short
1304 //
1305 // 2) end_of_redo_record
1306 // -2:short
1307 //
1308 // 3) tile update
1309 // tile_id:short (-1..32767)
1310 // x_coord:short
1311 // y_coord:short
1312 // layer:short (0..31)
1313 //
1314 // 4) property update (also used for links)
1315 // value_hi:short
1316 // value_lo:short
1317 // y_coord:short
1318 // x_coord:short
1319 // property:short (256+prop#)
1320 //
1321 // Since we use a circular buffer, we might overwrite the undo storage.
1322 // To detect this, before playing back commands we scan back and see
1323 // if we see an end_of_undo_record before hitting the relevant boundary,
1324 // it's wholly contained.
1325 //
1326 // When we read back through, we see them in reverse order, so
1327 // we'll see the layer number or property number first
1328 //
1329 // To be clearer about the circular buffer, there are two cases:
1330 // 1. a single record is larger than the whole buffer.
1331 // this is caught because the end_of_undo_record will
1332 // get overwritten.
1333 // 2. multiple records written are larger than the whole
1334 // buffer, so some of them have been overwritten by
1335 // the later ones. this is handled by explicitly tracking
1336 // the undo length; we never try to parse the data that
1337 // got overwritten
1338
1339 // given two points, compute the length between them
1340 #define stbte__wrap(pos) ((pos) & (STBTE__UNDO_BUFFER_COUNT-1))
1341
1342 #define STBTE__undo_record -2
1343 #define STBTE__redo_record -3
1344 #define STBTE__undo_junk -4 // this is written underneath the undo pointer, never used
1345
stbte__write_undo(stbte_tilemap * tm,short value)1346 static void stbte__write_undo(stbte_tilemap *tm, short value)
1347 {
1348 int pos = tm->undo_pos;
1349 tm->undo_buffer[pos] = value;
1350 tm->undo_pos = stbte__wrap(pos+1);
1351 tm->undo_len += (tm->undo_len < STBTE__UNDO_BUFFER_COUNT-2);
1352 tm->redo_len -= (tm->redo_len > 0);
1353 tm->undo_available_valid = 0;
1354 }
1355
stbte__write_redo(stbte_tilemap * tm,short value)1356 static void stbte__write_redo(stbte_tilemap *tm, short value)
1357 {
1358 int pos = tm->undo_pos;
1359 tm->undo_buffer[pos] = value;
1360 tm->undo_pos = stbte__wrap(pos-1);
1361 tm->redo_len += (tm->redo_len < STBTE__UNDO_BUFFER_COUNT-2);
1362 tm->undo_len -= (tm->undo_len > 0);
1363 tm->undo_available_valid = 0;
1364 }
1365
stbte__begin_undo(stbte_tilemap * tm)1366 static void stbte__begin_undo(stbte_tilemap *tm)
1367 {
1368 tm->redo_len = 0;
1369 stbte__write_undo(tm, STBTE__undo_record);
1370 stbte__ui.undoing = 1;
1371 stbte__ui.alert_msg = 0; // clear alert if they start doing something
1372 }
1373
stbte__end_undo(stbte_tilemap * tm)1374 static void stbte__end_undo(stbte_tilemap *tm)
1375 {
1376 if (stbte__ui.undoing) {
1377 // check if anything got written
1378 int pos = stbte__wrap(tm->undo_pos-1);
1379 if (tm->undo_buffer[pos] == STBTE__undo_record) {
1380 // empty undo record, move back
1381 tm->undo_pos = pos;
1382 STBTE_ASSERT(tm->undo_len > 0);
1383 tm->undo_len -= 1;
1384 }
1385 tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk;
1386 // otherwise do nothing
1387
1388 stbte__ui.undoing = 0;
1389 }
1390 }
1391
stbte__undo_record(stbte_tilemap * tm,int x,int y,int i,int v)1392 static void stbte__undo_record(stbte_tilemap *tm, int x, int y, int i, int v)
1393 {
1394 STBTE_ASSERT(stbte__ui.undoing);
1395 if (stbte__ui.undoing) {
1396 stbte__write_undo(tm, v);
1397 stbte__write_undo(tm, x);
1398 stbte__write_undo(tm, y);
1399 stbte__write_undo(tm, i);
1400 }
1401 }
1402
stbte__redo_record(stbte_tilemap * tm,int x,int y,int i,int v)1403 static void stbte__redo_record(stbte_tilemap *tm, int x, int y, int i, int v)
1404 {
1405 stbte__write_redo(tm, v);
1406 stbte__write_redo(tm, x);
1407 stbte__write_redo(tm, y);
1408 stbte__write_redo(tm, i);
1409 }
1410
stbte__extract_float(short s0,short s1)1411 static float stbte__extract_float(short s0, short s1)
1412 {
1413 union { float f; short s[2]; } converter;
1414 converter.s[0] = s0;
1415 converter.s[1] = s1;
1416 return converter.f;
1417 }
1418
stbte__extract_short(float f,int slot)1419 static short stbte__extract_short(float f, int slot)
1420 {
1421 union { float f; short s[2]; } converter;
1422 converter.f = f;
1423 return converter.s[slot];
1424 }
1425
stbte__undo_record_prop(stbte_tilemap * tm,int x,int y,int i,short s0,short s1)1426 static void stbte__undo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1)
1427 {
1428 STBTE_ASSERT(stbte__ui.undoing);
1429 if (stbte__ui.undoing) {
1430 stbte__write_undo(tm, s1);
1431 stbte__write_undo(tm, s0);
1432 stbte__write_undo(tm, x);
1433 stbte__write_undo(tm, y);
1434 stbte__write_undo(tm, 256+i);
1435 }
1436 }
1437
stbte__undo_record_prop_float(stbte_tilemap * tm,int x,int y,int i,float f)1438 static void stbte__undo_record_prop_float(stbte_tilemap *tm, int x, int y, int i, float f)
1439 {
1440 stbte__undo_record_prop(tm, x,y,i, stbte__extract_short(f,0), stbte__extract_short(f,1));
1441 }
1442
stbte__redo_record_prop(stbte_tilemap * tm,int x,int y,int i,short s0,short s1)1443 static void stbte__redo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1)
1444 {
1445 stbte__write_redo(tm, s1);
1446 stbte__write_redo(tm, s0);
1447 stbte__write_redo(tm, x);
1448 stbte__write_redo(tm, y);
1449 stbte__write_redo(tm, 256+i);
1450 }
1451
1452
stbte__undo_find_end(stbte_tilemap * tm)1453 static int stbte__undo_find_end(stbte_tilemap *tm)
1454 {
1455 // first scan through for the end record
1456 int i, pos = stbte__wrap(tm->undo_pos-1);
1457 for (i=0; i < tm->undo_len;) {
1458 STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk);
1459 if (tm->undo_buffer[pos] == STBTE__undo_record)
1460 break;
1461 if (tm->undo_buffer[pos] >= 255)
1462 pos = stbte__wrap(pos-5), i += 5;
1463 else
1464 pos = stbte__wrap(pos-4), i += 4;
1465 }
1466 if (i >= tm->undo_len)
1467 return -1;
1468 return pos;
1469 }
1470
stbte__undo(stbte_tilemap * tm)1471 static void stbte__undo(stbte_tilemap *tm)
1472 {
1473 int i, pos, endpos;
1474 endpos = stbte__undo_find_end(tm);
1475 if (endpos < 0)
1476 return;
1477
1478 // we found a complete undo record
1479 pos = stbte__wrap(tm->undo_pos-1);
1480
1481 // start a redo record
1482 stbte__write_redo(tm, STBTE__redo_record);
1483
1484 // so now go back through undo and apply in reverse
1485 // order, and copy it to redo
1486 for (i=0; endpos != pos; i += 4) {
1487 int x,y,n,v;
1488 // get the undo entry
1489 n = tm->undo_buffer[pos];
1490 y = tm->undo_buffer[stbte__wrap(pos-1)];
1491 x = tm->undo_buffer[stbte__wrap(pos-2)];
1492 v = tm->undo_buffer[stbte__wrap(pos-3)];
1493 if (n >= 255) {
1494 short s0=0,s1=0;
1495 int v2 = tm->undo_buffer[stbte__wrap(pos-4)];
1496 pos = stbte__wrap(pos-5);
1497 if (n > 255) {
1498 float vf = stbte__extract_float(v, v2);
1499 s0 = stbte__extract_short(tm->props[y][x][n-256], 0);
1500 s1 = stbte__extract_short(tm->props[y][x][n-256], 1);
1501 tm->props[y][x][n-256] = vf;
1502 } else {
1503 #ifdef STBTE_ALLOW_LINK
1504 s0 = tm->link[y][x].x;
1505 s1 = tm->link[y][x].y;
1506 stbte__set_link(tm, x,y, v, v2, STBTE__undo_none);
1507 #endif
1508 }
1509 // write the redo entry
1510 stbte__redo_record_prop(tm, x, y, n-256, s0,s1);
1511 // apply the undo entry
1512 } else {
1513 pos = stbte__wrap(pos-4);
1514 // write the redo entry
1515 stbte__redo_record(tm, x, y, n, tm->data[y][x][n]);
1516 // apply the undo entry
1517 tm->data[y][x][n] = (short) v;
1518 }
1519 }
1520 // overwrite undo record with junk
1521 tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk;
1522 }
1523
stbte__redo_find_end(stbte_tilemap * tm)1524 static int stbte__redo_find_end(stbte_tilemap *tm)
1525 {
1526 // first scan through for the end record
1527 int i, pos = stbte__wrap(tm->undo_pos+1);
1528 for (i=0; i < tm->redo_len;) {
1529 STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk);
1530 if (tm->undo_buffer[pos] == STBTE__redo_record)
1531 break;
1532 if (tm->undo_buffer[pos] >= 255)
1533 pos = stbte__wrap(pos+5), i += 5;
1534 else
1535 pos = stbte__wrap(pos+4), i += 4;
1536 }
1537 if (i >= tm->redo_len)
1538 return -1; // this should only ever happen if redo buffer is empty
1539 return pos;
1540 }
1541
stbte__redo(stbte_tilemap * tm)1542 static void stbte__redo(stbte_tilemap *tm)
1543 {
1544 // first scan through for the end record
1545 int i, pos, endpos;
1546 endpos = stbte__redo_find_end(tm);
1547 if (endpos < 0)
1548 return;
1549
1550 // we found a complete redo record
1551 pos = stbte__wrap(tm->undo_pos+1);
1552
1553 // start an undo record
1554 stbte__write_undo(tm, STBTE__undo_record);
1555
1556 for (i=0; pos != endpos; i += 4) {
1557 int x,y,n,v;
1558 n = tm->undo_buffer[pos];
1559 y = tm->undo_buffer[stbte__wrap(pos+1)];
1560 x = tm->undo_buffer[stbte__wrap(pos+2)];
1561 v = tm->undo_buffer[stbte__wrap(pos+3)];
1562 if (n >= 255) {
1563 int v2 = tm->undo_buffer[stbte__wrap(pos+4)];
1564 short s0=0,s1=0;
1565 pos = stbte__wrap(pos+5);
1566 if (n > 255) {
1567 float vf = stbte__extract_float(v, v2);
1568 s0 = stbte__extract_short(tm->props[y][x][n-256],0);
1569 s1 = stbte__extract_short(tm->props[y][x][n-256],1);
1570 tm->props[y][x][n-256] = vf;
1571 } else {
1572 #ifdef STBTE_ALLOW_LINK
1573 s0 = tm->link[y][x].x;
1574 s1 = tm->link[y][x].y;
1575 stbte__set_link(tm, x,y,v,v2, STBTE__undo_none);
1576 #endif
1577 }
1578 // don't use stbte__undo_record_prop because it's guarded
1579 stbte__write_undo(tm, s1);
1580 stbte__write_undo(tm, s0);
1581 stbte__write_undo(tm, x);
1582 stbte__write_undo(tm, y);
1583 stbte__write_undo(tm, n);
1584 } else {
1585 pos = stbte__wrap(pos+4);
1586 // don't use stbte__undo_record because it's guarded
1587 stbte__write_undo(tm, tm->data[y][x][n]);
1588 stbte__write_undo(tm, x);
1589 stbte__write_undo(tm, y);
1590 stbte__write_undo(tm, n);
1591 tm->data[y][x][n] = (short) v;
1592 }
1593 }
1594 tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk;
1595 }
1596
1597 // because detecting that undo is available
stbte__recompute_undo_available(stbte_tilemap * tm)1598 static void stbte__recompute_undo_available(stbte_tilemap *tm)
1599 {
1600 tm->undo_available = (stbte__undo_find_end(tm) >= 0);
1601 tm->redo_available = (stbte__redo_find_end(tm) >= 0);
1602 }
1603
stbte__undo_available(stbte_tilemap * tm)1604 static int stbte__undo_available(stbte_tilemap *tm)
1605 {
1606 if (!tm->undo_available_valid)
1607 stbte__recompute_undo_available(tm);
1608 return tm->undo_available;
1609 }
1610
stbte__redo_available(stbte_tilemap * tm)1611 static int stbte__redo_available(stbte_tilemap *tm)
1612 {
1613 if (!tm->undo_available_valid)
1614 stbte__recompute_undo_available(tm);
1615 return tm->redo_available;
1616 }
1617
1618 ///////////////////////////////////////////////////////////////////////////////////////////////////
1619
1620 #ifdef STBTE_ALLOW_LINK
stbte__set_link(stbte_tilemap * tm,int src_x,int src_y,int dest_x,int dest_y,int undo_mode)1621 static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode)
1622 {
1623 stbte__link *a;
1624 STBTE_ASSERT(src_x >= 0 && src_x < STBTE_MAX_TILEMAP_X && src_y >= 0 && src_y < STBTE_MAX_TILEMAP_Y);
1625 a = &tm->link[src_y][src_x];
1626 // check if it's a do nothing
1627 if (a->x == dest_x && a->y == dest_y)
1628 return;
1629 if (undo_mode != STBTE__undo_none ) {
1630 if (undo_mode == STBTE__undo_block) stbte__begin_undo(tm);
1631 stbte__undo_record_prop(tm, src_x, src_y, -1, a->x, a->y);
1632 if (undo_mode == STBTE__undo_block) stbte__end_undo(tm);
1633 }
1634 // check if there's an existing link
1635 if (a->x >= 0) {
1636 // decrement existing link refcount
1637 STBTE_ASSERT(tm->linkcount[a->y][a->x] > 0);
1638 --tm->linkcount[a->y][a->x];
1639 }
1640 // increment new dest
1641 if (dest_x >= 0) {
1642 ++tm->linkcount[dest_y][dest_x];
1643 }
1644 a->x = dest_x;
1645 a->y = dest_y;
1646 }
1647 #endif
1648
1649
stbte__draw_rect(int x0,int y0,int x1,int y1,unsigned int color)1650 static void stbte__draw_rect(int x0, int y0, int x1, int y1, unsigned int color)
1651 {
1652 STBTE_DRAW_RECT(x0,y0,x1,y1, color);
1653 }
1654
1655 #ifdef STBTE_ALLOW_LINK
stbte__draw_line(int x0,int y0,int x1,int y1,unsigned int color)1656 static void stbte__draw_line(int x0, int y0, int x1, int y1, unsigned int color)
1657 {
1658 int temp;
1659 if (x1 < x0) temp=x0,x0=x1,x1=temp;
1660 if (y1 < y0) temp=y0,y0=y1,y1=temp;
1661 stbte__draw_rect(x0,y0,x1+1,y1+1,color);
1662 }
1663
stbte__draw_link(int x0,int y0,int x1,int y1,unsigned int color)1664 static void stbte__draw_link(int x0, int y0, int x1, int y1, unsigned int color)
1665 {
1666 stbte__draw_line(x0,y0,x0,y1, color);
1667 stbte__draw_line(x0,y1,x1,y1, color);
1668 }
1669 #endif
1670
stbte__draw_frame(int x0,int y0,int x1,int y1,unsigned int color)1671 static void stbte__draw_frame(int x0, int y0, int x1, int y1, unsigned int color)
1672 {
1673 stbte__draw_rect(x0,y0,x1-1,y0+1,color);
1674 stbte__draw_rect(x1-1,y0,x1,y1-1,color);
1675 stbte__draw_rect(x0+1,y1-1,x1,y1,color);
1676 stbte__draw_rect(x0,y0+1,x0+1,y1,color);
1677 }
1678
stbte__get_char_width(int ch)1679 static int stbte__get_char_width(int ch)
1680 {
1681 return stbte__fontdata[ch-16];
1682 }
1683
stbte__get_char_bitmap(int ch)1684 static short *stbte__get_char_bitmap(int ch)
1685 {
1686 return stbte__fontdata + stbte__font_offset[ch-16];
1687 }
1688
stbte__draw_bitmask_as_columns(int x,int y,short bitmask,int color)1689 static void stbte__draw_bitmask_as_columns(int x, int y, short bitmask, int color)
1690 {
1691 int start_i = -1, i=0;
1692 while (bitmask) {
1693 if (bitmask & (1<<i)) {
1694 if (start_i < 0)
1695 start_i = i;
1696 } else if (start_i >= 0) {
1697 stbte__draw_rect(x, y+start_i, x+1, y+i, color);
1698 start_i = -1;
1699 bitmask &= ~((1<<i)-1); // clear all the old bits; we don't clear them as we go to save code
1700 }
1701 ++i;
1702 }
1703 }
1704
stbte__draw_bitmap(int x,int y,int w,short * bitmap,int color)1705 static void stbte__draw_bitmap(int x, int y, int w, short *bitmap, int color)
1706 {
1707 int i;
1708 for (i=0; i < w; ++i)
1709 stbte__draw_bitmask_as_columns(x+i, y, *bitmap++, color);
1710 }
1711
stbte__draw_text_core(int x,int y,const char * str,int w,int color,int digitspace)1712 static void stbte__draw_text_core(int x, int y, const char *str, int w, int color, int digitspace)
1713 {
1714 int x_end = x+w;
1715 while (*str) {
1716 int c = *str++;
1717 int cw = stbte__get_char_width(c);
1718 if (x + cw > x_end)
1719 break;
1720 stbte__draw_bitmap(x, y, cw, stbte__get_char_bitmap(c), color);
1721 if (digitspace && c == ' ')
1722 cw = stbte__get_char_width('0');
1723 x += cw+1;
1724 }
1725 }
1726
stbte__draw_text(int x,int y,const char * str,int w,int color)1727 static void stbte__draw_text(int x, int y, const char *str, int w, int color)
1728 {
1729 stbte__draw_text_core(x,y,str,w,color,0);
1730 }
1731
stbte__text_width(const char * str)1732 static int stbte__text_width(const char *str)
1733 {
1734 int x = 0;
1735 while (*str) {
1736 int c = *str++;
1737 int cw = stbte__get_char_width(c);
1738 x += cw+1;
1739 }
1740 return x;
1741 }
1742
stbte__draw_frame_delayed(int x0,int y0,int x1,int y1,int color)1743 static void stbte__draw_frame_delayed(int x0, int y0, int x1, int y1, int color)
1744 {
1745 if (stbte__ui.delaycount < STBTE__MAX_DELAYRECT) {
1746 stbte__colorrect r = { x0,y0,x1,y1,color };
1747 stbte__ui.delayrect[stbte__ui.delaycount++] = r;
1748 }
1749 }
1750
stbte__flush_delay(void)1751 static void stbte__flush_delay(void)
1752 {
1753 stbte__colorrect *r;
1754 int i;
1755 r = stbte__ui.delayrect;
1756 for (i=0; i < stbte__ui.delaycount; ++i,++r)
1757 stbte__draw_frame(r->x0,r->y0,r->x1,r->y1,r->color);
1758 stbte__ui.delaycount = 0;
1759 }
1760
stbte__activate(int id)1761 static void stbte__activate(int id)
1762 {
1763 stbte__ui.active_id = id;
1764 stbte__ui.active_event = stbte__ui.event;
1765 stbte__ui.accum_x = 0;
1766 stbte__ui.accum_y = 0;
1767 }
1768
stbte__hittest(int x0,int y0,int x1,int y1,int id)1769 static int stbte__hittest(int x0, int y0, int x1, int y1, int id)
1770 {
1771 int over = stbte__ui.mx >= x0 && stbte__ui.my >= y0
1772 && stbte__ui.mx < x1 && stbte__ui.my < y1;
1773
1774 if (over && stbte__ui.event >= STBTE__tick)
1775 stbte__ui.next_hot_id = id;
1776
1777 return over;
1778 }
1779
stbte__button_core(int id)1780 static int stbte__button_core(int id)
1781 {
1782 switch (stbte__ui.event) {
1783 case STBTE__leftdown:
1784 if (stbte__ui.hot_id == id && STBTE__INACTIVE())
1785 stbte__activate(id);
1786 break;
1787 case STBTE__leftup:
1788 if (stbte__ui.active_id == id && STBTE__IS_HOT(id)) {
1789 stbte__activate(0);
1790 return 1;
1791 }
1792 break;
1793 case STBTE__rightdown:
1794 if (stbte__ui.hot_id == id && STBTE__INACTIVE())
1795 stbte__activate(id);
1796 break;
1797 case STBTE__rightup:
1798 if (stbte__ui.active_id == id && STBTE__IS_HOT(id)) {
1799 stbte__activate(0);
1800 return -1;
1801 }
1802 break;
1803 }
1804 return 0;
1805 }
1806
stbte__draw_box(int x0,int y0,int x1,int y1,int colormode,int colorindex)1807 static void stbte__draw_box(int x0, int y0, int x1, int y1, int colormode, int colorindex)
1808 {
1809 stbte__draw_rect (x0,y0,x1,y1, stbte__color_table[colormode][STBTE__base ][colorindex]);
1810 stbte__draw_frame(x0,y0,x1,y1, stbte__color_table[colormode][STBTE__outline][colorindex]);
1811 }
1812
stbte__draw_textbox(int x0,int y0,int x1,int y1,char * text,int xoff,int yoff,int colormode,int colorindex)1813 static void stbte__draw_textbox(int x0, int y0, int x1, int y1, char *text, int xoff, int yoff, int colormode, int colorindex)
1814 {
1815 stbte__draw_box(x0,y0,x1,y1,colormode,colorindex);
1816 stbte__draw_text(x0+xoff,y0+yoff, text, x1-x0-xoff-1, stbte__color_table[colormode][STBTE__text][colorindex]);
1817 }
1818
stbte__button(int colormode,const char * label,int x,int y,int textoff,int width,int id,int toggled,int disabled)1819 static int stbte__button(int colormode, const char *label, int x, int y, int textoff, int width, int id, int toggled, int disabled)
1820 {
1821 int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT;
1822 int s = STBTE__BUTTON_INTERNAL_SPACING;
1823
1824 if (stbte__ui.event == STBTE__paint)
1825 stbte__draw_textbox(x0,y0,x1,y1, (char*) label,s+textoff,s, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled));
1826 if (disabled)
1827 return 0;
1828 return (stbte__button_core(id) == 1);
1829 }
1830
stbte__button_icon(int colormode,char ch,int x,int y,int width,int id,int toggled,int disabled)1831 static int stbte__button_icon(int colormode, char ch, int x, int y, int width, int id, int toggled, int disabled)
1832 {
1833 int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT;
1834 int s = STBTE__BUTTON_INTERNAL_SPACING;
1835
1836 if (stbte__ui.event == STBTE__paint) {
1837 char label[2] = { ch, 0 };
1838 int pad = (9 - stbte__get_char_width(ch))/2;
1839 stbte__draw_textbox(x0,y0,x1,y1, label,s+pad,s, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled));
1840 }
1841 if (disabled)
1842 return 0;
1843 return (stbte__button_core(id) == 1);
1844 }
1845
stbte__minibutton(int colormode,int x,int y,int ch,int id)1846 static int stbte__minibutton(int colormode, int x, int y, int ch, int id)
1847 {
1848 int x0 = x, y0 = y, x1 = x+8, y1 = y+7;
1849 if (stbte__ui.event == STBTE__paint) {
1850 char str[2] = { (char)ch, 0 };
1851 stbte__draw_textbox(x0,y0,x1,y1, str,1,0,colormode, STBTE__INDEX_FOR_ID(id,0,0));
1852 }
1853 return stbte__button_core(id);
1854 }
1855
stbte__layerbutton(int x,int y,int ch,int id,int toggled,int disabled,int colormode)1856 static int stbte__layerbutton(int x, int y, int ch, int id, int toggled, int disabled, int colormode)
1857 {
1858 int x0 = x, y0 = y, x1 = x+10, y1 = y+11;
1859 if (stbte__ui.event == STBTE__paint) {
1860 char str[2] = { (char)ch, 0 };
1861 int off = (9-stbte__get_char_width(ch))/2;
1862 stbte__draw_textbox(x0,y0,x1,y1, str, off+1,2, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled));
1863 }
1864 if (disabled)
1865 return 0;
1866 return stbte__button_core(id);
1867 }
1868
stbte__microbutton(int x,int y,int size,int id,int colormode)1869 static int stbte__microbutton(int x, int y, int size, int id, int colormode)
1870 {
1871 int x0 = x, y0 = y, x1 = x+size, y1 = y+size;
1872 if (stbte__ui.event == STBTE__paint) {
1873 stbte__draw_box(x0,y0,x1,y1, colormode, STBTE__INDEX_FOR_ID(id,0,0));
1874 }
1875 return stbte__button_core(id);
1876 }
1877
stbte__microbutton_dragger(int x,int y,int size,int id,int * pos)1878 static int stbte__microbutton_dragger(int x, int y, int size, int id, int *pos)
1879 {
1880 int x0 = x, y0 = y, x1 = x+size, y1 = y+size;
1881 switch (stbte__ui.event) {
1882 case STBTE__paint:
1883 stbte__draw_box(x0,y0,x1,y1, STBTE__cexpander, STBTE__INDEX_FOR_ID(id,0,0));
1884 break;
1885 case STBTE__leftdown:
1886 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
1887 stbte__activate(id);
1888 stbte__ui.sx = stbte__ui.mx - *pos;
1889 }
1890 break;
1891 case STBTE__mousemove:
1892 if (STBTE__IS_ACTIVE(id) && stbte__ui.active_event == STBTE__leftdown) {
1893 *pos = stbte__ui.mx - stbte__ui.sx;
1894 }
1895 break;
1896 case STBTE__leftup:
1897 if (STBTE__IS_ACTIVE(id))
1898 stbte__activate(0);
1899 break;
1900 default:
1901 return stbte__button_core(id);
1902 }
1903 return 0;
1904 }
1905
stbte__category_button(const char * label,int x,int y,int width,int id,int toggled)1906 static int stbte__category_button(const char *label, int x, int y, int width, int id, int toggled)
1907 {
1908 int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT;
1909 int s = STBTE__BUTTON_INTERNAL_SPACING;
1910
1911 if (stbte__ui.event == STBTE__paint)
1912 stbte__draw_textbox(x0,y0,x1,y1, (char*) label, s,s, STBTE__ccategory_button, STBTE__INDEX_FOR_ID(id,0,toggled));
1913
1914 return (stbte__button_core(id) == 1);
1915 }
1916
1917 enum
1918 {
1919 STBTE__none,
1920 STBTE__begin,
1921 STBTE__end,
1922 STBTE__change,
1923 };
1924
1925 // returns -1 if value changes, 1 at end of drag
stbte__slider(int x0,int w,int y,int range,int * value,int id)1926 static int stbte__slider(int x0, int w, int y, int range, int *value, int id)
1927 {
1928 int x1 = x0+w;
1929 int pos = *value * w / (range+1);
1930 int event_mouse_move = STBTE__change;
1931 switch (stbte__ui.event) {
1932 case STBTE__paint:
1933 stbte__draw_rect(x0,y,x1,y+1, 0x808080);
1934 stbte__draw_rect(x0+pos-1,y-1,x0+pos+2,y+2, 0xffffff);
1935 break;
1936 case STBTE__leftdown:
1937 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
1938 stbte__activate(id);
1939 event_mouse_move = STBTE__begin;
1940 }
1941 // fall through
1942 case STBTE__mousemove:
1943 if (STBTE__IS_ACTIVE(id)) {
1944 int v = (stbte__ui.mx-x0)*(range+1)/w;
1945 if (v < 0) v = 0; else if (v > range) v = range;
1946 *value = v;
1947 return event_mouse_move;
1948 }
1949 break;
1950 case STBTE__leftup:
1951 if (STBTE__IS_ACTIVE(id)) {
1952 stbte__activate(0);
1953 return STBTE__end;
1954 }
1955 break;
1956 }
1957 return STBTE__none;
1958 }
1959
1960 #if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__)
1961 #define stbte__sprintf sprintf_s
1962 #define stbte__sizeof(s) , sizeof(s)
1963 #else
1964 #define stbte__sprintf sprintf
1965 #define stbte__sizeof(s)
1966 #endif
1967
stbte__float_control(int x0,int y0,int w,float minv,float maxv,float scale,const char * fmt,float * value,int colormode,int id)1968 static int stbte__float_control(int x0, int y0, int w, float minv, float maxv, float scale, const char *fmt, float *value, int colormode, int id)
1969 {
1970 int x1 = x0+w;
1971 int y1 = y0+11;
1972 switch (stbte__ui.event) {
1973 case STBTE__paint: {
1974 char text[32];
1975 stbte__sprintf(text stbte__sizeof(text), fmt ? fmt : "%6.2f", *value);
1976 stbte__draw_textbox(x0,y0,x1,y1, text, 1,2, colormode, STBTE__INDEX_FOR_ID(id,0,0));
1977 break;
1978 }
1979 case STBTE__leftdown:
1980 case STBTE__rightdown:
1981 if (STBTE__IS_HOT(id) && STBTE__INACTIVE())
1982 stbte__activate(id);
1983 return STBTE__begin;
1984 break;
1985 case STBTE__leftup:
1986 case STBTE__rightup:
1987 if (STBTE__IS_ACTIVE(id)) {
1988 stbte__activate(0);
1989 return STBTE__end;
1990 }
1991 break;
1992 case STBTE__mousemove:
1993 if (STBTE__IS_ACTIVE(id)) {
1994 float v = *value, delta;
1995 int ax = stbte__ui.accum_x/STBTE_FLOAT_CONTROL_GRANULARITY;
1996 int ay = stbte__ui.accum_y/STBTE_FLOAT_CONTROL_GRANULARITY;
1997 stbte__ui.accum_x -= ax*STBTE_FLOAT_CONTROL_GRANULARITY;
1998 stbte__ui.accum_y -= ay*STBTE_FLOAT_CONTROL_GRANULARITY;
1999 if (stbte__ui.shift) {
2000 if (stbte__ui.active_event == STBTE__leftdown)
2001 delta = ax * 16.0f + ay;
2002 else
2003 delta = ax / 16.0f + ay / 256.0f;
2004 } else {
2005 if (stbte__ui.active_event == STBTE__leftdown)
2006 delta = ax*10.0f + ay;
2007 else
2008 delta = ax * 0.1f + ay * 0.01f;
2009 }
2010 v += delta * scale;
2011 if (v < minv) v = minv;
2012 if (v > maxv) v = maxv;
2013 *value = v;
2014 return STBTE__change;
2015 }
2016 break;
2017 }
2018 return STBTE__none;
2019 }
2020
stbte__scrollbar(int x,int y0,int y1,int * val,int v0,int v1,int num_vis,int id)2021 static void stbte__scrollbar(int x, int y0, int y1, int *val, int v0, int v1, int num_vis, int id)
2022 {
2023 int over;
2024 int thumbpos;
2025 if (v1 - v0 <= num_vis)
2026 return;
2027
2028 // generate thumbpos from numvis
2029 thumbpos = y0+2 + (y1-y0-4) * *val / (v1 - v0 - num_vis);
2030 if (thumbpos < y0) thumbpos = y0;
2031 if (thumbpos >= y1) thumbpos = y1;
2032 over = stbte__hittest(x-1,y0,x+2,y1,id);
2033 switch (stbte__ui.event) {
2034 case STBTE__paint:
2035 stbte__draw_rect(x,y0,x+1,y1, stbte__color_table[STBTE__cscrollbar][STBTE__text][STBTE__idle]);
2036 stbte__draw_box(x-1,thumbpos-3,x+2,thumbpos+4, STBTE__cscrollbar, STBTE__INDEX_FOR_ID(id,0,0));
2037 break;
2038 case STBTE__leftdown:
2039 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
2040 // check if it's over the thumb
2041 stbte__activate(id);
2042 *val = ((stbte__ui.my-y0) * (v1 - v0 - num_vis) + (y1-y0)/2)/ (y1-y0);
2043 }
2044 break;
2045 case STBTE__mousemove:
2046 if (STBTE__IS_ACTIVE(id) && stbte__ui.mx >= x-15 && stbte__ui.mx <= x+15)
2047 *val = ((stbte__ui.my-y0) * (v1 - v0 - num_vis) + (y1-y0)/2)/ (y1-y0);
2048 break;
2049 case STBTE__leftup:
2050 if (STBTE__IS_ACTIVE(id))
2051 stbte__activate(0);
2052 break;
2053
2054 }
2055
2056 if (*val >= v1-num_vis)
2057 *val = v1-num_vis;
2058 if (*val <= v0)
2059 *val = v0;
2060 }
2061
2062
stbte__compute_digits(stbte_tilemap * tm)2063 static void stbte__compute_digits(stbte_tilemap *tm)
2064 {
2065 if (tm->max_x >= 1000 || tm->max_y >= 1000)
2066 tm->digits = 4;
2067 else if (tm->max_x >= 100 || tm->max_y >= 100)
2068 tm->digits = 3;
2069 else
2070 tm->digits = 2;
2071 }
2072
stbte__is_single_selection(void)2073 static int stbte__is_single_selection(void)
2074 {
2075 return stbte__ui.has_selection
2076 && stbte__ui.select_x0 == stbte__ui.select_x1
2077 && stbte__ui.select_y0 == stbte__ui.select_y1;
2078 }
2079
2080 typedef struct
2081 {
2082 int width, height;
2083 int x,y;
2084 int active;
2085 float retracted;
2086 } stbte__region_t;
2087
2088 static stbte__region_t stbte__region[4];
2089
2090 #define STBTE__TOOLBAR_ICON_SIZE (9+2*2)
2091 #define STBTE__TOOLBAR_PASTE_SIZE (34+2*2)
2092
2093 // This routine computes where every panel goes onscreen: computes
2094 // a minimum width for each side based on which panels are on that
2095 // side, and accounts for width-dependent layout of certain panels.
stbte__compute_panel_locations(stbte_tilemap * tm)2096 static void stbte__compute_panel_locations(stbte_tilemap *tm)
2097 {
2098 int i, limit, w, k;
2099 int window_width = stbte__ui.x1 - stbte__ui.x0;
2100 int window_height = stbte__ui.y1 - stbte__ui.y0;
2101 int min_width[STBTE__num_panel]={0,0,0,0,0,0,0};
2102 int height[STBTE__num_panel]={0,0,0,0,0,0,0};
2103 int panel_active[STBTE__num_panel]={1,0,1,1,1,1,1};
2104 int vpos[4] = { 0,0,0,0 };
2105 stbte__panel *p = stbte__ui.panel;
2106 stbte__panel *pt = &p[STBTE__panel_toolbar];
2107 #ifdef STBTE__NO_PROPS
2108 int props = 0;
2109 #else
2110 int props = 1;
2111 #endif
2112
2113 for (i=0; i < 4; ++i) {
2114 stbte__region[i].active = 0;
2115 stbte__region[i].width = 0;
2116 stbte__region[i].height = 0;
2117 }
2118
2119 // compute number of digits needs for info panel
2120 stbte__compute_digits(tm);
2121
2122 // determine which panels are active
2123 panel_active[STBTE__panel_categories] = tm->num_categories != 0;
2124 panel_active[STBTE__panel_layers ] = tm->num_layers > 1;
2125 #ifdef STBTE__COLORPICKER
2126 panel_active[STBTE__panel_colorpick ] = 1;
2127 #endif
2128
2129 panel_active[STBTE__panel_props ] = props && stbte__is_single_selection();
2130
2131 // compute minimum widths for each panel (assuming they're on sides not top)
2132 min_width[STBTE__panel_info ] = 8 + 11 + 7*tm->digits+17+7; // estimate min width of "w:0000"
2133 min_width[STBTE__panel_colorpick ] = 120;
2134 min_width[STBTE__panel_tiles ] = 4 + tm->palette_spacing_x + 5; // 5 for scrollbar
2135 min_width[STBTE__panel_categories] = 4 + 42 + 5; // 42 is enough to show ~7 chars; 5 for scrollbar
2136 min_width[STBTE__panel_layers ] = 4 + 54 + 30*tm->has_layer_names; // 2 digits plus 3 buttons plus scrollbar
2137 min_width[STBTE__panel_toolbar ] = 4 + STBTE__TOOLBAR_PASTE_SIZE; // wide enough for 'Paste' button
2138 min_width[STBTE__panel_props ] = 80; // narrowest info panel
2139
2140 // compute minimum widths for left & right panels based on the above
2141 stbte__region[0].width = stbte__ui.left_width;
2142 stbte__region[1].width = stbte__ui.right_width;
2143
2144 for (i=0; i < STBTE__num_panel; ++i) {
2145 if (panel_active[i]) {
2146 int side = stbte__ui.panel[i].side;
2147 if (min_width[i] > stbte__region[side].width)
2148 stbte__region[side].width = min_width[i];
2149 stbte__region[side].active = 1;
2150 }
2151 }
2152
2153 // now compute the heights of each panel
2154
2155 // if toolbar at top, compute its size & push the left and right start points down
2156 if (stbte__region[STBTE__side_top].active) {
2157 int height = STBTE__TOOLBAR_ICON_SIZE+2;
2158 pt->x0 = stbte__ui.x0;
2159 pt->y0 = stbte__ui.y0;
2160 pt->width = window_width;
2161 pt->height = height;
2162 vpos[STBTE__side_left] = vpos[STBTE__side_right] = height;
2163 } else {
2164 int num_rows = STBTE__num_tool * ((stbte__region[pt->side].width-4)/STBTE__TOOLBAR_ICON_SIZE);
2165 height[STBTE__panel_toolbar] = num_rows*13 + 3*15 + 4; // 3*15 for cut/copy/paste, which are stacked vertically
2166 }
2167
2168 for (i=0; i < 4; ++i)
2169 stbte__region[i].y = stbte__ui.y0 + vpos[i];
2170
2171 for (i=0; i < 2; ++i) {
2172 int anim = (int) (stbte__region[i].width * stbte__region[i].retracted);
2173 stbte__region[i].x = (i == STBTE__side_left) ? stbte__ui.x0 - anim : stbte__ui.x1 - stbte__region[i].width + anim;
2174 }
2175
2176 // color picker
2177 height[STBTE__panel_colorpick] = 300;
2178
2179 // info panel
2180 w = stbte__region[p[STBTE__panel_info].side].width;
2181 p[STBTE__panel_info].mode = (w >= 8 + (11+7*tm->digits+17)*2 + 4);
2182 if (p[STBTE__panel_info].mode)
2183 height[STBTE__panel_info] = 5 + 11*2 + 2 + tm->palette_spacing_y;
2184 else
2185 height[STBTE__panel_info] = 5 + 11*4 + 2 + tm->palette_spacing_y;
2186
2187 // layers
2188 limit = 6 + stbte__ui.panel[STBTE__panel_layers].delta_height;
2189 height[STBTE__panel_layers] = (tm->num_layers > limit ? limit : tm->num_layers)*15 + 7 + (tm->has_layer_names ? 0 : 11) + props*13;
2190
2191 // categories
2192 limit = 6 + stbte__ui.panel[STBTE__panel_categories].delta_height;
2193 height[STBTE__panel_categories] = (tm->num_categories+1 > limit ? limit : tm->num_categories+1)*11 + 14;
2194 if (stbte__ui.panel[STBTE__panel_categories].side == stbte__ui.panel[STBTE__panel_categories].side)
2195 height[STBTE__panel_categories] -= 4;
2196
2197 // palette
2198 k = (stbte__region[p[STBTE__panel_tiles].side].width - 8) / tm->palette_spacing_x;
2199 if (k == 0) k = 1;
2200 height[STBTE__panel_tiles] = ((tm->num_tiles+k-1)/k) * tm->palette_spacing_y + 8;
2201
2202 // properties panel
2203 height[STBTE__panel_props] = 9 + STBTE_MAX_PROPERTIES*14;
2204
2205 // now compute the locations of all the panels
2206 for (i=0; i < STBTE__num_panel; ++i) {
2207 if (panel_active[i]) {
2208 int side = p[i].side;
2209 if (side == STBTE__side_left || side == STBTE__side_right) {
2210 p[i].width = stbte__region[side].width;
2211 p[i].x0 = stbte__region[side].x;
2212 p[i].y0 = stbte__ui.y0 + vpos[side];
2213 p[i].height = height[i];
2214 vpos[side] += height[i];
2215 if (vpos[side] > window_height) {
2216 vpos[side] = window_height;
2217 p[i].height = stbte__ui.y1 - p[i].y0;
2218 }
2219 } else {
2220 ; // it's at top, it's already been explicitly set up earlier
2221 }
2222 } else {
2223 // inactive panel
2224 p[i].height = 0;
2225 p[i].width = 0;
2226 p[i].x0 = stbte__ui.x1;
2227 p[i].y0 = stbte__ui.y1;
2228 }
2229 }
2230 }
2231
2232 // unique identifiers for imgui
2233 enum
2234 {
2235 STBTE__map=1,
2236 STBTE__region,
2237 STBTE__panel, // panel background to hide map, and misc controls
2238 STBTE__info, // info data
2239 STBTE__toolbarA, STBTE__toolbarB, // toolbar buttons: param is tool number
2240 STBTE__palette, // palette selectors: param is tile index
2241 STBTE__categories, // category selectors: param is category index
2242 STBTE__layer, //
2243 STBTE__solo, STBTE__hide, STBTE__lock, // layer controls: param is layer
2244 STBTE__scrollbar, // param is panel ID
2245 STBTE__panel_mover, // p1 is panel ID, p2 is destination side
2246 STBTE__panel_sizer, // param panel ID
2247 STBTE__scrollbar_id,
2248 STBTE__colorpick_id,
2249 STBTE__prop_flag,
2250 STBTE__prop_float,
2251 STBTE__prop_int,
2252 };
2253
2254 // id is: [ 24-bit data : 7-bit identifier ]
2255 // map id is: [ 12-bit y : 12 bit x : 7-bit identifier ]
2256
2257 #define STBTE__ID(n,p) ((n) + ((p)<<7))
2258 #define STBTE__ID2(n,p,q) STBTE__ID(n, ((p)<<12)+(q) )
2259 #define STBTE__IDMAP(x,y) STBTE__ID2(STBTE__map, x,y)
2260
stbte__activate_map(int x,int y)2261 static void stbte__activate_map(int x, int y)
2262 {
2263 stbte__ui.active_id = STBTE__IDMAP(x,y);
2264 stbte__ui.active_event = stbte__ui.event;
2265 stbte__ui.sx = x;
2266 stbte__ui.sy = y;
2267 }
2268
stbte__alert(const char * msg)2269 static void stbte__alert(const char *msg)
2270 {
2271 stbte__ui.alert_msg = msg;
2272 stbte__ui.alert_timer = 3;
2273 }
2274
2275 #define STBTE__BG(tm,layer) ((layer) == 0 ? (tm)->background_tile : STBTE__NO_TILE)
2276
2277
2278
stbte__brush_predict(stbte_tilemap * tm,short result[])2279 static void stbte__brush_predict(stbte_tilemap *tm, short result[])
2280 {
2281 stbte__tileinfo *ti;
2282 int i;
2283
2284 if (tm->cur_tile < 0) return;
2285
2286 ti = &tm->tiles[tm->cur_tile];
2287
2288 // find lowest legit layer to paint it on, and put it there
2289 for (i=0; i < tm->num_layers; ++i) {
2290 // check if object is allowed on layer
2291 if (!(ti->layermask & (1 << i)))
2292 continue;
2293
2294 if (i != tm->solo_layer) {
2295 // if there's a selected layer, can only paint on that
2296 if (tm->cur_layer >= 0 && i != tm->cur_layer)
2297 continue;
2298
2299 // if the layer is hidden, we can't see it
2300 if (tm->layerinfo[i].hidden)
2301 continue;
2302
2303 // if the layer is locked, we can't write to it
2304 if (tm->layerinfo[i].locked == STBTE__locked)
2305 continue;
2306
2307 // if the layer is non-empty and protected, can't write to it
2308 if (tm->layerinfo[i].locked == STBTE__protected && result[i] != STBTE__BG(tm,i))
2309 continue;
2310 }
2311
2312 result[i] = ti->id;
2313 return;
2314 }
2315 }
2316
stbte__brush(stbte_tilemap * tm,int x,int y)2317 static void stbte__brush(stbte_tilemap *tm, int x, int y)
2318 {
2319 stbte__tileinfo *ti;
2320
2321 // find lowest legit layer to paint it on, and put it there
2322 int i;
2323
2324 if (tm->cur_tile < 0) return;
2325
2326 ti = &tm->tiles[tm->cur_tile];
2327
2328 for (i=0; i < tm->num_layers; ++i) {
2329 // check if object is allowed on layer
2330 if (!(ti->layermask & (1 << i)))
2331 continue;
2332
2333 if (i != tm->solo_layer) {
2334 // if there's a selected layer, can only paint on that
2335 if (tm->cur_layer >= 0 && i != tm->cur_layer)
2336 continue;
2337
2338 // if the layer is hidden, we can't see it
2339 if (tm->layerinfo[i].hidden)
2340 continue;
2341
2342 // if the layer is locked, we can't write to it
2343 if (tm->layerinfo[i].locked == STBTE__locked)
2344 continue;
2345
2346 // if the layer is non-empty and protected, can't write to it
2347 if (tm->layerinfo[i].locked == STBTE__protected && tm->data[y][x][i] != STBTE__BG(tm,i))
2348 continue;
2349 }
2350
2351 stbte__undo_record(tm,x,y,i,tm->data[y][x][i]);
2352 tm->data[y][x][i] = ti->id;
2353 return;
2354 }
2355
2356 //stbte__alert("Selected tile not valid on active layer(s)");
2357 }
2358
2359 enum
2360 {
2361 STBTE__erase_none = -1,
2362 STBTE__erase_brushonly = 0,
2363 STBTE__erase_any = 1,
2364 STBTE__erase_all = 2,
2365 };
2366
stbte__erase_predict(stbte_tilemap * tm,short result[],int allow_any)2367 static int stbte__erase_predict(stbte_tilemap *tm, short result[], int allow_any)
2368 {
2369 stbte__tileinfo *ti = tm->cur_tile >= 0 ? &tm->tiles[tm->cur_tile] : NULL;
2370 int i;
2371
2372 if (allow_any == STBTE__erase_none)
2373 return allow_any;
2374
2375 // first check if only one layer is legit
2376 i = tm->cur_layer;
2377 if (tm->solo_layer >= 0)
2378 i = tm->solo_layer;
2379
2380 // if only one layer is legit, directly process that one for clarity
2381 if (i >= 0) {
2382 short bg = (i == 0 ? tm->background_tile : -1);
2383 if (tm->solo_layer < 0) {
2384 // check that we're allowed to write to it
2385 if (tm->layerinfo[i].hidden) return STBTE__erase_none;
2386 if (tm->layerinfo[i].locked) return STBTE__erase_none;
2387 }
2388 if (result[i] == bg)
2389 return STBTE__erase_none; // didn't erase anything
2390 if (ti && result[i] == ti->id && (i != 0 || ti->id != tm->background_tile)) {
2391 result[i] = bg;
2392 return STBTE__erase_brushonly;
2393 }
2394 if (allow_any == STBTE__erase_any) {
2395 result[i] = bg;
2396 return STBTE__erase_any;
2397 }
2398 return STBTE__erase_none;
2399 }
2400
2401 // if multiple layers are legit, first scan all for brush data
2402
2403 if (ti && allow_any != STBTE__erase_all) {
2404 for (i=tm->num_layers-1; i >= 0; --i) {
2405 if (result[i] != ti->id)
2406 continue;
2407 if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden)
2408 continue;
2409 if (i == 0 && result[i] == tm->background_tile)
2410 return STBTE__erase_none;
2411 result[i] = STBTE__BG(tm,i);
2412 return STBTE__erase_brushonly;
2413 }
2414 }
2415
2416 if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all)
2417 return STBTE__erase_none;
2418
2419 // apply layer filters, erase from top
2420 for (i=tm->num_layers-1; i >= 0; --i) {
2421 if (result[i] < 0)
2422 continue;
2423 if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden)
2424 continue;
2425 if (i == 0 && result[i] == tm->background_tile)
2426 return STBTE__erase_none;
2427 result[i] = STBTE__BG(tm,i);
2428 if (allow_any != STBTE__erase_all)
2429 return STBTE__erase_any;
2430 }
2431
2432 if (allow_any == STBTE__erase_all)
2433 return allow_any;
2434 return STBTE__erase_none;
2435 }
2436
stbte__erase(stbte_tilemap * tm,int x,int y,int allow_any)2437 static int stbte__erase(stbte_tilemap *tm, int x, int y, int allow_any)
2438 {
2439 stbte__tileinfo *ti = tm->cur_tile >= 0 ? &tm->tiles[tm->cur_tile] : NULL;
2440 int i;
2441
2442 if (allow_any == STBTE__erase_none)
2443 return allow_any;
2444
2445 // first check if only one layer is legit
2446 i = tm->cur_layer;
2447 if (tm->solo_layer >= 0)
2448 i = tm->solo_layer;
2449
2450 // if only one layer is legit, directly process that one for clarity
2451 if (i >= 0) {
2452 short bg = (i == 0 ? tm->background_tile : -1);
2453 if (tm->solo_layer < 0) {
2454 // check that we're allowed to write to it
2455 if (tm->layerinfo[i].hidden) return STBTE__erase_none;
2456 if (tm->layerinfo[i].locked) return STBTE__erase_none;
2457 }
2458 if (tm->data[y][x][i] == bg)
2459 return -1; // didn't erase anything
2460 if (ti && tm->data[y][x][i] == ti->id && (i != 0 || ti->id != tm->background_tile)) {
2461 stbte__undo_record(tm,x,y,i,tm->data[y][x][i]);
2462 tm->data[y][x][i] = bg;
2463 return STBTE__erase_brushonly;
2464 }
2465 if (allow_any == STBTE__erase_any) {
2466 stbte__undo_record(tm,x,y,i,tm->data[y][x][i]);
2467 tm->data[y][x][i] = bg;
2468 return STBTE__erase_any;
2469 }
2470 return STBTE__erase_none;
2471 }
2472
2473 // if multiple layers are legit, first scan all for brush data
2474
2475 if (ti && allow_any != STBTE__erase_all) {
2476 for (i=tm->num_layers-1; i >= 0; --i) {
2477 if (tm->data[y][x][i] != ti->id)
2478 continue;
2479 if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden)
2480 continue;
2481 if (i == 0 && tm->data[y][x][i] == tm->background_tile)
2482 return STBTE__erase_none;
2483 stbte__undo_record(tm,x,y,i,tm->data[y][x][i]);
2484 tm->data[y][x][i] = STBTE__BG(tm,i);
2485 return STBTE__erase_brushonly;
2486 }
2487 }
2488
2489 if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all)
2490 return STBTE__erase_none;
2491
2492 // apply layer filters, erase from top
2493 for (i=tm->num_layers-1; i >= 0; --i) {
2494 if (tm->data[y][x][i] < 0)
2495 continue;
2496 if (tm->layerinfo[i].locked || tm->layerinfo[i].hidden)
2497 continue;
2498 if (i == 0 && tm->data[y][x][i] == tm->background_tile)
2499 return STBTE__erase_none;
2500 stbte__undo_record(tm,x,y,i,tm->data[y][x][i]);
2501 tm->data[y][x][i] = STBTE__BG(tm,i);
2502 if (allow_any != STBTE__erase_all)
2503 return STBTE__erase_any;
2504 }
2505 if (allow_any == STBTE__erase_all)
2506 return allow_any;
2507 return STBTE__erase_none;
2508 }
2509
stbte__find_tile(stbte_tilemap * tm,int tile_id)2510 static int stbte__find_tile(stbte_tilemap *tm, int tile_id)
2511 {
2512 int i;
2513 for (i=0; i < tm->num_tiles; ++i)
2514 if (tm->tiles[i].id == tile_id)
2515 return i;
2516 stbte__alert("Eyedropped tile that isn't in tileset");
2517 return -1;
2518 }
2519
stbte__eyedrop(stbte_tilemap * tm,int x,int y)2520 static void stbte__eyedrop(stbte_tilemap *tm, int x, int y)
2521 {
2522 int i,j;
2523
2524 // flush eyedropper state
2525 if (stbte__ui.eyedrop_x != x || stbte__ui.eyedrop_y != y) {
2526 stbte__ui.eyedrop_x = x;
2527 stbte__ui.eyedrop_y = y;
2528 stbte__ui.eyedrop_last_layer = tm->num_layers;
2529 }
2530
2531 // if only one layer is active, query that
2532 i = tm->cur_layer;
2533 if (tm->solo_layer >= 0)
2534 i = tm->solo_layer;
2535 if (i >= 0) {
2536 if (tm->data[y][x][i] == STBTE__NO_TILE)
2537 return;
2538 tm->cur_tile = stbte__find_tile(tm, tm->data[y][x][i]);
2539 return;
2540 }
2541
2542 // if multiple layers, continue from previous
2543 i = stbte__ui.eyedrop_last_layer;
2544 for (j=0; j < tm->num_layers; ++j) {
2545 if (--i < 0)
2546 i = tm->num_layers-1;
2547 if (tm->layerinfo[i].hidden)
2548 continue;
2549 if (tm->data[y][x][i] == STBTE__NO_TILE)
2550 continue;
2551 stbte__ui.eyedrop_last_layer = i;
2552 tm->cur_tile = stbte__find_tile(tm, tm->data[y][x][i]);
2553 return;
2554 }
2555 }
2556
stbte__should_copy_properties(stbte_tilemap * tm)2557 static int stbte__should_copy_properties(stbte_tilemap *tm)
2558 {
2559 int i;
2560 if (tm->propmode == STBTE__propmode_always)
2561 return 1;
2562 if (tm->propmode == STBTE__propmode_never)
2563 return 0;
2564 if (tm->solo_layer >= 0 || tm->cur_layer >= 0)
2565 return 0;
2566 for (i=0; i < tm->num_layers; ++i)
2567 if (tm->layerinfo[i].hidden || tm->layerinfo[i].locked)
2568 return 0;
2569 return 1;
2570 }
2571
2572 // compute the result of pasting into a tile non-destructively so we can preview it
stbte__paste_stack(stbte_tilemap * tm,short result[],short dest[],short src[],int dragging)2573 static void stbte__paste_stack(stbte_tilemap *tm, short result[], short dest[], short src[], int dragging)
2574 {
2575 int i;
2576
2577 // special case single-layer
2578 i = tm->cur_layer;
2579 if (tm->solo_layer >= 0)
2580 i = tm->solo_layer;
2581 if (i >= 0) {
2582 if (tm->solo_layer < 0) {
2583 // check that we're allowed to write to it
2584 if (tm->layerinfo[i].hidden) return;
2585 if (tm->layerinfo[i].locked == STBTE__locked) return;
2586 // if protected, dest has to be empty
2587 if (tm->layerinfo[i].locked == STBTE__protected && dest[i] != STBTE__BG(tm,i)) return;
2588 // if dragging w/o copy, we will try to erase stuff, which protection disallows
2589 if (dragging && tm->layerinfo[i].locked == STBTE__protected)
2590 return;
2591 }
2592 result[i] = dest[i];
2593 if (src[i] != STBTE__BG(tm,i))
2594 result[i] = src[i];
2595 return;
2596 }
2597
2598 for (i=0; i < tm->num_layers; ++i) {
2599 result[i] = dest[i];
2600 if (src[i] != STBTE__NO_TILE)
2601 if (!tm->layerinfo[i].hidden && tm->layerinfo[i].locked != STBTE__locked)
2602 if (tm->layerinfo[i].locked == STBTE__unlocked || (!dragging && dest[i] == STBTE__BG(tm,i)))
2603 result[i] = src[i];
2604 }
2605 }
2606
2607 // compute the result of dragging away from a tile
stbte__clear_stack(stbte_tilemap * tm,short result[])2608 static void stbte__clear_stack(stbte_tilemap *tm, short result[])
2609 {
2610 int i;
2611 // special case single-layer
2612 i = tm->cur_layer;
2613 if (tm->solo_layer >= 0)
2614 i = tm->solo_layer;
2615 if (i >= 0)
2616 result[i] = STBTE__BG(tm,i);
2617 else
2618 for (i=0; i < tm->num_layers; ++i)
2619 if (!tm->layerinfo[i].hidden && tm->layerinfo[i].locked == STBTE__unlocked)
2620 result[i] = STBTE__BG(tm,i);
2621 }
2622
2623 // check if some map square is active
2624 #define STBTE__IS_MAP_ACTIVE() ((stbte__ui.active_id & 127) == STBTE__map)
2625 #define STBTE__IS_MAP_HOT() ((stbte__ui.hot_id & 127) == STBTE__map)
2626
stbte__fillrect(stbte_tilemap * tm,int x0,int y0,int x1,int y1,int fill)2627 static void stbte__fillrect(stbte_tilemap *tm, int x0, int y0, int x1, int y1, int fill)
2628 {
2629 int i,j;
2630
2631 stbte__begin_undo(tm);
2632 if (x0 > x1) i=x0,x0=x1,x1=i;
2633 if (y0 > y1) j=y0,y0=y1,y1=j;
2634 for (j=y0; j <= y1; ++j)
2635 for (i=x0; i <= x1; ++i)
2636 if (fill)
2637 stbte__brush(tm, i,j);
2638 else
2639 stbte__erase(tm, i,j,STBTE__erase_any);
2640 stbte__end_undo(tm);
2641 // suppress warning from brush
2642 stbte__ui.alert_msg = 0;
2643 }
2644
stbte__select_rect(stbte_tilemap * tm,int x0,int y0,int x1,int y1)2645 static void stbte__select_rect(stbte_tilemap *tm, int x0, int y0, int x1, int y1)
2646 {
2647 stbte__ui.has_selection = 1;
2648 stbte__ui.select_x0 = (x0 < x1 ? x0 : x1);
2649 stbte__ui.select_x1 = (x0 < x1 ? x1 : x0);
2650 stbte__ui.select_y0 = (y0 < y1 ? y0 : y1);
2651 stbte__ui.select_y1 = (y0 < y1 ? y1 : y0);
2652 }
2653
stbte__copy_properties(float * dest,float * src)2654 static void stbte__copy_properties(float *dest, float *src)
2655 {
2656 int i;
2657 for (i=0; i < STBTE_MAX_PROPERTIES; ++i)
2658 dest[i] = src[i];
2659 }
2660
stbte__copy_cut(stbte_tilemap * tm,int cut)2661 static void stbte__copy_cut(stbte_tilemap *tm, int cut)
2662 {
2663 int i,j,n,w,h,p=0;
2664 int copy_props = stbte__should_copy_properties(tm);
2665 if (!stbte__ui.has_selection)
2666 return;
2667 w = stbte__ui.select_x1 - stbte__ui.select_x0 + 1;
2668 h = stbte__ui.select_y1 - stbte__ui.select_y0 + 1;
2669 if (STBTE_MAX_COPY / w < h) {
2670 stbte__alert("Selection too large for copy buffer, increase STBTE_MAX_COPY");
2671 return;
2672 }
2673
2674 for (i=0; i < w*h; ++i)
2675 for (n=0; n < tm->num_layers; ++n)
2676 stbte__ui.copybuffer[i][n] = STBTE__NO_TILE;
2677
2678 if (cut)
2679 stbte__begin_undo(tm);
2680 for (j=stbte__ui.select_y0; j <= stbte__ui.select_y1; ++j) {
2681 for (i=stbte__ui.select_x0; i <= stbte__ui.select_x1; ++i) {
2682 for (n=0; n < tm->num_layers; ++n) {
2683 if (tm->solo_layer >= 0) {
2684 if (tm->solo_layer != n)
2685 continue;
2686 } else {
2687 if (tm->cur_layer >= 0)
2688 if (tm->cur_layer != n)
2689 continue;
2690 if (tm->layerinfo[n].hidden)
2691 continue;
2692 if (cut && tm->layerinfo[n].locked)
2693 continue;
2694 }
2695 stbte__ui.copybuffer[p][n] = tm->data[j][i][n];
2696 if (cut) {
2697 stbte__undo_record(tm,i,j,n, tm->data[j][i][n]);
2698 tm->data[j][i][n] = (n==0 ? tm->background_tile : -1);
2699 }
2700 }
2701 if (copy_props) {
2702 stbte__copy_properties(stbte__ui.copyprops[p], tm->props[j][i]);
2703 #ifdef STBTE_ALLOW_LINK
2704 stbte__ui.copylinks[p] = tm->link[j][i];
2705 if (cut)
2706 stbte__set_link(tm, i,j,-1,-1, STBTE__undo_record);
2707 #endif
2708 }
2709 ++p;
2710 }
2711 }
2712 if (cut)
2713 stbte__end_undo(tm);
2714 stbte__ui.copy_width = w;
2715 stbte__ui.copy_height = h;
2716 stbte__ui.has_copy = 1;
2717 //stbte__ui.has_selection = 0;
2718 stbte__ui.copy_has_props = copy_props;
2719 stbte__ui.copy_src = tm; // used to give better semantics when copying links
2720 stbte__ui.copy_src_x = stbte__ui.select_x0;
2721 stbte__ui.copy_src_y = stbte__ui.select_y0;
2722 }
2723
stbte__in_rect(int x,int y,int x0,int y0,int w,int h)2724 static int stbte__in_rect(int x, int y, int x0, int y0, int w, int h)
2725 {
2726 return x >= x0 && x < x0+w && y >= y0 && y < y0+h;
2727 }
2728
2729 #ifdef STBTE_ALLOW_LINK
stbte__in_src_rect(int x,int y)2730 static int stbte__in_src_rect(int x, int y)
2731 {
2732 return stbte__in_rect(x,y, stbte__ui.copy_src_x, stbte__ui.copy_src_y, stbte__ui.copy_width, stbte__ui.copy_height);
2733 }
2734
stbte__in_dest_rect(int x,int y,int destx,int desty)2735 static int stbte__in_dest_rect(int x, int y, int destx, int desty)
2736 {
2737 return stbte__in_rect(x,y, destx, desty, stbte__ui.copy_width, stbte__ui.copy_height);
2738 }
2739 #endif
2740
stbte__paste(stbte_tilemap * tm,int mapx,int mapy)2741 static void stbte__paste(stbte_tilemap *tm, int mapx, int mapy)
2742 {
2743 int w = stbte__ui.copy_width;
2744 int h = stbte__ui.copy_height;
2745 int i,j,k,p;
2746 int x = mapx - (w>>1);
2747 int y = mapy - (h>>1);
2748 int copy_props = stbte__should_copy_properties(tm) && stbte__ui.copy_has_props;
2749 if (stbte__ui.has_copy == 0)
2750 return;
2751 stbte__begin_undo(tm);
2752 p = 0;
2753 for (j=0; j < h; ++j) {
2754 for (i=0; i < w; ++i) {
2755 if (y+j >= 0 && y+j < tm->max_y && x+i >= 0 && x+i < tm->max_x) {
2756 // compute the new stack
2757 short tilestack[STBTE_MAX_LAYERS];
2758 for (k=0; k < tm->num_layers; ++k)
2759 tilestack[k] = tm->data[y+j][x+i][k];
2760 stbte__paste_stack(tm, tilestack, tilestack, stbte__ui.copybuffer[p], 0);
2761 // update anything that changed
2762 for (k=0; k < tm->num_layers; ++k) {
2763 if (tilestack[k] != tm->data[y+j][x+i][k]) {
2764 stbte__undo_record(tm, x+i,y+j,k, tm->data[y+j][x+i][k]);
2765 tm->data[y+j][x+i][k] = tilestack[k];
2766 }
2767 }
2768 }
2769 if (copy_props) {
2770 #ifdef STBTE_ALLOW_LINK
2771 // need to decide how to paste a link, so there's a few cases
2772 int destx = -1, desty = -1;
2773 stbte__link *link = &stbte__ui.copylinks[p];
2774
2775 // check if link is within-rect
2776 if (stbte__in_src_rect(link->x, link->y)) {
2777 // new link should point to copy (but only if copy is within map)
2778 destx = x + (link->x - stbte__ui.copy_src_x);
2779 desty = y + (link->y - stbte__ui.copy_src_y);
2780 } else if (tm == stbte__ui.copy_src) {
2781 // if same map, then preserve link unless target is overwritten
2782 if (!stbte__in_dest_rect(link->x,link->y,x,y)) {
2783 destx = link->x;
2784 desty = link->y;
2785 }
2786 }
2787 // this is necessary for offset-copy, but also in case max_x/max_y has changed
2788 if (destx < 0 || destx >= tm->max_x || desty < 0 || desty >= tm->max_y)
2789 destx = -1, desty = -1;
2790 stbte__set_link(tm, x+i, y+j, destx, desty, STBTE__undo_record);
2791 #endif
2792 for (k=0; k < STBTE_MAX_PROPERTIES; ++k) {
2793 if (tm->props[y+j][x+i][k] != stbte__ui.copyprops[p][k])
2794 stbte__undo_record_prop_float(tm, x+i, y+j, k, tm->props[y+j][x+i][k]);
2795 }
2796 stbte__copy_properties(tm->props[y+j][x+i], stbte__ui.copyprops[p]);
2797 }
2798 ++p;
2799 }
2800 }
2801 stbte__end_undo(tm);
2802 }
2803
stbte__drag_update(stbte_tilemap * tm,int mapx,int mapy,int copy_props)2804 static void stbte__drag_update(stbte_tilemap *tm, int mapx, int mapy, int copy_props)
2805 {
2806 int w = stbte__ui.drag_w, h = stbte__ui.drag_h;
2807 int ox,oy,i,deleted=0,written=0;
2808 short temp[STBTE_MAX_LAYERS];
2809 short *data = NULL;
2810 if (!stbte__ui.shift) {
2811 ox = mapx - stbte__ui.drag_x;
2812 oy = mapy - stbte__ui.drag_y;
2813 if (ox >= 0 && ox < w && oy >= 0 && oy < h) {
2814 deleted=1;
2815 for (i=0; i < tm->num_layers; ++i)
2816 temp[i] = tm->data[mapy][mapx][i];
2817 data = temp;
2818 stbte__clear_stack(tm, data);
2819 }
2820 }
2821 ox = mapx - stbte__ui.drag_dest_x;
2822 oy = mapy - stbte__ui.drag_dest_y;
2823 // if this map square is in the target drag region
2824 if (ox >= 0 && ox < w && oy >= 0 && oy < h) {
2825 // and the src map square is on the map
2826 if (stbte__in_rect(stbte__ui.drag_x+ox, stbte__ui.drag_y+oy, 0, 0, tm->max_x, tm->max_y)) {
2827 written = 1;
2828 if (data == NULL) {
2829 for (i=0; i < tm->num_layers; ++i)
2830 temp[i] = tm->data[mapy][mapx][i];
2831 data = temp;
2832 }
2833 stbte__paste_stack(tm, data, data, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift);
2834 if (copy_props) {
2835 for (i=0; i < STBTE_MAX_PROPERTIES; ++i) {
2836 if (tm->props[mapy][mapx][i] != tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i]) {
2837 stbte__undo_record_prop_float(tm, mapx, mapy, i, tm->props[mapy][mapx][i]);
2838 tm->props[mapy][mapx][i] = tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i];
2839 }
2840 }
2841 }
2842 }
2843 }
2844 if (data) {
2845 for (i=0; i < tm->num_layers; ++i) {
2846 if (tm->data[mapy][mapx][i] != data[i]) {
2847 stbte__undo_record(tm, mapx, mapy, i, tm->data[mapy][mapx][i]);
2848 tm->data[mapy][mapx][i] = data[i];
2849 }
2850 }
2851 }
2852 #ifdef STBTE_ALLOW_LINK
2853 if (copy_props) {
2854 int overwritten=0, moved=0, copied=0;
2855 // since this function is called on EVERY tile, we can fix up even tiles not
2856 // involved in the move
2857
2858 stbte__link *k;
2859 // first, determine what src link ends up here
2860 k = &tm->link[mapy][mapx]; // by default, it's the one currently here
2861 if (deleted) // if dragged away, it's erased
2862 k = NULL;
2863 if (written) // if dragged into, it gets that link
2864 k = &tm->link[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox];
2865
2866 // now check whether the *target* gets moved or overwritten
2867 if (k && k->x >= 0) {
2868 overwritten = stbte__in_rect(k->x, k->y, stbte__ui.drag_dest_x, stbte__ui.drag_dest_y, w, h);
2869 if (!stbte__ui.shift)
2870 moved = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h);
2871 else
2872 copied = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h);
2873 }
2874
2875 if (deleted || written || overwritten || moved || copied) {
2876 // choose the final link value based on the above
2877 if (k == NULL || k->x < 0)
2878 stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record);
2879 else if (moved || (copied && written)) {
2880 // if we move the target, we update to point to the new target;
2881 // or, if we copy the target and the source is part of the copy, then update to new target
2882 int x = k->x + (stbte__ui.drag_dest_x - stbte__ui.drag_x);
2883 int y = k->y + (stbte__ui.drag_dest_y - stbte__ui.drag_y);
2884 if (!(x >= 0 && y >= 0 && x < tm->max_x && y < tm->max_y))
2885 x = -1, y = -1;
2886 stbte__set_link(tm, mapx, mapy, x, y, STBTE__undo_record);
2887 } else if (overwritten) {
2888 stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record);
2889 } else
2890 stbte__set_link(tm, mapx, mapy, k->x, k->y, STBTE__undo_record);
2891 }
2892 }
2893 #endif
2894 }
2895
stbte__drag_place(stbte_tilemap * tm,int mapx,int mapy)2896 static void stbte__drag_place(stbte_tilemap *tm, int mapx, int mapy)
2897 {
2898 int i,j;
2899 int copy_props = stbte__should_copy_properties(tm);
2900 int move_x = (stbte__ui.drag_dest_x - stbte__ui.drag_x);
2901 int move_y = (stbte__ui.drag_dest_y - stbte__ui.drag_y);
2902 if (move_x == 0 && move_y == 0)
2903 return;
2904
2905 stbte__begin_undo(tm);
2906 // we now need a 2D memmove-style mover that doesn't
2907 // overwrite any data as it goes. this requires being
2908 // direction sensitive in the same way as memmove
2909 if (move_y > 0 || (move_y == 0 && move_x > 0)) {
2910 for (j=tm->max_y-1; j >= 0; --j)
2911 for (i=tm->max_x-1; i >= 0; --i)
2912 stbte__drag_update(tm,i,j,copy_props);
2913 } else {
2914 for (j=0; j < tm->max_y; ++j)
2915 for (i=0; i < tm->max_x; ++i)
2916 stbte__drag_update(tm,i,j,copy_props);
2917 }
2918 stbte__end_undo(tm);
2919
2920 stbte__ui.has_selection = 1;
2921 stbte__ui.select_x0 = stbte__ui.drag_dest_x;
2922 stbte__ui.select_y0 = stbte__ui.drag_dest_y;
2923 stbte__ui.select_x1 = stbte__ui.select_x0 + stbte__ui.drag_w - 1;
2924 stbte__ui.select_y1 = stbte__ui.select_y0 + stbte__ui.drag_h - 1;
2925 }
2926
stbte__tile_paint(stbte_tilemap * tm,int sx,int sy,int mapx,int mapy,int layer)2927 static void stbte__tile_paint(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy, int layer)
2928 {
2929 int i;
2930 int id = STBTE__IDMAP(mapx,mapy);
2931 short *data = tm->data[mapy][mapx];
2932 short temp[STBTE_MAX_LAYERS];
2933
2934 if (STBTE__IS_MAP_HOT()) {
2935 if (stbte__ui.pasting) {
2936 int ox = mapx - stbte__ui.paste_x;
2937 int oy = mapy - stbte__ui.paste_y;
2938 if (ox >= 0 && ox < stbte__ui.copy_width && oy >= 0 && oy < stbte__ui.copy_height) {
2939 stbte__paste_stack(tm, temp, tm->data[mapy][mapx], stbte__ui.copybuffer[oy*stbte__ui.copy_width+ox], 0);
2940 data = temp;
2941 }
2942 } else if (stbte__ui.dragging) {
2943 int ox,oy;
2944 for (i=0; i < tm->num_layers; ++i)
2945 temp[i] = tm->data[mapy][mapx][i];
2946 data = temp;
2947
2948 // if it's in the source area, remove things unless shift-dragging
2949 ox = mapx - stbte__ui.drag_x;
2950 oy = mapy - stbte__ui.drag_y;
2951 if (!stbte__ui.shift && ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) {
2952 stbte__clear_stack(tm, temp);
2953 }
2954
2955 ox = mapx - stbte__ui.drag_dest_x;
2956 oy = mapy - stbte__ui.drag_dest_y;
2957 if (ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) {
2958 stbte__paste_stack(tm, temp, temp, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift);
2959 }
2960 } else if (STBTE__IS_MAP_ACTIVE()) {
2961 if (stbte__ui.tool == STBTE__tool_rect) {
2962 if ((stbte__ui.ms_time & 511) < 380) {
2963 int ex = ((stbte__ui.hot_id >> 19) & 4095);
2964 int ey = ((stbte__ui.hot_id >> 7) & 4095);
2965 int sx = stbte__ui.sx;
2966 int sy = stbte__ui.sy;
2967
2968 if ( ((mapx >= sx && mapx < ex+1) || (mapx >= ex && mapx < sx+1))
2969 && ((mapy >= sy && mapy < ey+1) || (mapy >= ey && mapy < sy+1))) {
2970 int i;
2971 for (i=0; i < tm->num_layers; ++i)
2972 temp[i] = tm->data[mapy][mapx][i];
2973 data = temp;
2974 if (stbte__ui.active_event == STBTE__leftdown)
2975 stbte__brush_predict(tm, temp);
2976 else
2977 stbte__erase_predict(tm, temp, STBTE__erase_any);
2978 }
2979 }
2980 }
2981 }
2982 }
2983
2984 if (STBTE__IS_HOT(id) && STBTE__INACTIVE() && !stbte__ui.pasting) {
2985 if (stbte__ui.tool == STBTE__tool_brush) {
2986 if ((stbte__ui.ms_time & 511) < 300) {
2987 data = temp;
2988 for (i=0; i < tm->num_layers; ++i)
2989 temp[i] = tm->data[mapy][mapx][i];
2990 stbte__brush_predict(tm, temp);
2991 }
2992 }
2993 }
2994
2995 {
2996 i = layer;
2997 if (i == tm->solo_layer || (!tm->layerinfo[i].hidden && tm->solo_layer < 0))
2998 if (data[i] >= 0)
2999 STBTE_DRAW_TILE(x0,y0, (unsigned short) data[i], 0, tm->props[mapy][mapx]);
3000 }
3001 }
3002
stbte__tile(stbte_tilemap * tm,int sx,int sy,int mapx,int mapy)3003 static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy)
3004 {
3005 int tool = stbte__ui.tool;
3006 int x0=sx, y0=sy;
3007 int x1=sx+tm->spacing_x, y1=sy+tm->spacing_y;
3008 int id = STBTE__IDMAP(mapx,mapy);
3009 int over = stbte__hittest(x0,y0,x1,y1, id);
3010 switch (stbte__ui.event) {
3011 case STBTE__paint: {
3012 if (stbte__ui.pasting || stbte__ui.dragging || stbte__ui.scrolling)
3013 break;
3014 if (stbte__ui.scrollkey && !STBTE__IS_MAP_ACTIVE())
3015 break;
3016 if (STBTE__IS_HOT(id) && STBTE__IS_MAP_ACTIVE() && (tool == STBTE__tool_rect || tool == STBTE__tool_select)) {
3017 int rx0,ry0,rx1,ry1,t;
3018 // compute the center of each rect
3019 rx0 = x0 + tm->spacing_x/2;
3020 ry0 = y0 + tm->spacing_y/2;
3021 rx1 = rx0 + (stbte__ui.sx - mapx) * tm->spacing_x;
3022 ry1 = ry0 + (stbte__ui.sy - mapy) * tm->spacing_y;
3023 if (rx0 > rx1) t=rx0,rx0=rx1,rx1=t;
3024 if (ry0 > ry1) t=ry0,ry0=ry1,ry1=t;
3025 rx0 -= tm->spacing_x/2;
3026 ry0 -= tm->spacing_y/2;
3027 rx1 += tm->spacing_x/2;
3028 ry1 += tm->spacing_y/2;
3029 stbte__draw_frame(rx0-1,ry0-1,rx1+1,ry1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT);
3030 break;
3031 }
3032 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
3033 stbte__draw_frame(x0-1,y0-1,x1+1,y1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT);
3034 }
3035 #ifdef STBTE_ALLOW_LINK
3036 if (stbte__ui.show_links && tm->link[mapy][mapx].x >= 0) {
3037 int tx = tm->link[mapy][mapx].x;
3038 int ty = tm->link[mapy][mapx].y;
3039 int lx0,ly0,lx1,ly1;
3040 if (STBTE_ALLOW_LINK(tm->data[mapy][mapx], tm->props[mapy][mapx],
3041 tm->data[ty ][tx ], tm->props[ty ][tx ]))
3042 {
3043 lx0 = x0 + (tm->spacing_x >> 1) - 1;
3044 ly0 = y0 + (tm->spacing_y >> 1) - 1;
3045 lx1 = lx0 + (tx - mapx) * tm->spacing_x + 2;
3046 ly1 = ly0 + (ty - mapy) * tm->spacing_y + 2;
3047 stbte__draw_link(lx0,ly0,lx1,ly1,
3048 STBTE_LINK_COLOR(tm->data[mapy][mapx], tm->props[mapy][mapx],
3049 tm->data[ty ][tx ], tm->props[ty ][tx]));
3050 }
3051 }
3052 #endif
3053 break;
3054 }
3055 }
3056
3057 if (stbte__ui.pasting) {
3058 switch (stbte__ui.event) {
3059 case STBTE__leftdown:
3060 if (STBTE__IS_HOT(id)) {
3061 stbte__ui.pasting = 0;
3062 stbte__paste(tm, mapx, mapy);
3063 stbte__activate(0);
3064 }
3065 break;
3066 case STBTE__leftup:
3067 // just clear it no matter what, since they might click away to clear it
3068 stbte__activate(0);
3069 break;
3070 case STBTE__rightdown:
3071 if (STBTE__IS_HOT(id)) {
3072 stbte__activate(0);
3073 stbte__ui.pasting = 0;
3074 }
3075 break;
3076 }
3077 return;
3078 }
3079
3080 if (stbte__ui.scrolling) {
3081 if (stbte__ui.event == STBTE__leftup) {
3082 stbte__activate(0);
3083 stbte__ui.scrolling = 0;
3084 }
3085 if (stbte__ui.event == STBTE__mousemove) {
3086 tm->scroll_x += (stbte__ui.start_x - stbte__ui.mx);
3087 tm->scroll_y += (stbte__ui.start_y - stbte__ui.my);
3088 stbte__ui.start_x = stbte__ui.mx;
3089 stbte__ui.start_y = stbte__ui.my;
3090 }
3091 return;
3092 }
3093
3094 // regardless of tool, leftdown is a scrolldrag
3095 if (STBTE__IS_HOT(id) && stbte__ui.scrollkey && stbte__ui.event == STBTE__leftdown) {
3096 stbte__ui.scrolling = 1;
3097 stbte__ui.start_x = stbte__ui.mx;
3098 stbte__ui.start_y = stbte__ui.my;
3099 return;
3100 }
3101
3102 switch (tool) {
3103 case STBTE__tool_brush:
3104 switch (stbte__ui.event) {
3105 case STBTE__mousemove:
3106 if (STBTE__IS_MAP_ACTIVE() && over) {
3107 // don't brush/erase same tile multiple times unless they move away and back @TODO should just be only once, but that needs another data structure
3108 if (!STBTE__IS_ACTIVE(id)) {
3109 if (stbte__ui.active_event == STBTE__leftdown)
3110 stbte__brush(tm, mapx, mapy);
3111 else
3112 stbte__erase(tm, mapx, mapy, stbte__ui.brush_state);
3113 stbte__ui.active_id = id; // switch to this map square so we don't rebrush IT multiple times
3114 }
3115 }
3116 break;
3117 case STBTE__leftdown:
3118 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
3119 stbte__activate(id);
3120 stbte__begin_undo(tm);
3121 stbte__brush(tm, mapx, mapy);
3122 }
3123 break;
3124 case STBTE__rightdown:
3125 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
3126 stbte__activate(id);
3127 stbte__begin_undo(tm);
3128 if (stbte__erase(tm, mapx, mapy, STBTE__erase_any) == STBTE__erase_brushonly)
3129 stbte__ui.brush_state = STBTE__erase_brushonly;
3130 else
3131 stbte__ui.brush_state = STBTE__erase_any;
3132 }
3133 break;
3134 case STBTE__leftup:
3135 case STBTE__rightup:
3136 if (STBTE__IS_MAP_ACTIVE()) {
3137 stbte__end_undo(tm);
3138 stbte__activate(0);
3139 }
3140 break;
3141 }
3142 break;
3143
3144 #ifdef STBTE_ALLOW_LINK
3145 case STBTE__tool_link:
3146 switch (stbte__ui.event) {
3147 case STBTE__leftdown:
3148 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
3149 stbte__activate(id);
3150 stbte__ui.linking = 1;
3151 stbte__ui.sx = mapx;
3152 stbte__ui.sy = mapy;
3153 // @TODO: undo
3154 }
3155 break;
3156 case STBTE__leftup:
3157 if (STBTE__IS_HOT(id) && STBTE__IS_MAP_ACTIVE()) {
3158 if ((mapx != stbte__ui.sx || mapy != stbte__ui.sy) &&
3159 STBTE_ALLOW_LINK(tm->data[stbte__ui.sy][stbte__ui.sx], tm->props[stbte__ui.sy][stbte__ui.sx],
3160 tm->data[mapy][mapx], tm->props[mapy][mapx]))
3161 stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, STBTE__undo_block);
3162 else
3163 stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, -1,-1, STBTE__undo_block);
3164 stbte__ui.linking = 0;
3165 stbte__activate(0);
3166 }
3167 break;
3168
3169 case STBTE__rightdown:
3170 if (STBTE__IS_ACTIVE(id)) {
3171 stbte__activate(0);
3172 stbte__ui.linking = 0;
3173 }
3174 break;
3175 }
3176 break;
3177 #endif
3178
3179 case STBTE__tool_erase:
3180 switch (stbte__ui.event) {
3181 case STBTE__mousemove:
3182 if (STBTE__IS_MAP_ACTIVE() && over)
3183 stbte__erase(tm, mapx, mapy, STBTE__erase_all);
3184 break;
3185 case STBTE__leftdown:
3186 if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) {
3187 stbte__activate(id);
3188 stbte__begin_undo(tm);
3189 stbte__erase(tm, mapx, mapy, STBTE__erase_all);
3190 }
3191 break;
3192 case STBTE__leftup:
3193 if (STBTE__IS_MAP_ACTIVE()) {
3194 stbte__end_undo(tm);
3195 stbte__activate(0);
3196 }
3197 break;
3198 }
3199 break;
3200
3201 case STBTE__tool_select:
3202 if (STBTE__IS_HOT(id)) {
3203 switch (stbte__ui.event) {
3204 case STBTE__leftdown:
3205 if (STBTE__INACTIVE()) {
3206 // if we're clicking in an existing selection...
3207 if (stbte__ui.has_selection) {
3208 if ( mapx >= stbte__ui.select_x0 && mapx <= stbte__ui.select_x1
3209 && mapy >= stbte__ui.select_y0 && mapy <= stbte__ui.select_y1)
3210 {
3211 stbte__ui.dragging = 1;
3212 stbte__ui.drag_x = stbte__ui.select_x0;
3213 stbte__ui.drag_y = stbte__ui.select_y0;
3214 stbte__ui.drag_w = stbte__ui.select_x1 - stbte__ui.select_x0 + 1;
3215 stbte__ui.drag_h = stbte__ui.select_y1 - stbte__ui.select_y0 + 1;
3216 stbte__ui.drag_offx = mapx - stbte__ui.select_x0;
3217 stbte__ui.drag_offy = mapy - stbte__ui.select_y0;
3218 }
3219 }
3220 stbte__ui.has_selection = 0; // no selection until it completes
3221 stbte__activate_map(mapx,mapy);
3222 }
3223 break;
3224 case STBTE__leftup:
3225 if (STBTE__IS_MAP_ACTIVE()) {
3226 if (stbte__ui.dragging) {
3227 stbte__drag_place(tm, mapx,mapy);
3228 stbte__ui.dragging = 0;
3229 stbte__activate(0);
3230 } else {
3231 stbte__select_rect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy);
3232 stbte__activate(0);
3233 }
3234 }
3235 break;
3236 case STBTE__rightdown:
3237 stbte__ui.has_selection = 0;
3238 break;
3239 }
3240 }
3241 break;
3242
3243 case STBTE__tool_rect:
3244 if (STBTE__IS_HOT(id)) {
3245 switch (stbte__ui.event) {
3246 case STBTE__leftdown:
3247 if (STBTE__INACTIVE())
3248 stbte__activate_map(mapx,mapy);
3249 break;
3250 case STBTE__leftup:
3251 if (STBTE__IS_MAP_ACTIVE()) {
3252 stbte__fillrect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, 1);
3253 stbte__activate(0);
3254 }
3255 break;
3256 case STBTE__rightdown:
3257 if (STBTE__INACTIVE())
3258 stbte__activate_map(mapx,mapy);
3259 break;
3260 case STBTE__rightup:
3261 if (STBTE__IS_MAP_ACTIVE()) {
3262 stbte__fillrect(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, 0);
3263 stbte__activate(0);
3264 }
3265 break;
3266 }
3267 }
3268 break;
3269
3270
3271 case STBTE__tool_eyedrop:
3272 switch (stbte__ui.event) {
3273 case STBTE__leftdown:
3274 if (STBTE__IS_HOT(id) && STBTE__INACTIVE())
3275 stbte__eyedrop(tm,mapx,mapy);
3276 break;
3277 }
3278 break;
3279 }
3280 }
3281
stbte__start_paste(stbte_tilemap * tm)3282 static void stbte__start_paste(stbte_tilemap *tm)
3283 {
3284 if (stbte__ui.has_copy) {
3285 stbte__ui.pasting = 1;
3286 stbte__activate(STBTE__ID(STBTE__toolbarB,3));
3287 }
3288 }
3289
stbte__toolbar(stbte_tilemap * tm,int x0,int y0,int w,int h)3290 static void stbte__toolbar(stbte_tilemap *tm, int x0, int y0, int w, int h)
3291 {
3292 int i;
3293 int estimated_width = 13 * STBTE__num_tool + 8+8+ 120+4 - 30;
3294 int x = x0 + w/2 - estimated_width/2;
3295 int y = y0+1;
3296
3297 for (i=0; i < STBTE__num_tool; ++i) {
3298 int highlight=0, disable=0;
3299 highlight = (stbte__ui.tool == i);
3300 if (i == STBTE__tool_undo || i == STBTE__tool_showgrid)
3301 x += 8;
3302 if (i == STBTE__tool_showgrid && stbte__ui.show_grid)
3303 highlight = 1;
3304 if (i == STBTE__tool_showlinks && stbte__ui.show_links)
3305 highlight = 1;
3306 if (i == STBTE__tool_fill)
3307 continue;
3308 #ifndef STBTE_ALLOW_LINK
3309 if (i == STBTE__tool_link || i == STBTE__tool_showlinks)
3310 disable = 1;
3311 #endif
3312 if (i == STBTE__tool_undo && !stbte__undo_available(tm))
3313 disable = 1;
3314 if (i == STBTE__tool_redo && !stbte__redo_available(tm))
3315 disable = 1;
3316 if (stbte__button_icon(STBTE__ctoolbar_button, toolchar[i], x, y, 13, STBTE__ID(STBTE__toolbarA, i), highlight, disable)) {
3317 switch (i) {
3318 case STBTE__tool_eyedrop:
3319 stbte__ui.eyedrop_last_layer = tm->num_layers; // flush eyedropper state
3320 // fallthrough
3321 default:
3322 stbte__ui.tool = i;
3323 stbte__ui.has_selection = 0;
3324 break;
3325 case STBTE__tool_showlinks:
3326 stbte__ui.show_links = !stbte__ui.show_links;
3327 break;
3328 case STBTE__tool_showgrid:
3329 stbte__ui.show_grid = (stbte__ui.show_grid+1)%3;
3330 break;
3331 case STBTE__tool_undo:
3332 stbte__undo(tm);
3333 break;
3334 case STBTE__tool_redo:
3335 stbte__redo(tm);
3336 break;
3337 }
3338 }
3339 x += 13;
3340 }
3341
3342 x += 8;
3343 if (stbte__button(STBTE__ctoolbar_button, "cut" , x, y,10, 40, STBTE__ID(STBTE__toolbarB,0), 0, !stbte__ui.has_selection))
3344 stbte__copy_cut(tm, 1);
3345 x += 42;
3346 if (stbte__button(STBTE__ctoolbar_button, "copy" , x, y, 5, 40, STBTE__ID(STBTE__toolbarB,1), 0, !stbte__ui.has_selection))
3347 stbte__copy_cut(tm, 0);
3348 x += 42;
3349 if (stbte__button(STBTE__ctoolbar_button, "paste", x, y, 0, 40, STBTE__ID(STBTE__toolbarB,2), stbte__ui.pasting, !stbte__ui.has_copy))
3350 stbte__start_paste(tm);
3351 }
3352
3353 #define STBTE__TEXTCOLOR(n) stbte__color_table[n][STBTE__text][STBTE__idle]
3354
stbte__info_value(const char * label,int x,int y,int val,int digits,int id)3355 static int stbte__info_value(const char *label, int x, int y, int val, int digits, int id)
3356 {
3357 if (stbte__ui.event == STBTE__paint) {
3358 int off = 9-stbte__get_char_width(label[0]);
3359 char text[16];
3360 stbte__sprintf(text stbte__sizeof(text), label, digits, val);
3361 stbte__draw_text_core(x+off,y, text, 999, STBTE__TEXTCOLOR(STBTE__cpanel),1);
3362 }
3363 if (id) {
3364 x += 9+7*digits+4;
3365 if (stbte__minibutton(STBTE__cmapsize, x,y, '+', STBTE__ID2(id,1,0)))
3366 val += (stbte__ui.shift ? 10 : 1);
3367 x += 9;
3368 if (stbte__minibutton(STBTE__cmapsize, x,y, '-', STBTE__ID2(id,2,0)))
3369 val -= (stbte__ui.shift ? 10 : 1);
3370 if (val < 1) val = 1; else if (val > 4096) val = 4096;
3371 }
3372 return val;
3373 }
3374
stbte__info(stbte_tilemap * tm,int x0,int y0,int w,int h)3375 static void stbte__info(stbte_tilemap *tm, int x0, int y0, int w, int h)
3376 {
3377 int mode = stbte__ui.panel[STBTE__panel_info].mode;
3378 int s = 11+7*tm->digits+4+15;
3379 int x,y;
3380 int in_region;
3381
3382 x = x0+2;
3383 y = y0+2;
3384 tm->max_x = stbte__info_value("w:%*d",x,y, tm->max_x, tm->digits, STBTE__ID(STBTE__info,0));
3385 if (mode)
3386 x += s;
3387 else
3388 y += 11;
3389 tm->max_y = stbte__info_value("h:%*d",x,y, tm->max_y, tm->digits, STBTE__ID(STBTE__info,1));
3390 x = x0+2;
3391 y += 11;
3392 in_region = (stbte__ui.hot_id & 127) == STBTE__map;
3393 stbte__info_value(in_region ? "x:%*d" : "x:",x,y, (stbte__ui.hot_id>>19)&4095, tm->digits, 0);
3394 if (mode)
3395 x += s;
3396 else
3397 y += 11;
3398 stbte__info_value(in_region ? "y:%*d" : "y:",x,y, (stbte__ui.hot_id>> 7)&4095, tm->digits, 0);
3399 y += 15;
3400 x = x0+2;
3401 stbte__draw_text(x,y,"brush:",40,STBTE__TEXTCOLOR(STBTE__cpanel));
3402 if (tm->cur_tile >= 0)
3403 STBTE_DRAW_TILE(x+43,y-3,tm->tiles[tm->cur_tile].id,1,0);
3404 }
3405
stbte__layers(stbte_tilemap * tm,int x0,int y0,int w,int h)3406 static void stbte__layers(stbte_tilemap *tm, int x0, int y0, int w, int h)
3407 {
3408 static const char *propmodes[3] = {
3409 "default", "always", "never"
3410 };
3411 int num_rows;
3412 int i, y, n;
3413 int x1 = x0+w;
3414 int y1 = y0+h;
3415 int xoff = 20;
3416
3417 if (tm->has_layer_names) {
3418 int side = stbte__ui.panel[STBTE__panel_layers].side;
3419 xoff = stbte__region[side].width - 42;
3420 xoff = (xoff < tm->layername_width + 10 ? xoff : tm->layername_width + 10);
3421 }
3422
3423 x0 += 2;
3424 y0 += 5;
3425 if (!tm->has_layer_names) {
3426 if (stbte__ui.event == STBTE__paint) {
3427 stbte__draw_text(x0,y0, "Layers", w-4, STBTE__TEXTCOLOR(STBTE__cpanel));
3428 }
3429 y0 += 11;
3430 }
3431 num_rows = (y1-y0)/15;
3432 #ifndef STBTE_NO_PROPS
3433 --num_rows;
3434 #endif
3435 y = y0;
3436 for (i=0; i < tm->num_layers; ++i) {
3437 char text[3], *str = (char *) tm->layerinfo[i].name;
3438 static char lockedchar[3] = { 'U', 'P', 'L' };
3439 int locked = tm->layerinfo[i].locked;
3440 int disabled = (tm->solo_layer >= 0 && tm->solo_layer != i);
3441 if (i-tm->layer_scroll >= 0 && i-tm->layer_scroll < num_rows) {
3442 if (str == NULL)
3443 stbte__sprintf(str=text stbte__sizeof(text), "%2d", i+1);
3444 if (stbte__button(STBTE__clayer_button, str, x0,y,(i+1<10)*2,xoff-2, STBTE__ID(STBTE__layer,i), tm->cur_layer==i,0))
3445 tm->cur_layer = (tm->cur_layer == i ? -1 : i);
3446 if (stbte__layerbutton(x0+xoff + 0,y+1,'H',STBTE__ID(STBTE__hide,i), tm->layerinfo[i].hidden,disabled,STBTE__clayer_hide))
3447 tm->layerinfo[i].hidden = !tm->layerinfo[i].hidden;
3448 if (stbte__layerbutton(x0+xoff + 12,y+1,lockedchar[locked],STBTE__ID(STBTE__lock,i), locked!=0,disabled,STBTE__clayer_lock))
3449 tm->layerinfo[i].locked = (locked+1)%3;
3450 if (stbte__layerbutton(x0+xoff + 24,y+1,'S',STBTE__ID(STBTE__solo,i), tm->solo_layer==i,0,STBTE__clayer_solo))
3451 tm->solo_layer = (tm->solo_layer == i ? -1 : i);
3452 y += 15;
3453 }
3454 }
3455 stbte__scrollbar(x1-4, y0,y-2, &tm->layer_scroll, 0, tm->num_layers, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__layer));
3456 #ifndef STBTE_NO_PROPS
3457 n = stbte__text_width("prop:")+2;
3458 stbte__draw_text(x0,y+2, "prop:", w, STBTE__TEXTCOLOR(STBTE__cpanel));
3459 i = w - n - 4;
3460 if (i > 50) i = 50;
3461 if (stbte__button(STBTE__clayer_button, propmodes[tm->propmode], x0+n,y,0,i, STBTE__ID(STBTE__layer,256), 0,0))
3462 tm->propmode = (tm->propmode+1)%3;
3463 #endif
3464 }
3465
stbte__categories(stbte_tilemap * tm,int x0,int y0,int w,int h)3466 static void stbte__categories(stbte_tilemap *tm, int x0, int y0, int w, int h)
3467 {
3468 int s=11, x,y, i;
3469 int num_rows = h / s;
3470
3471 w -= 4;
3472 x = x0+2;
3473 y = y0+4;
3474 if (tm->category_scroll == 0) {
3475 if (stbte__category_button("*ALL*", x,y, w, STBTE__ID(STBTE__categories, 65535), tm->cur_category == -1)) {
3476 stbte__choose_category(tm, -1);
3477 }
3478 y += s;
3479 }
3480
3481 for (i=0; i < tm->num_categories; ++i) {
3482 if (i+1 - tm->category_scroll >= 0 && i+1 - tm->category_scroll < num_rows) {
3483 if (y + 10 > y0+h)
3484 return;
3485 if (stbte__category_button(tm->categories[i], x,y,w, STBTE__ID(STBTE__categories,i), tm->cur_category == i))
3486 stbte__choose_category(tm, i);
3487 y += s;
3488 }
3489 }
3490 stbte__scrollbar(x0+w, y0+4, y0+h-4, &tm->category_scroll, 0, tm->num_categories+1, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__categories));
3491 }
3492
stbte__tile_in_palette(stbte_tilemap * tm,int x,int y,int slot)3493 static void stbte__tile_in_palette(stbte_tilemap *tm, int x, int y, int slot)
3494 {
3495 int id = STBTE__ID(STBTE__palette, slot);
3496 switch (stbte__ui.event) {
3497 case STBTE__paint:
3498 stbte__draw_rect(x,y,x+tm->palette_spacing_x-1,y+tm->palette_spacing_x-1, STBTE_COLOR_TILEPALETTE_BACKGROUND);
3499 STBTE_DRAW_TILE(x,y,t->id, slot == tm->cur_tile,0);
3500 if (slot == tm->cur_tile)
3501 stbte__draw_frame_delayed(x-1,y-1,x+tm->palette_spacing_x,y+tm->palette_spacing_y, STBTE_COLOR_TILEPALETTE_OUTLINE);
3502 break;
3503 default:
3504 if (stbte__button_core(id))
3505 tm->cur_tile = slot;
3506 break;
3507 }
3508 }
3509
stbte__palette_of_tiles(stbte_tilemap * tm,int x0,int y0,int w,int h)3510 static void stbte__palette_of_tiles(stbte_tilemap *tm, int x0, int y0, int w, int h)
3511 {
3512 int i,x,y;
3513 int num_vis_rows = (h-6) / tm->palette_spacing_y;
3514 int num_columns = (w-2-6) / tm->palette_spacing_x;
3515 int num_total_rows;
3516 int column,row;
3517 int x1 = x0+w, y1=y0+h;
3518 x = x0+2;
3519 y = y0+6;
3520
3521 if (num_columns == 0)
3522 return;
3523
3524 num_total_rows = (tm->cur_palette_count + num_columns-1) / num_columns; // ceil()
3525
3526 column = 0;
3527 row = -tm->palette_scroll;
3528 for (i=0; i < tm->num_tiles; ++i) {
3529 stbte__tileinfo *t = &tm->tiles[i];
3530
3531 // filter based on category
3532 if (tm->cur_category >= 0 && t->category_id != tm->cur_category)
3533 continue;
3534
3535 // display it
3536 if (row >= 0 && row < num_vis_rows) {
3537 x = x0 + 2 + tm->palette_spacing_x * column;
3538 y = y0 + 6 + tm->palette_spacing_y * row;
3539 stbte__tile_in_palette(tm,x,y,i);
3540 }
3541
3542 ++column;
3543 if (column == num_columns) {
3544 column = 0;
3545 ++row;
3546 }
3547 }
3548 stbte__flush_delay();
3549 stbte__scrollbar(x1-4, y0+6, y1-2, &tm->palette_scroll, 0, num_total_rows, num_vis_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__palette));
3550 }
3551
3552 static float stbte__saved;
stbte__props_panel(stbte_tilemap * tm,int x0,int y0,int w,int h)3553 static void stbte__props_panel(stbte_tilemap *tm, int x0, int y0, int w, int h)
3554 {
3555 int x1 = x0+w;
3556 int i;
3557 int y = y0 + 5, x = x0+2;
3558 int slider_width = 60;
3559 int mx,my;
3560 float *p;
3561 short *data;
3562 if (!stbte__is_single_selection())
3563 return;
3564 mx = stbte__ui.select_x0;
3565 my = stbte__ui.select_y0;
3566 p = tm->props[my][mx];
3567 data = tm->data[my][mx];
3568 for (i=0; i < STBTE_MAX_PROPERTIES; ++i) {
3569 unsigned int n = STBTE_PROP_TYPE(i, data, p);
3570 if (n) {
3571 char *s = (char*) STBTE_PROP_NAME(i, data, p);
3572 if (s == NULL) s = (char*) "";
3573 switch (n & 3) {
3574 case STBTE_PROP_bool: {
3575 int flag = (int) p[i];
3576 if (stbte__layerbutton(x,y, flag ? 'x' : ' ', STBTE__ID(STBTE__prop_flag,i), flag, 0, 2)) {
3577 stbte__begin_undo(tm);
3578 stbte__undo_record_prop_float(tm,mx,my,i,(float) flag);
3579 p[i] = (float) !flag;
3580 stbte__end_undo(tm);
3581 }
3582 stbte__draw_text(x+13,y+1,s,x1-(x+13)-2,STBTE__TEXTCOLOR(STBTE__cpanel));
3583 y += 13;
3584 break;
3585 }
3586 case STBTE_PROP_int: {
3587 int a = (int) STBTE_PROP_MIN(i,data,p);
3588 int b = (int) STBTE_PROP_MAX(i,data,p);
3589 int v = (int) p[i] - a;
3590 if (a+v != p[i] || v < 0 || v > b-a) {
3591 if (v < 0) v = 0;
3592 if (v > b-a) v = b-a;
3593 p[i] = (float) (a+v); // @TODO undo
3594 }
3595 switch (stbte__slider(x, slider_width, y+7, b-a, &v, STBTE__ID(STBTE__prop_int,i)))
3596 {
3597 case STBTE__begin:
3598 stbte__saved = p[i];
3599 // fallthrough
3600 case STBTE__change:
3601 p[i] = (float) (a+v); // @TODO undo
3602 break;
3603 case STBTE__end:
3604 if (p[i] != stbte__saved) {
3605 stbte__begin_undo(tm);
3606 stbte__undo_record_prop_float(tm,mx,my,i,stbte__saved);
3607 stbte__end_undo(tm);
3608 }
3609 break;
3610 }
3611 stbte__draw_text(x+slider_width+2,y+2, s, x1-1-(x+slider_width+2), STBTE__TEXTCOLOR(STBTE__cpanel));
3612 y += 12;
3613 break;
3614 }
3615 case STBTE_PROP_float: {
3616 float a = (float) STBTE_PROP_MIN(i, data,p);
3617 float b = (float) STBTE_PROP_MAX(i, data,p);
3618 float c = STBTE_PROP_FLOAT_SCALE(i, data, p);
3619 float old;
3620 if (p[i] < a || p[i] > b) {
3621 // @TODO undo
3622 if (p[i] < a) p[i] = a;
3623 if (p[i] > b) p[i] = b;
3624 }
3625 old = p[i];
3626 switch (stbte__float_control(x, y, 50, a, b, c, "%8.4f", &p[i], STBTE__layer,STBTE__ID(STBTE__prop_float,i))) {
3627 case STBTE__begin:
3628 stbte__saved = old;
3629 break;
3630 case STBTE__end:
3631 if (stbte__saved != p[i]) {
3632 stbte__begin_undo(tm);
3633 stbte__undo_record_prop_float(tm,mx,my,i, stbte__saved);
3634 stbte__end_undo(tm);
3635 }
3636 break;
3637 }
3638 stbte__draw_text(x+53,y+1, s, x1-1-(x+53), STBTE__TEXTCOLOR(STBTE__cpanel));
3639 y += 12;
3640 break;
3641 }
3642 }
3643 }
3644 }
3645 }
3646
3647 static int stbte__cp_mode, stbte__cp_aspect, stbte__cp_state, stbte__cp_index, stbte__save, stbte__cp_altered, stbte__color_copy;
3648 #ifdef STBTE__COLORPICKER
stbte__dump_colorstate(void)3649 static void stbte__dump_colorstate(void)
3650 {
3651 int i,j,k;
3652 printf("static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][STBTE__num_color_states] =\n");
3653 printf("{\n");
3654 printf(" {\n");
3655 for (k=0; k < STBTE__num_color_modes; ++k) {
3656 for (j=0; j < STBTE__num_color_aspects; ++j) {
3657 printf(" { ");
3658 for (i=0; i < STBTE__num_color_states; ++i) {
3659 printf("0x%06x, ", stbte__color_table[k][j][i]);
3660 }
3661 printf("},\n");
3662 }
3663 if (k+1 < STBTE__num_color_modes)
3664 printf(" }, {\n");
3665 else
3666 printf(" },\n");
3667 }
3668 printf("};\n");
3669 }
3670
stbte__colorpicker(int x0,int y0,int w,int h)3671 static void stbte__colorpicker(int x0, int y0, int w, int h)
3672 {
3673 int x1 = x0+w, y1 = y0+h, x,y, i;
3674
3675 x = x0+2; y = y0+6;
3676
3677 y += 5;
3678 x += 8;
3679
3680
3681 {
3682 int color = stbte__color_table[stbte__cp_mode][stbte__cp_aspect][stbte__cp_index];
3683 int rgb[3];
3684 if (stbte__cp_altered && stbte__cp_index == STBTE__idle)
3685 color = stbte__save;
3686
3687 if (stbte__minibutton(STBTE__cmapsize, x1-20,y+ 5, 'C', STBTE__ID2(STBTE__colorpick_id,4,0)))
3688 stbte__color_copy = color;
3689 if (stbte__minibutton(STBTE__cmapsize, x1-20,y+15, 'P', STBTE__ID2(STBTE__colorpick_id,4,1)))
3690 color = stbte__color_copy;
3691
3692 rgb[0] = color >> 16; rgb[1] = (color>>8)&255; rgb[2] = color & 255;
3693 for (i=0; i < 3; ++i) {
3694 if (stbte__slider(x+8,64, y, 255, rgb+i, STBTE__ID2(STBTE__colorpick_id,3,i)) > 0)
3695 stbte__dump_colorstate();
3696 y += 15;
3697 }
3698 if (stbte__ui.event != STBTE__paint && stbte__ui.event != STBTE__tick)
3699 stbte__color_table[stbte__cp_mode][stbte__cp_aspect][stbte__cp_index] = (rgb[0]<<16)|(rgb[1]<<8)|(rgb[2]);
3700 }
3701
3702 y += 5;
3703
3704 // states
3705 x = x0+2+35;
3706 if (stbte__ui.event == STBTE__paint) {
3707 static char *states[] = { "idle", "over", "down", "down&over", "selected", "selected&over", "disabled" };
3708 stbte__draw_text(x, y+1, states[stbte__cp_index], x1-x-1, 0xffffff);
3709 }
3710
3711 x = x0+24; y += 12;
3712
3713 for (i=3; i >= 0; --i) {
3714 int state = 0 != (stbte__cp_state & (1 << i));
3715 if (stbte__layerbutton(x,y, "OASD"[i], STBTE__ID2(STBTE__colorpick_id, 0,i), state,0, STBTE__clayer_button)) {
3716 stbte__cp_state ^= (1 << i);
3717 stbte__cp_index = stbte__state_to_index[0][0][0][stbte__cp_state];
3718 }
3719 x += 16;
3720 }
3721 x = x0+2; y += 18;
3722
3723 for (i=0; i < 3; ++i) {
3724 static char *labels[] = { "Base", "Edge", "Text" };
3725 if (stbte__button(STBTE__ctoolbar_button, labels[i], x,y,0,36, STBTE__ID2(STBTE__colorpick_id,1,i), stbte__cp_aspect==i,0))
3726 stbte__cp_aspect = i;
3727 x += 40;
3728 }
3729
3730 y += 18;
3731 x = x0+2;
3732
3733 for (i=0; i < STBTE__num_color_modes; ++i) {
3734 if (stbte__button(STBTE__ctoolbar_button, stbte__color_names[i], x, y, 0,80, STBTE__ID2(STBTE__colorpick_id,2,i), stbte__cp_mode == i,0))
3735 stbte__cp_mode = i;
3736 y += 12;
3737 }
3738
3739 // make the currently selected aspect flash, unless we're actively dragging color slider etc
3740 if (stbte__ui.event == STBTE__tick) {
3741 stbte__save = stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle];
3742 if ((stbte__ui.active_id & 127) != STBTE__colorpick_id) {
3743 if ((stbte__ui.ms_time & 2047) < 200) {
3744 stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle] ^= 0x1f1f1f;
3745 stbte__cp_altered = 1;
3746 }
3747 }
3748 }
3749 }
3750 #endif
3751
stbte__editor_traverse(stbte_tilemap * tm)3752 static void stbte__editor_traverse(stbte_tilemap *tm)
3753 {
3754 int i,j,i0,j0,i1,j1,n;
3755
3756 if (tm == NULL)
3757 return;
3758 if (stbte__ui.x0 == stbte__ui.x1 || stbte__ui.y0 == stbte__ui.y1)
3759 return;
3760
3761 stbte__prepare_tileinfo(tm);
3762
3763 stbte__compute_panel_locations(tm); // @OPTIMIZE: we don't need to recompute this every time
3764
3765 if (stbte__ui.event == STBTE__paint) {
3766 // fill screen with border
3767 stbte__draw_rect(stbte__ui.x0, stbte__ui.y0, stbte__ui.x1, stbte__ui.y1, STBTE_COLOR_TILEMAP_BORDER);
3768 // fill tilemap with tilemap background
3769 stbte__draw_rect(stbte__ui.x0 - tm->scroll_x, stbte__ui.y0 - tm->scroll_y,
3770 stbte__ui.x0 - tm->scroll_x + tm->spacing_x * tm->max_x,
3771 stbte__ui.y0 - tm->scroll_y + tm->spacing_y * tm->max_y, STBTE_COLOR_TILEMAP_BACKGROUND);
3772 }
3773
3774 // step 1: traverse all the tilemap data...
3775
3776 i0 = (tm->scroll_x - tm->spacing_x) / tm->spacing_x;
3777 j0 = (tm->scroll_y - tm->spacing_y) / tm->spacing_y;
3778 i1 = (tm->scroll_x + stbte__ui.x1 - stbte__ui.x0) / tm->spacing_x + 1;
3779 j1 = (tm->scroll_y + stbte__ui.y1 - stbte__ui.y0) / tm->spacing_y + 1;
3780
3781 if (i0 < 0) i0 = 0;
3782 if (j0 < 0) j0 = 0;
3783 if (i1 > tm->max_x) i1 = tm->max_x;
3784 if (j1 > tm->max_y) j1 = tm->max_y;
3785
3786 if (stbte__ui.event == STBTE__paint) {
3787 // draw all of layer 0, then all of layer 1, etc, instead of old
3788 // way which drew entire stack of each tile at once
3789 for (n=0; n < tm->num_layers; ++n) {
3790 for (j=j0; j < j1; ++j) {
3791 for (i=i0; i < i1; ++i) {
3792 int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x;
3793 int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y;
3794 stbte__tile_paint(tm, x, y, i, j, n);
3795 }
3796 }
3797 if (n == 0 && stbte__ui.show_grid == 1) {
3798 int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x;
3799 int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y;
3800 for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x)
3801 stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID);
3802 for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y)
3803 stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID);
3804 }
3805 }
3806 }
3807
3808 if (stbte__ui.event == STBTE__paint) {
3809 // draw grid on top of everything except UI
3810 if (stbte__ui.show_grid == 2) {
3811 int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x;
3812 int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y;
3813 for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x)
3814 stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID);
3815 for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y)
3816 stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID);
3817 }
3818 }
3819
3820 for (j=j0; j < j1; ++j) {
3821 for (i=i0; i < i1; ++i) {
3822 int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x;
3823 int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y;
3824 stbte__tile(tm, x, y, i, j);
3825 }
3826 }
3827
3828 if (stbte__ui.event == STBTE__paint) {
3829 // draw the selection border
3830 if (stbte__ui.has_selection) {
3831 int x0,y0,x1,y1;
3832 x0 = stbte__ui.x0 + (stbte__ui.select_x0 ) * tm->spacing_x - tm->scroll_x;
3833 y0 = stbte__ui.y0 + (stbte__ui.select_y0 ) * tm->spacing_y - tm->scroll_y;
3834 x1 = stbte__ui.x0 + (stbte__ui.select_x1 + 1) * tm->spacing_x - tm->scroll_x + 1;
3835 y1 = stbte__ui.y0 + (stbte__ui.select_y1 + 1) * tm->spacing_y - tm->scroll_y + 1;
3836 stbte__draw_frame(x0,y0,x1,y1, (stbte__ui.ms_time & 256 ? STBTE_COLOR_SELECTION_OUTLINE1 : STBTE_COLOR_SELECTION_OUTLINE2));
3837 }
3838
3839 stbte__flush_delay(); // draw a dynamic link on top of the queued links
3840
3841 #ifdef STBTE_ALLOW_LINK
3842 if (stbte__ui.linking && STBTE__IS_MAP_HOT()) {
3843 int x0,y0,x1,y1;
3844 int color;
3845 int ex = ((stbte__ui.hot_id >> 19) & 4095);
3846 int ey = ((stbte__ui.hot_id >> 7) & 4095);
3847 x0 = stbte__ui.x0 + (stbte__ui.sx ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)+1;
3848 y0 = stbte__ui.y0 + (stbte__ui.sy ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)+1;
3849 x1 = stbte__ui.x0 + (ex ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)-1;
3850 y1 = stbte__ui.y0 + (ey ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)-1;
3851 if (STBTE_ALLOW_LINK(tm->data[stbte__ui.sy][stbte__ui.sx], tm->props[stbte__ui.sy][stbte__ui.sx], tm->data[ey][ex], tm->props[ey][ex]))
3852 color = STBTE_LINK_COLOR_DRAWING;
3853 else
3854 color = STBTE_LINK_COLOR_DISALLOWED;
3855 stbte__draw_link(x0,y0,x1,y1, color);
3856 }
3857 #endif
3858 }
3859 stbte__flush_delay();
3860
3861 // step 2: traverse the panels
3862 for (i=0; i < STBTE__num_panel; ++i) {
3863 stbte__panel *p = &stbte__ui.panel[i];
3864 if (stbte__ui.event == STBTE__paint) {
3865 stbte__draw_box(p->x0,p->y0,p->x0+p->width,p->y0+p->height, STBTE__cpanel, STBTE__idle);
3866 }
3867 // obscure tilemap data underneath panel
3868 stbte__hittest(p->x0,p->y0,p->x0+p->width,p->y0+p->height, STBTE__ID2(STBTE__panel, i, 0));
3869 switch (i) {
3870 case STBTE__panel_toolbar:
3871 if (stbte__ui.event == STBTE__paint)
3872 stbte__draw_rect(p->x0,p->y0,p->x0+p->width,p->y0+p->height, stbte__color_table[STBTE__ctoolbar][STBTE__base][STBTE__idle]);
3873 stbte__toolbar(tm,p->x0,p->y0,p->width,p->height);
3874 break;
3875 case STBTE__panel_info:
3876 stbte__info(tm,p->x0,p->y0,p->width,p->height);
3877 break;
3878 case STBTE__panel_layers:
3879 stbte__layers(tm,p->x0,p->y0,p->width,p->height);
3880 break;
3881 case STBTE__panel_categories:
3882 stbte__categories(tm,p->x0,p->y0,p->width,p->height);
3883 break;
3884 case STBTE__panel_colorpick:
3885 #ifdef STBTE__COLORPICKER
3886 stbte__colorpicker(p->x0,p->y0,p->width,p->height);
3887 #endif
3888 break;
3889 case STBTE__panel_tiles:
3890 // erase boundary between categories and tiles if they're on same side
3891 if (stbte__ui.event == STBTE__paint && p->side == stbte__ui.panel[STBTE__panel_categories].side)
3892 stbte__draw_rect(p->x0+1,p->y0-1,p->x0+p->width-1,p->y0+1, stbte__color_table[STBTE__cpanel][STBTE__base][STBTE__idle]);
3893 stbte__palette_of_tiles(tm,p->x0,p->y0,p->width,p->height);
3894 break;
3895 case STBTE__panel_props:
3896 stbte__props_panel(tm,p->x0,p->y0,p->width,p->height);
3897 break;
3898 }
3899 // draw the panel side selectors
3900 for (j=0; j < 2; ++j) {
3901 int result;
3902 if (i == STBTE__panel_toolbar) continue;
3903 result = stbte__microbutton(p->x0+p->width - 1 - 2*4 + 4*j,p->y0+2,3, STBTE__ID2(STBTE__panel, i, j+1), STBTE__cpanel_sider+j);
3904 if (result) {
3905 switch (j) {
3906 case 0: p->side = result > 0 ? STBTE__side_left : STBTE__side_right; break;
3907 case 1: p->delta_height += result; break;
3908 }
3909 }
3910 }
3911 }
3912
3913 if (stbte__ui.panel[STBTE__panel_categories].delta_height < -5) stbte__ui.panel[STBTE__panel_categories].delta_height = -5;
3914 if (stbte__ui.panel[STBTE__panel_layers ].delta_height < -5) stbte__ui.panel[STBTE__panel_layers ].delta_height = -5;
3915
3916
3917 // step 3: traverse the regions to place expander controls on them
3918 for (i=0; i < 2; ++i) {
3919 if (stbte__region[i].active) {
3920 int x = stbte__region[i].x;
3921 int width;
3922 if (i == STBTE__side_left)
3923 width = stbte__ui.left_width , x += stbte__region[i].width + 1;
3924 else
3925 width = -stbte__ui.right_width, x -= 6;
3926 if (stbte__microbutton_dragger(x, stbte__region[i].y+2, 5, STBTE__ID(STBTE__region,i), &width)) {
3927 // if non-0, it is expanding, so retract it
3928 if (stbte__region[i].retracted == 0.0)
3929 stbte__region[i].retracted = 0.01f;
3930 else
3931 stbte__region[i].retracted = 0.0;
3932 }
3933 if (i == STBTE__side_left)
3934 stbte__ui.left_width = width;
3935 else
3936 stbte__ui.right_width = -width;
3937 if (stbte__ui.event == STBTE__tick) {
3938 if (stbte__region[i].retracted && stbte__region[i].retracted < 1.0f) {
3939 stbte__region[i].retracted += stbte__ui.dt*4;
3940 if (stbte__region[i].retracted > 1)
3941 stbte__region[i].retracted = 1;
3942 }
3943 }
3944 }
3945 }
3946
3947 if (stbte__ui.event == STBTE__paint && stbte__ui.alert_msg) {
3948 int w = stbte__text_width(stbte__ui.alert_msg);
3949 int x = (stbte__ui.x0+stbte__ui.x1)/2;
3950 int y = (stbte__ui.y0+stbte__ui.y1)*5/6;
3951 stbte__draw_rect (x-w/2-4,y-8, x+w/2+4,y+8, 0x604020);
3952 stbte__draw_frame(x-w/2-4,y-8, x+w/2+4,y+8, 0x906030);
3953 stbte__draw_text (x-w/2,y-4, stbte__ui.alert_msg, w+1, 0xff8040);
3954 }
3955
3956 #ifdef STBTE_SHOW_CURSOR
3957 if (stbte__ui.event == STBTE__paint)
3958 stbte__draw_bitmap(stbte__ui.mx, stbte__ui.my, stbte__get_char_width(26), stbte__get_char_bitmap(26), 0xe0e0e0);
3959 #endif
3960
3961 if (stbte__ui.event == STBTE__tick && stbte__ui.alert_msg) {
3962 stbte__ui.alert_timer -= stbte__ui.dt;
3963 if (stbte__ui.alert_timer < 0) {
3964 stbte__ui.alert_timer = 0;
3965 stbte__ui.alert_msg = 0;
3966 }
3967 }
3968
3969 if (stbte__ui.event == STBTE__paint) {
3970 stbte__color_table[stbte__cp_mode][stbte__cp_aspect][STBTE__idle] = stbte__save;
3971 stbte__cp_altered = 0;
3972 }
3973 }
3974
stbte__do_event(stbte_tilemap * tm)3975 static void stbte__do_event(stbte_tilemap *tm)
3976 {
3977 stbte__ui.next_hot_id = 0;
3978 stbte__editor_traverse(tm);
3979 stbte__ui.hot_id = stbte__ui.next_hot_id;
3980
3981 // automatically cancel on mouse-up in case the object that triggered it
3982 // doesn't exist anymore
3983 if (stbte__ui.active_id) {
3984 if (stbte__ui.event == STBTE__leftup || stbte__ui.event == STBTE__rightup) {
3985 if (!stbte__ui.pasting) {
3986 stbte__activate(0);
3987 if (stbte__ui.undoing)
3988 stbte__end_undo(tm);
3989 stbte__ui.scrolling = 0;
3990 stbte__ui.dragging = 0;
3991 stbte__ui.linking = 0;
3992 }
3993 }
3994 }
3995
3996 // we could do this stuff in the widgets directly, but it would keep recomputing
3997 // the same thing on every tile, which seems dumb.
3998
3999 if (stbte__ui.pasting) {
4000 if (STBTE__IS_MAP_HOT()) {
4001 // compute pasting location based on last hot
4002 stbte__ui.paste_x = ((stbte__ui.hot_id >> 19) & 4095) - (stbte__ui.copy_width >> 1);
4003 stbte__ui.paste_y = ((stbte__ui.hot_id >> 7) & 4095) - (stbte__ui.copy_height >> 1);
4004 }
4005 }
4006 if (stbte__ui.dragging) {
4007 if (STBTE__IS_MAP_HOT()) {
4008 stbte__ui.drag_dest_x = ((stbte__ui.hot_id >> 19) & 4095) - stbte__ui.drag_offx;
4009 stbte__ui.drag_dest_y = ((stbte__ui.hot_id >> 7) & 4095) - stbte__ui.drag_offy;
4010 }
4011 }
4012 }
4013
stbte__set_event(int event,int x,int y)4014 static void stbte__set_event(int event, int x, int y)
4015 {
4016 stbte__ui.event = event;
4017 stbte__ui.mx = x;
4018 stbte__ui.my = y;
4019 stbte__ui.dx = x - stbte__ui.last_mouse_x;
4020 stbte__ui.dy = y - stbte__ui.last_mouse_y;
4021 stbte__ui.last_mouse_x = x;
4022 stbte__ui.last_mouse_y = y;
4023 stbte__ui.accum_x += stbte__ui.dx;
4024 stbte__ui.accum_y += stbte__ui.dy;
4025 }
4026
stbte_draw(stbte_tilemap * tm)4027 void stbte_draw(stbte_tilemap *tm)
4028 {
4029 stbte__ui.event = STBTE__paint;
4030 stbte__editor_traverse(tm);
4031 }
4032
stbte_mouse_move(stbte_tilemap * tm,int x,int y,int shifted,int scrollkey)4033 void stbte_mouse_move(stbte_tilemap *tm, int x, int y, int shifted, int scrollkey)
4034 {
4035 stbte__set_event(STBTE__mousemove, x,y);
4036 stbte__ui.shift = shifted;
4037 stbte__ui.scrollkey = scrollkey;
4038 stbte__do_event(tm);
4039 }
4040
stbte_mouse_button(stbte_tilemap * tm,int x,int y,int right,int down,int shifted,int scrollkey)4041 void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, int shifted, int scrollkey)
4042 {
4043 static int events[2][2] = { { STBTE__leftup , STBTE__leftdown },
4044 { STBTE__rightup, STBTE__rightdown } };
4045 stbte__set_event(events[right][down], x,y);
4046 stbte__ui.shift = shifted;
4047 stbte__ui.scrollkey = scrollkey;
4048
4049 stbte__do_event(tm);
4050 }
4051
stbte_mouse_wheel(stbte_tilemap * tm,int x,int y,int vscroll)4052 void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll)
4053 {
4054 // not implemented yet -- need different way of hittesting
4055 }
4056
stbte_action(stbte_tilemap * tm,enum stbte_action act)4057 void stbte_action(stbte_tilemap *tm, enum stbte_action act)
4058 {
4059 switch (act) {
4060 case STBTE_tool_select: stbte__ui.tool = STBTE__tool_select; break;
4061 case STBTE_tool_brush: stbte__ui.tool = STBTE__tool_brush; break;
4062 case STBTE_tool_erase: stbte__ui.tool = STBTE__tool_erase; break;
4063 case STBTE_tool_rectangle: stbte__ui.tool = STBTE__tool_rect; break;
4064 case STBTE_tool_eyedropper: stbte__ui.tool = STBTE__tool_eyedrop; break;
4065 case STBTE_tool_link: stbte__ui.tool = STBTE__tool_link; break;
4066 case STBTE_act_toggle_grid: stbte__ui.show_grid = (stbte__ui.show_grid+1) % 3; break;
4067 case STBTE_act_toggle_links: stbte__ui.show_links ^= 1; break;
4068 case STBTE_act_undo: stbte__undo(tm); break;
4069 case STBTE_act_redo: stbte__redo(tm); break;
4070 case STBTE_act_cut: stbte__copy_cut(tm, 1); break;
4071 case STBTE_act_copy: stbte__copy_cut(tm, 0); break;
4072 case STBTE_act_paste: stbte__start_paste(tm); break;
4073 case STBTE_scroll_left: tm->scroll_x -= tm->spacing_x; break;
4074 case STBTE_scroll_right: tm->scroll_x += tm->spacing_x; break;
4075 case STBTE_scroll_up: tm->scroll_y -= tm->spacing_y; break;
4076 case STBTE_scroll_down: tm->scroll_y += tm->spacing_y; break;
4077 }
4078 }
4079
stbte_tick(stbte_tilemap * tm,float dt)4080 void stbte_tick(stbte_tilemap *tm, float dt)
4081 {
4082 stbte__ui.event = STBTE__tick;
4083 stbte__ui.dt = dt;
4084 stbte__do_event(tm);
4085 stbte__ui.ms_time += (int) (dt * 1024) + 1; // make sure if time is superfast it always updates a little
4086 }
4087
stbte_mouse_sdl(stbte_tilemap * tm,const void * sdl_event,float xs,float ys,int xo,int yo)4088 void stbte_mouse_sdl(stbte_tilemap *tm, const void *sdl_event, float xs, float ys, int xo, int yo)
4089 {
4090 #ifdef _SDL_H
4091 SDL_Event *event = (SDL_Event *) sdl_event;
4092 SDL_Keymod km = SDL_GetModState();
4093 int shift = (km & KMOD_LCTRL) || (km & KMOD_RCTRL);
4094 int scrollkey = 0 != SDL_GetKeyboardState(NULL)[SDL_SCANCODE_SPACE];
4095 switch (event->type) {
4096 case SDL_MOUSEMOTION:
4097 stbte_mouse_move(tm, (int) (xs*event->motion.x+xo), (int) (ys*event->motion.y+yo), shift, scrollkey);
4098 break;
4099 case SDL_MOUSEBUTTONUP:
4100 stbte_mouse_button(tm, (int) (xs*event->button.x+xo), (int) (ys*event->button.y+yo), event->button.button != SDL_BUTTON_LEFT, 0, shift, scrollkey);
4101 break;
4102 case SDL_MOUSEBUTTONDOWN:
4103 stbte_mouse_button(tm, (int) (xs*event->button.x+xo), (int) (ys*event->button.y+yo), event->button.button != SDL_BUTTON_LEFT, 1, shift, scrollkey);
4104 break;
4105 case SDL_MOUSEWHEEL:
4106 stbte_mouse_wheel(tm, stbte__ui.mx, stbte__ui.my, event->wheel.y);
4107 break;
4108 }
4109 #else
4110 STBTE__NOTUSED(tm);
4111 STBTE__NOTUSED(sdl_event);
4112 STBTE__NOTUSED(xs);
4113 STBTE__NOTUSED(ys);
4114 STBTE__NOTUSED(xo);
4115 STBTE__NOTUSED(yo);
4116 #endif
4117 }
4118
4119 #endif // STB_TILEMAP_EDITOR_IMPLEMENTATION
4120
4121 /*
4122 ------------------------------------------------------------------------------
4123 This software is available under 2 licenses -- choose whichever you prefer.
4124 ------------------------------------------------------------------------------
4125 ALTERNATIVE A - MIT License
4126 Copyright (c) 2017 Sean Barrett
4127 Permission is hereby granted, free of charge, to any person obtaining a copy of
4128 this software and associated documentation files (the "Software"), to deal in
4129 the Software without restriction, including without limitation the rights to
4130 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
4131 of the Software, and to permit persons to whom the Software is furnished to do
4132 so, subject to the following conditions:
4133 The above copyright notice and this permission notice shall be included in all
4134 copies or substantial portions of the Software.
4135 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4136 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4137 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4138 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4139 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4140 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
4141 SOFTWARE.
4142 ------------------------------------------------------------------------------
4143 ALTERNATIVE B - Public Domain (www.unlicense.org)
4144 This is free and unencumbered software released into the public domain.
4145 Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
4146 software, either in source code form or as a compiled binary, for any purpose,
4147 commercial or non-commercial, and by any means.
4148 In jurisdictions that recognize copyright laws, the author or authors of this
4149 software dedicate any and all copyright interest in the software to the public
4150 domain. We make this dedication for the benefit of the public at large and to
4151 the detriment of our heirs and successors. We intend this dedication to be an
4152 overt act of relinquishment in perpetuity of all present and future rights to
4153 this software under copyright law.
4154 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4155 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4156 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4157 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
4158 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
4159 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
4160 ------------------------------------------------------------------------------
4161 */
4162