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