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