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
4 // Embeddable tilemap editor for C/C++
9 // How to compile/use the library
10 // Additional configuration macros
12 // Info on editing multiple levels
21 // Q: What counts as a tilemap for this library?
23 // A: An array of rectangles, where each rectangle contains a small
26 // Q: What are the limitations?
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.
32 // Q: How do I compile this?
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.
38 // Q: What advantages does this have over a standalone editor?
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.
44 // Q: Can I live-edit my game maps?
46 // A: Not really, the editor keeps its own map representation.
48 // Q: How do I save and load maps?
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.)
59 // Q: I want to have tiles change appearance based on what's
60 // adjacent, or other tile-display/substitution trickiness.
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.
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).
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!)
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.
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.
82 // Q: How do I scale the user interface?
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.
90 // Q: How do I scale the map display?
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.
99 // Q: How does object editing work?
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.
108 // Q: How are properties themselves handled?
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.
115 // Q: What if I want to have two different objects/spawners in
116 // one tile, both of which have their own properties?
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.
124 // Q: Can I do one-to-many linking?
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
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?
135 // A: There is no way to do this. (Unless you can reverse one link.)
137 // Q: How does cut & paste interact with object properties & links?
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.
146 // Q: What happens if the library gets a memory allocation failure
147 // while I'm editing? Will I lose my work?
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.
155 // Q: What happens if the library crashes while I'm editing? Will
158 // A: Yes. Save often.
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:
168 // #define STB_TILEMAP_EDITOR_IMPLEMENTATION
169 // // this triggers the implementation
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)
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
183 // #include "stb_tilemap_editor.h"
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:
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.
203 // // Since the tiledata is passed to you, you can choose which property
204 // // is bound to that slot based on that data.
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.
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.
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.
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.
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
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
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
241 // [[ support for those below is not implemented yet ]]
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.
250 // ADDITIONAL CONFIGURATION
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.
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
266 // Further documentation appears in the header-file section below.
268 // EDITING MULTIPLE LEVELS
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.)
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
294 // - fix bug when pasting into protected layer
295 // - better color scheme
296 // - internal-use color picker
297 // 0.10 initial release
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
310 // Main editor & features
312 // Additional features:
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.
326 ///////////////////////////////////////////////////////////////////////
330 #ifndef STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H
331 #define STB_TILEMAP_INCLUDE_STB_TILEMAP_EDITOR_H
334 #ifndef _CRT_SECURE_NO_WARNINGS
335 #define _CRT_SECURE_NO_WARNINGS
341 typedef struct stbte_tilemap stbte_tilemap
;
343 // these are the drawmodes used in STBTE_DRAW_TILE
346 STBTE_drawmode_deemphasize
= -1,
347 STBTE_drawmode_normal
= 0,
348 STBTE_drawmode_emphasize
= 1,
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
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
372 // If insufficient memory, returns NULL
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
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
392 extern void stbte_draw(stbte_tilemap
*tm
);
394 extern void stbte_tick(stbte_tilemap
*tm
, float time_in_seconds_since_last_frame
);
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
);
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
);
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
417 STBTE_tool_rectangle
,
418 STBTE_tool_eyedropper
,
420 STBTE_act_toggle_grid
,
421 STBTE_act_toggle_links
,
432 extern void stbte_action(stbte_tilemap
*tm
, enum stbte_action act
);
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.)
443 #define STBTE_EMPTY -1
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
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.
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
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.
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
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
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
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
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
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
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
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)
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)
501 #ifdef STB_TILEMAP_EDITOR_IMPLEMENTATION
504 #define STBTE_ASSERT assert
509 #define STBTE__NOTUSED(v) (void)(v)
511 #define STBTE__NOTUSED(v) (void)sizeof(v)
514 #ifndef STBTE_MAX_TILEMAP_X
515 #define STBTE_MAX_TILEMAP_X 200
518 #ifndef STBTE_MAX_TILEMAP_Y
519 #define STBTE_MAX_TILEMAP_Y 200
522 #ifndef STBTE_MAX_LAYERS
523 #define STBTE_MAX_LAYERS 8
526 #ifndef STBTE_MAX_CATEGORIES
527 #define STBTE_MAX_CATEGORIES 100
530 #ifndef STBTE_MAX_COPY
531 #define STBTE_MAX_COPY 65536
534 #ifndef STBTE_UNDO_BUFFER_BYTES
535 #define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB
538 #ifndef STBTE_PROP_TYPE
539 #define STBTE__NO_PROPS
540 #define STBTE_PROP_TYPE(n,td,tp) 0
543 #ifndef STBTE_PROP_NAME
544 #define STBTE_PROP_NAME(n,td,tp) ""
547 #ifndef STBTE_MAX_PROPERTIES
548 #define STBTE_MAX_PROPERTIES 10
551 #ifndef STBTE_PROP_MIN
552 #define STBTE_PROP_MIN(n,td,tp) 0
555 #ifndef STBTE_PROP_MAX
556 #define STBTE_PROP_MAX(n,td,tp) 100.0
559 #ifndef STBTE_PROP_FLOAT_SCALE
560 #define STBTE_PROP_FLOAT_SCALE(n,td,tp) 1 // default scale size
563 #ifndef STBTE_FLOAT_CONTROL_GRANULARITY
564 #define STBTE_FLOAT_CONTROL_GRANULARITY 4
568 #define STBTE__UNDO_BUFFER_COUNT (STBTE_UNDO_BUFFER_BYTES>>1)
570 #if STBTE_MAX_TILEMAP_X > 4096 || STBTE_MAX_TILEMAP_Y > 4096
571 #error "Maximum editable map size is 4096 x 4096"
573 #if STBTE_MAX_LAYERS > 32
574 #error "Maximum layers allowed is 32"
576 #if STBTE_UNDO_BUFFER_COUNT & (STBTE_UNDO_BUFFER_COUNT-1)
577 #error "Undo buffer size must be a power of 2"
580 #if STBTE_MAX_PROPERTIES == 0
581 #define STBTE__NO_PROPS
584 #ifdef STBTE__NO_PROPS
585 #undef STBTE_MAX_PROPERTIES
586 #define STBTE_MAX_PROPERTIES 1 // so we can declare arrays
600 STBTE__num_color_aspects
,
610 STBTE__selected_over
,
612 STBTE__num_color_states
,
619 STBTE__ctoolbar_button
,
625 STBTE__clayer_button
,
629 STBTE__ccategory_button
,
631 STBTE__num_color_modes
,
634 #ifdef STBTE__COLORPICKER
635 static char *stbte__color_names
[] =
637 "expander", "toolbar", "tool button", "panel",
638 "panel c1", "panel c2", "scollbar", "map button",
639 "layer", "hide", "lock", "solo",
642 #endif // STBTE__COLORPICKER
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
] =
648 { 0x000000, 0x84987c, 0xdcdca8, 0xdcdca8, 0x40c040, 0x60d060, 0x505050, },
649 { 0xa4b090, 0xe0ec80, 0xffffc0, 0xffffc0, 0x80ff80, 0x80ff80, 0x606060, },
650 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
652 { 0x808890, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, },
653 { 0x605860, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, 0x606060, },
654 { 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, },
656 { 0x3c5068, 0x7088a8, 0x647488, 0x94b4dc, 0x8890c4, 0x9caccc, 0x404040, },
657 { 0x889cb8, 0x889cb8, 0x889cb8, 0x889cb8, 0x84c4e8, 0xacc8ff, 0x0c0c08, },
658 { 0xbcc4cc, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x707074, },
660 { 0x403848, 0x403010, 0x403010, 0x403010, 0x403010, 0x403010, 0x303024, },
661 { 0x68546c, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0xc08040, 0x605030, },
662 { 0xf4e4ff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
664 { 0xb4b04c, 0xacac60, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, },
665 { 0xa0a04c, 0xd0d04c, 0xffff80, 0xffff80, 0x80ff80, 0x80ff80, 0x606060, },
666 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
668 { 0x40c440, 0x60d060, 0xc0ffc0, 0xc0ffc0, 0x40c040, 0x60d060, 0x505050, },
669 { 0x40c040, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x80ff80, 0x606060, },
670 { 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
672 { 0x9090ac, 0xa0a0b8, 0xbcb8cc, 0xbcb8cc, 0x909040, 0x909040, 0x909040, },
673 { 0xa0a0b8, 0xb0b4d0, 0xa0a0b8, 0xa0a0b8, 0xa0a050, 0xa0a050, 0xa0a050, },
674 { 0x808088, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, 0x808030, },
676 { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, },
677 { 0x646064, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, },
678 { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
680 { 0x704c70, 0x885c8c, 0x9c68a4, 0xb870bc, 0xb490bc, 0xb490bc, 0x302828, },
681 { 0xb09cb4, 0xcca8d4, 0xc060c0, 0xa07898, 0xe0b8e0, 0xe0b8e0, 0x403838, },
682 { 0xdccce4, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0x909090, },
684 { 0x646494, 0x888cb8, 0xb0b0b0, 0xb0b0cc, 0x9c9cf4, 0x8888b0, 0x50506c, },
685 { 0x9090a4, 0xb0b4d4, 0xb0b0dc, 0xb0b0cc, 0xd0d0fc, 0xd0d4f0, 0x606060, },
686 { 0xb4b4d4, 0xe4e4ff, 0xffffff, 0xffffff, 0xe0e4ff, 0xececff, 0x909090, },
688 { 0x646444, 0x888c64, 0xb0b0b0, 0xb0b088, 0xaca858, 0x88886c, 0x505050, },
689 { 0x88886c, 0xb0b490, 0xb0b0b0, 0xb0b088, 0xd8d898, 0xd0d4b0, 0x606060, },
690 { 0xb4b49c, 0xffffd8, 0xffffff, 0xffffd4, 0xffffdc, 0xffffcc, 0x909090, },
692 { 0x906464, 0xb48c8c, 0xd4b0b0, 0xdcb0b0, 0xff9c9c, 0xc88888, 0x505050, },
693 { 0xb47c80, 0xd4b4b8, 0xc4a8a8, 0xdcb0b0, 0xffc0c0, 0xfce8ec, 0x606060, },
694 { 0xe0b4b4, 0xffdcd8, 0xffd8d4, 0xffe0e4, 0xffece8, 0xffffff, 0x909090, },
696 { 0x403848, 0x403848, 0x403848, 0x886894, 0x7c80c8, 0x7c80c8, 0x302828, },
697 { 0x403848, 0x403848, 0x403848, 0x403848, 0x7c80c8, 0x7c80c8, 0x403838, },
698 { 0xc8c4c8, 0xffffff, 0xffffff, 0xffffff, 0xe8e8ec, 0xffffff, 0x909090, },
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
711 #ifndef STBTE_LINK_COLOR
712 #define STBTE_LINK_COLOR(src,sp,dest,dp) 0x5030ff
715 #ifndef STBTE_LINK_COLOR_DRAWING
716 #define STBTE_LINK_COLOR_DRAWING 0xff40ff
719 #ifndef STBTE_LINK_COLOR_DISALLOWED
720 #define STBTE_LINK_COLOR_DISALLOWED 0x602060
724 // disabled, selected, down, over
725 static unsigned char stbte__state_to_index
[2][2][2][2] =
728 { { STBTE__idle
, STBTE__over
}, { STBTE__down
, STBTE__over_down
}, },
729 { { STBTE__selected
, STBTE__selected_over
}, { STBTE__down
, STBTE__over_down
}, },
731 { { STBTE__disabled
, STBTE__disabled
}, { STBTE__disabled
, STBTE__disabled
}, },
732 { { STBTE__selected
, STBTE__selected_over
}, { STBTE__disabled
, STBTE__disabled
}, },
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))
738 #define STBTE__FONT_HEIGHT 9
739 static short stbte__font_offset
[95+16];
740 static short stbte__fontdata
[769] =
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,
779 unsigned short category_id
;
781 unsigned int layermask
;
784 #define MAX_LAYERMASK (1 << (8*sizeof(unsigned int)))
786 typedef short stbte__tiledata
;
788 #define STBTE__NO_TILE -1
792 STBTE__panel_toolbar
,
793 STBTE__panel_colorpick
,
797 STBTE__panel_categories
,
821 STBTE__tool_showgrid
,
822 STBTE__tool_showlinks
,
826 // copy/cut/paste aren't included here because they're displayed differently
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, };
836 STBTE__propmode_default
,
837 STBTE__propmode_always
,
838 STBTE__propmode_never
,
845 // from here down does hittesting
858 int delta_height
; // number of rows they've requested for this
866 int x0
,y0
,x1
,y1
,color
;
869 #define STBTE__MAX_DELAYRECT 256
873 int tool
, active_event
;
874 int active_id
, hot_id
, next_hot_id
;
878 int shift
, scrollkey
;
880 int side_extended
[2];
881 stbte__colorrect delayrect
[STBTE__MAX_DELAYRECT
];
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
;
892 int drag_x
, drag_y
, drag_w
, drag_h
;
893 int drag_offx
, drag_offy
, drag_dest_x
, drag_dest_y
;
895 int has_selection
, select_x0
, select_y0
, select_x1
, select_y1
;
897 int x0
,y0
,x1
,y1
, left_width
, right_width
; // configurable widths
899 const char *alert_msg
;
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
];
907 int copy_src_x
, copy_src_y
;
908 stbte_tilemap
*copy_src
;
909 int copy_width
,copy_height
,has_copy
,copy_has_props
;
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 };
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))
919 #define STBTE__BUTTON_HEIGHT (STBTE__FONT_HEIGHT + 2 * STBTE__BUTTON_INTERNAL_SPACING)
920 #define STBTE__BUTTON_INTERNAL_SPACING (2 + (STBTE__FONT_HEIGHT>>4))
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
];
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
;
960 stbte__layer layerinfo
[STBTE_MAX_LAYERS
];
966 int undo_pos
, undo_len
, redo_len
;
967 short background_tile
;
968 unsigned char id_in_use
[32768>>3];
972 static char *default_category
= "[unassigned]";
974 static void stbte__init_gui(void)
977 stbte__ui
.initted
= 1;
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
;
985 stbte__ui
.panel
[STBTE__panel_toolbar
].side
= STBTE__side_top
;
986 stbte__ui
.panel
[STBTE__panel_colorpick
].side
= STBTE__side_right
;
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;
995 for (i
=0; i
< 95+16; ++i
) {
996 stbte__font_offset
[i
] = n
;
997 n
+= stbte__fontdata
[i
];
1001 stbte_tilemap
*stbte_create_map(int map_x
, int map_y
, int map_layers
, int spacing_x
, int spacing_y
, int max_tiles
)
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
)
1012 if (!stbte__ui
.initted
)
1015 tm
= (stbte_tilemap
*) malloc(sizeof(*tm
) + sizeof(*tm
->tiles
) * max_tiles
+ STBTE_UNDO_BUFFER_BYTES
);
1019 tm
->tiles
= (stbte__tileinfo
*) (tm
+1);
1020 tm
->undo_buffer
= (short *) (tm
->tiles
+ max_tiles
);
1021 tm
->num_layers
= map_layers
;
1024 tm
->spacing_x
= spacing_x
;
1025 tm
->spacing_y
= spacing_y
;
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;
1033 tm
->solo_layer
= -1;
1037 tm
->category_scroll
= 0;
1038 tm
->layer_scroll
= 0;
1040 tm
->has_layer_names
= 0;
1041 tm
->layername_width
= 0;
1042 tm
->undo_available_valid
= 0;
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;
1050 tm
->background_tile
= STBTE__NO_TILE
;
1051 stbte_clear_map(tm
);
1053 tm
->max_tiles
= max_tiles
;
1055 for (i
=0; i
< 32768/8; ++i
)
1056 tm
->id_in_use
[i
] = 0;
1057 tm
->tileinfo_dirty
= 1;
1061 void stbte_set_background_tile(stbte_tilemap
*tm
, short id
)
1064 STBTE_ASSERT(id
>= -1 && id
< 32768);
1065 if (id
>= 32768 || id
< -1)
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
;
1073 void stbte_set_spacing(stbte_tilemap
*tm
, int spacing_x
, int spacing_y
, int palette_spacing_x
, int palette_spacing_y
)
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
;
1081 void stbte_set_sidewidths(int left
, int right
)
1083 stbte__ui
.left_width
= left
;
1084 stbte__ui
.right_width
= right
;
1087 void stbte_set_display(int x0
, int y0
, int x1
, int y1
)
1095 void stbte_define_tile(stbte_tilemap
*tm
, unsigned short id
, unsigned int layermask
, const char * category_c
)
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))))
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
;
1111 tm
->tileinfo_dirty
= 1;
1114 static int stbte__text_width(const char *str
);
1116 void stbte_set_layername(stbte_tilemap
*tm
, int layer
, const char *layername
)
1118 STBTE_ASSERT(layer
>= 0 && layer
< tm
->num_layers
);
1119 if (layer
>= 0 && layer
< tm
->num_layers
) {
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
);
1128 void stbte_get_dimensions(stbte_tilemap
*tm
, int *max_x
, int *max_y
)
1134 short* stbte_get_tile(stbte_tilemap
*tm
, int x
, int y
)
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
)
1139 return tm
->data
[y
][x
];
1142 float *stbte_get_properties(stbte_tilemap
*tm
, int x
, int y
)
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
)
1147 return tm
->props
[y
][x
];
1150 void stbte_get_link(stbte_tilemap
*tm
, int x
, int y
, int *destx
, int *desty
)
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
;
1159 if (!STBTE_ALLOW_LINK(tm
->data
[y
][x
], tm
->props
[y
][x
], tm
->data
[gy
][gx
], tm
->props
[gy
][gx
]))
1167 void stbte_set_property(stbte_tilemap
*tm
, int x
, int y
, int n
, float val
)
1169 tm
->props
[y
][x
][n
] = val
;
1172 static void stbte__set_link(stbte_tilemap
*tm
, int src_x
, int src_y
, int dest_x
, int dest_y
, int undo_mode
);
1181 void stbte_set_link(stbte_tilemap
*tm
, int x
, int y
, int destx
, int desty
)
1183 #ifdef STBTE_ALLOW_LINK
1184 stbte__set_link(tm
, x
, y
, destx
, desty
, STBTE__undo_none
);
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
1194 void stbte_set_dimensions(stbte_tilemap
*tm
, int map_x
, int map_y
)
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
)
1204 void stbte_clear_map(stbte_tilemap
*tm
)
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;
1221 void stbte_set_tile(stbte_tilemap
*tm
, int x
, int y
, int layer
, signed short tile
)
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
)
1228 if (layer
< 0 || layer
>= tm
->num_layers
|| tile
< -1)
1230 tm
->data
[y
][x
][layer
] = tile
;
1233 static void stbte__choose_category(stbte_tilemap
*tm
, int category
)
1236 tm
->cur_category
= category
;
1237 for (i
=0; i
< tm
->num_tiles
; ++i
)
1238 if (tm
->tiles
[i
].category_id
== category
|| category
== -1)
1240 tm
->cur_palette_count
= n
;
1241 tm
->palette_scroll
= 0;
1244 static int stbte__strequal(char *p
, char *q
)
1247 if (*p
++ != *q
++) return 0;
1251 static void stbte__compute_tileinfo(stbte_tilemap
*tm
)
1255 tm
->num_categories
=0;
1257 for (i
=0; i
< tm
->num_tiles
; ++i
) {
1258 stbte__tileinfo
*t
= &tm
->tiles
[i
];
1260 for (j
=0; j
< tm
->num_categories
; ++j
)
1261 if (stbte__strequal(t
->category
, tm
->categories
[j
]))
1263 tm
->categories
[j
] = t
->category
;
1264 ++tm
->num_categories
;
1266 t
->category_id
= (unsigned short) j
;
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;
1275 stbte__choose_category(tm
, tm
->cur_category
);
1277 tm
->tileinfo_dirty
= 0;
1280 static void stbte__prepare_tileinfo(stbte_tilemap
*tm
)
1282 if (tm
->tileinfo_dirty
)
1283 stbte__compute_tileinfo(tm
);
1287 /////////////////////// undo system ////////////////////////
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.
1293 // the commands are:
1295 // 1) end_of_undo_record
1298 // 2) end_of_redo_record
1302 // tile_id:short (-1..32767)
1305 // layer:short (0..31)
1307 // 4) property update (also used for links)
1312 // property:short (256+prop#)
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.
1319 // When we read back through, we see them in reverse order, so
1320 // we'll see the layer number or property number first
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
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
1332 // given two points, compute the length between them
1333 #define stbte__wrap(pos) ((pos) & (STBTE__UNDO_BUFFER_COUNT-1))
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
1339 static void stbte__write_undo(stbte_tilemap
*tm
, short value
)
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;
1349 static void stbte__write_redo(stbte_tilemap
*tm
, short value
)
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;
1359 static void stbte__begin_undo(stbte_tilemap
*tm
)
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
1367 static void stbte__end_undo(stbte_tilemap
*tm
)
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
1375 STBTE_ASSERT(tm
->undo_len
> 0);
1378 tm
->undo_buffer
[tm
->undo_pos
] = STBTE__undo_junk
;
1379 // otherwise do nothing
1381 stbte__ui
.undoing
= 0;
1385 static void stbte__undo_record(stbte_tilemap
*tm
, int x
, int y
, int i
, int v
)
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
);
1396 static void stbte__redo_record(stbte_tilemap
*tm
, int x
, int y
, int i
, int v
)
1398 stbte__write_redo(tm
, v
);
1399 stbte__write_redo(tm
, x
);
1400 stbte__write_redo(tm
, y
);
1401 stbte__write_redo(tm
, i
);
1404 static float stbte__extract_float(short s0
, short s1
)
1406 union { float f
; short s
[2]; } converter
;
1407 converter
.s
[0] = s0
;
1408 converter
.s
[1] = s1
;
1412 static short stbte__extract_short(float f
, int slot
)
1414 union { float f
; short s
[2]; } converter
;
1416 return converter
.s
[slot
];
1419 static void stbte__undo_record_prop(stbte_tilemap
*tm
, int x
, int y
, int i
, short s0
, short s1
)
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
);
1431 static void stbte__undo_record_prop_float(stbte_tilemap
*tm
, int x
, int y
, int i
, float f
)
1433 stbte__undo_record_prop(tm
, x
,y
,i
, stbte__extract_short(f
,0), stbte__extract_short(f
,1));
1436 static void stbte__redo_record_prop(stbte_tilemap
*tm
, int x
, int y
, int i
, short s0
, short s1
)
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
);
1446 static int stbte__undo_find_end(stbte_tilemap
*tm
)
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
)
1454 if (tm
->undo_buffer
[pos
] >= 255)
1455 pos
= stbte__wrap(pos
-5), i
+= 5;
1457 pos
= stbte__wrap(pos
-4), i
+= 4;
1459 if (i
>= tm
->undo_len
)
1464 static void stbte__undo(stbte_tilemap
*tm
)
1467 endpos
= stbte__undo_find_end(tm
);
1471 // we found a complete undo record
1472 pos
= stbte__wrap(tm
->undo_pos
-1);
1474 // start a redo record
1475 stbte__write_redo(tm
, STBTE__redo_record
);
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) {
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)];
1488 int v2
= tm
->undo_buffer
[stbte__wrap(pos
-4)];
1489 pos
= stbte__wrap(pos
-5);
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
;
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
);
1502 // write the redo entry
1503 stbte__redo_record_prop(tm
, x
, y
, n
-256, s0
,s1
);
1504 // apply the undo entry
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
;
1513 // overwrite undo record with junk
1514 tm
->undo_buffer
[tm
->undo_pos
] = STBTE__undo_junk
;
1517 static int stbte__redo_find_end(stbte_tilemap
*tm
)
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
)
1525 if (tm
->undo_buffer
[pos
] >= 255)
1526 pos
= stbte__wrap(pos
+5), i
+= 5;
1528 pos
= stbte__wrap(pos
+4), i
+= 4;
1530 if (i
>= tm
->redo_len
)
1531 return -1; // this should only ever happen if redo buffer is empty
1535 static void stbte__redo(stbte_tilemap
*tm
)
1537 // first scan through for the end record
1539 endpos
= stbte__redo_find_end(tm
);
1543 // we found a complete redo record
1544 pos
= stbte__wrap(tm
->undo_pos
+1);
1546 // start an undo record
1547 stbte__write_undo(tm
, STBTE__undo_record
);
1549 for (i
=0; pos
!= endpos
; i
+= 4) {
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)];
1556 int v2
= tm
->undo_buffer
[stbte__wrap(pos
+4)];
1558 pos
= stbte__wrap(pos
+5);
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
;
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
);
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
);
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
;
1587 tm
->undo_buffer
[tm
->undo_pos
] = STBTE__undo_junk
;
1590 // because detecting that undo is available
1591 static void stbte__recompute_undo_available(stbte_tilemap
*tm
)
1593 tm
->undo_available
= (stbte__undo_find_end(tm
) >= 0);
1594 tm
->redo_available
= (stbte__redo_find_end(tm
) >= 0);
1597 static int stbte__undo_available(stbte_tilemap
*tm
)
1599 if (!tm
->undo_available_valid
)
1600 stbte__recompute_undo_available(tm
);
1601 return tm
->undo_available
;
1604 static int stbte__redo_available(stbte_tilemap
*tm
)
1606 if (!tm
->undo_available_valid
)
1607 stbte__recompute_undo_available(tm
);
1608 return tm
->redo_available
;
1611 ///////////////////////////////////////////////////////////////////////////////////////////////////
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
)
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
)
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
);
1627 // check if there's an existing link
1629 // decrement existing link refcount
1630 STBTE_ASSERT(tm
->linkcount
[a
->y
][a
->x
] > 0);
1631 --tm
->linkcount
[a
->y
][a
->x
];
1633 // increment new dest
1635 ++tm
->linkcount
[dest_y
][dest_x
];
1643 static void stbte__draw_rect(int x0
, int y0
, int x1
, int y1
, unsigned int color
)
1645 STBTE_DRAW_RECT(x0
,y0
,x1
,y1
, color
);
1648 static void stbte__draw_line(int x0
, int y0
, int x1
, int y1
, unsigned int color
)
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
);
1656 static void stbte__draw_link(int x0
, int y0
, int x1
, int y1
, unsigned int color
)
1658 stbte__draw_line(x0
,y0
,x0
,y1
, color
);
1659 stbte__draw_line(x0
,y1
,x1
,y1
, color
);
1662 static void stbte__draw_frame(int x0
, int y0
, int x1
, int y1
, unsigned int color
)
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
);
1670 static void stbte__draw_halfframe(int x0
, int y0
, int x1
, int y1
, unsigned int color
)
1672 stbte__draw_rect(x0
,y0
,x1
,y0
+1,color
);
1673 stbte__draw_rect(x0
,y0
+1,x0
+1,y1
,color
);
1676 static int stbte__get_char_width(int ch
)
1678 return stbte__fontdata
[ch
-16];
1681 static short *stbte__get_char_bitmap(int ch
)
1683 return stbte__fontdata
+ stbte__font_offset
[ch
-16];
1686 static void stbte__draw_bitmask_as_columns(int x
, int y
, short bitmask
, int color
)
1688 int start_i
= -1, i
=0;
1690 if (bitmask
& (1<<i
)) {
1693 } else if (start_i
>= 0) {
1694 stbte__draw_rect(x
, y
+start_i
, x
+1, y
+i
, color
);
1696 bitmask
&= ~((1<<i
)-1); // clear all the old bits; we don't clear them as we go to save code
1702 static void stbte__draw_bitmap(int x
, int y
, int w
, short *bitmap
, int color
)
1705 for (i
=0; i
< w
; ++i
)
1706 stbte__draw_bitmask_as_columns(x
+i
, y
, *bitmap
++, color
);
1709 static void stbte__draw_text_core(int x
, int y
, const char *str
, int w
, int color
, int digitspace
)
1714 int cw
= stbte__get_char_width(c
);
1717 stbte__draw_bitmap(x
, y
, cw
, stbte__get_char_bitmap(c
), color
);
1718 if (digitspace
&& c
== ' ')
1719 cw
= stbte__get_char_width('0');
1724 static void stbte__draw_text(int x
, int y
, const char *str
, int w
, int color
)
1726 stbte__draw_text_core(x
,y
,str
,w
,color
,0);
1729 static int stbte__text_width(const char *str
)
1734 int cw
= stbte__get_char_width(c
);
1740 static void stbte__draw_frame_delayed(int x0
, int y0
, int x1
, int y1
, int color
)
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
;
1748 static void stbte__flush_delay(void)
1750 stbte__colorrect
*r
;
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;
1758 static void stbte__activate(int id
)
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;
1766 static int stbte__hittest(int x0
, int y0
, int x1
, int y1
, int id
)
1768 int over
= stbte__ui
.mx
>= x0
&& stbte__ui
.my
>= y0
1769 && stbte__ui
.mx
< x1
&& stbte__ui
.my
< y1
;
1771 if (over
&& stbte__ui
.event
>= STBTE__tick
)
1772 stbte__ui
.next_hot_id
= id
;
1777 static int stbte__button_core(int id
)
1779 switch (stbte__ui
.event
) {
1780 case STBTE__leftdown
:
1781 if (stbte__ui
.hot_id
== id
&& STBTE__INACTIVE())
1782 stbte__activate(id
);
1785 if (stbte__ui
.active_id
== id
&& STBTE__IS_HOT(id
)) {
1790 case STBTE__rightdown
:
1791 if (stbte__ui
.hot_id
== id
&& STBTE__INACTIVE())
1792 stbte__activate(id
);
1794 case STBTE__rightup
:
1795 if (stbte__ui
.active_id
== id
&& STBTE__IS_HOT(id
)) {
1804 static void stbte__draw_box(int x0
, int y0
, int x1
, int y1
, int colormode
, int colorindex
)
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
]);
1810 static void stbte__draw_textbox(int x0
, int y0
, int x1
, int y1
, char *text
, int xoff
, int yoff
, int colormode
, int colorindex
)
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
]);
1816 static int stbte__button(int colormode
, char *label
, int x
, int y
, int textoff
, int width
, int id
, int toggled
, int disabled
)
1818 int x0
=x
,y0
=y
, x1
=x
+width
,y1
=y
+STBTE__BUTTON_HEIGHT
;
1819 int s
= STBTE__BUTTON_INTERNAL_SPACING
;
1821 int over
= !disabled
&& stbte__hittest(x0
,y0
,x1
,y1
,id
);
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
));
1827 return (stbte__button_core(id
) == 1);
1830 static int stbte__button_icon(int colormode
, char ch
, int x
, int y
, int width
, int id
, int toggled
, int disabled
)
1832 int x0
=x
,y0
=y
, x1
=x
+width
,y1
=y
+STBTE__BUTTON_HEIGHT
;
1833 int s
= STBTE__BUTTON_INTERNAL_SPACING
;
1835 int over
= stbte__hittest(x0
,y0
,x1
,y1
,id
);
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
));
1844 return (stbte__button_core(id
) == 1);
1847 static int stbte__minibutton(int colormode
, int x
, int y
, int ch
, int id
)
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));
1855 return stbte__button_core(id
);
1858 static int stbte__layerbutton(int x
, int y
, int ch
, int id
, int toggled
, int disabled
, int colormode
)
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
));
1869 return stbte__button_core(id
);
1872 static int stbte__microbutton(int x
, int y
, int size
, int id
, int colormode
)
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));
1879 return stbte__button_core(id
);
1882 static int stbte__microbutton_dragger(int x
, int y
, int size
, int id
, int *pos
)
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
) {
1888 stbte__draw_box(x0
,y0
,x1
,y1
, STBTE__cexpander
, STBTE__INDEX_FOR_ID(id
,0,0));
1890 case STBTE__leftdown
:
1891 if (STBTE__IS_HOT(id
) && STBTE__INACTIVE()) {
1892 stbte__activate(id
);
1893 stbte__ui
.sx
= stbte__ui
.mx
- *pos
;
1896 case STBTE__mousemove
:
1897 if (STBTE__IS_ACTIVE(id
) && stbte__ui
.active_event
== STBTE__leftdown
) {
1898 *pos
= stbte__ui
.mx
- stbte__ui
.sx
;
1902 if (STBTE__IS_ACTIVE(id
))
1906 return stbte__button_core(id
);
1911 static int stbte__category_button(char *label
, int x
, int y
, int width
, int id
, int toggled
)
1913 int x0
=x
,y0
=y
, x1
=x
+width
,y1
=y
+STBTE__BUTTON_HEIGHT
;
1914 int s
= STBTE__BUTTON_INTERNAL_SPACING
;
1916 int over
= stbte__hittest(x0
,y0
,x1
,y1
,id
);
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
));
1921 return (stbte__button_core(id
) == 1);
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
)
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
) {
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);
1944 case STBTE__leftdown
:
1945 if (STBTE__IS_HOT(id
) && STBTE__INACTIVE()) {
1946 stbte__activate(id
);
1947 event_mouse_move
= STBTE__begin
;
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
;
1955 return event_mouse_move
;
1959 if (STBTE__IS_ACTIVE(id
)) {
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
)
1972 int over
= stbte__hittest(x0
,y0
,x1
,y1
,id
);
1973 switch (stbte__ui
.event
) {
1974 case STBTE__paint
: {
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));
1980 case STBTE__leftdown
:
1981 case STBTE__rightdown
:
1982 if (STBTE__IS_HOT(id
) && STBTE__INACTIVE())
1983 stbte__activate(id
);
1984 return STBTE__begin
;
1987 case STBTE__rightup
:
1988 if (STBTE__IS_ACTIVE(id
)) {
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
;
2004 delta
= ax
/ 16.0f
+ ay
/ 256.0f
;
2006 if (stbte__ui
.active_event
== STBTE__leftdown
)
2007 delta
= ax
*10.0f
+ ay
;
2009 delta
= ax
* 0.1f
+ ay
* 0.01f
;
2012 if (v
< minv
) v
= minv
;
2013 if (v
> maxv
) v
= maxv
;
2015 return STBTE__change
;
2022 static void stbte__scrollbar(int x
, int y0
, int y1
, int *val
, int v0
, int v1
, int num_vis
, int id
)
2026 if (v1
- v0
<= num_vis
)
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
) {
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));
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
);
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
);
2051 if (STBTE__IS_ACTIVE(id
))
2057 if (*val
>= v1
-num_vis
)
2064 static void stbte__compute_digits(stbte_tilemap
*tm
)
2066 if (tm
->max_x
>= 1000 || tm
->max_y
>= 1000)
2068 else if (tm
->max_x
>= 100 || tm
->max_y
>= 100)
2074 static int stbte__is_single_selection(void)
2076 return stbte__ui
.has_selection
2077 && stbte__ui
.select_x0
== stbte__ui
.select_x1
2078 && stbte__ui
.select_y0
== stbte__ui
.select_y1
;
2089 static stbte__region_t stbte__region
[4];
2091 #define STBTE__TOOLBAR_ICON_SIZE (9+2*2)
2092 #define STBTE__TOOLBAR_PASTE_SIZE (34+2*2)
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
)
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
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;
2120 // compute number of digits needs for info panel
2121 stbte__compute_digits(tm
);
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;
2130 panel_active
[STBTE__panel_props
] = props
&& stbte__is_single_selection();
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
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
;
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;
2154 // now compute the heights of each panel
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
;
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
2169 for (i
=0; i
< 4; ++i
)
2170 stbte__region
[i
].y
= stbte__ui
.y0
+ vpos
[i
];
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
;
2178 height
[STBTE__panel_colorpick
] = 300;
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
;
2186 height
[STBTE__panel_info
] = 5 + 11*4 + 2 + tm
->palette_spacing_y
;
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;
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;
2199 k
= (stbte__region
[p
[STBTE__panel_tiles
].side
].width
- 8) / tm
->palette_spacing_x
;
2201 height
[STBTE__panel_tiles
] = ((tm
->num_tiles
+k
-1)/k
) * tm
->palette_spacing_y
+ 8;
2204 height
[STBTE__panel_props
] = 9 + STBTE_MAX_PROPERTIES
*14;
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
;
2221 ; // it's at top, it's already been explicitly set up earlier
2227 p
[i
].x0
= stbte__ui
.x1
;
2228 p
[i
].y0
= stbte__ui
.y1
;
2233 // unique identifiers for imgui
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
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
,
2255 // id is: [ 24-bit data : 7-bit identifer ]
2256 // map id is: [ 12-bit y : 12 bit x : 7-bit identifier ]
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)
2262 static void stbte__activate_map(int x
, int y
)
2264 stbte__ui
.active_id
= STBTE__IDMAP(x
,y
);
2265 stbte__ui
.active_event
= stbte__ui
.event
;
2270 static void stbte__alert(const char *msg
)
2272 stbte__ui
.alert_msg
= msg
;
2273 stbte__ui
.alert_timer
= 3;
2276 #define STBTE__BG(tm,layer) ((layer) == 0 ? (tm)->background_tile : STBTE__NO_TILE)
2280 static void stbte__brush_predict(stbte_tilemap
*tm
, short result
[])
2282 int layer_to_paint
= tm
->cur_layer
;
2283 stbte__tileinfo
*ti
;
2286 if (tm
->cur_tile
< 0) return;
2288 ti
= &tm
->tiles
[tm
->cur_tile
];
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
)))
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
)
2301 // if the layer is hidden, we can't see it
2302 if (tm
->layerinfo
[i
].hidden
)
2305 // if the layer is locked, we can't write to it
2306 if (tm
->layerinfo
[i
].locked
== STBTE__locked
)
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
))
2319 static void stbte__brush(stbte_tilemap
*tm
, int x
, int y
)
2321 int layer_to_paint
= tm
->cur_layer
;
2322 stbte__tileinfo
*ti
;
2324 // find lowest legit layer to paint it on, and put it there
2327 if (tm
->cur_tile
< 0) return;
2329 ti
= &tm
->tiles
[tm
->cur_tile
];
2331 for (i
=0; i
< tm
->num_layers
; ++i
) {
2332 // check if object is allowed on layer
2333 if (!(ti
->layermask
& (1 << i
)))
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
)
2341 // if the layer is hidden, we can't see it
2342 if (tm
->layerinfo
[i
].hidden
)
2345 // if the layer is locked, we can't write to it
2346 if (tm
->layerinfo
[i
].locked
== STBTE__locked
)
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
))
2354 stbte__undo_record(tm
,x
,y
,i
,tm
->data
[y
][x
][i
]);
2355 tm
->data
[y
][x
][i
] = ti
->id
;
2359 //stbte__alert("Selected tile not valid on active layer(s)");
2364 STBTE__erase_none
= -1,
2365 STBTE__erase_brushonly
= 0,
2366 STBTE__erase_any
= 1,
2367 STBTE__erase_all
= 2,
2370 static int stbte__erase_predict(stbte_tilemap
*tm
, short result
[], int allow_any
)
2372 stbte__tileinfo
*ti
= tm
->cur_tile
>= 0 ? &tm
->tiles
[tm
->cur_tile
] : NULL
;
2375 if (allow_any
== STBTE__erase_none
)
2378 // first check if only one layer is legit
2380 if (tm
->solo_layer
>= 0)
2383 // if only one layer is legit, directly process that one for clarity
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
;
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
)) {
2395 return STBTE__erase_brushonly
;
2397 if (allow_any
== STBTE__erase_any
) {
2399 return STBTE__erase_any
;
2401 return STBTE__erase_none
;
2404 // if multiple layers are legit, first scan all for brush data
2406 if (ti
&& allow_any
!= STBTE__erase_all
) {
2407 for (i
=tm
->num_layers
-1; i
>= 0; --i
) {
2408 if (result
[i
] != ti
->id
)
2410 if (tm
->layerinfo
[i
].locked
|| tm
->layerinfo
[i
].hidden
)
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
;
2419 if (allow_any
!= STBTE__erase_any
&& allow_any
!= STBTE__erase_all
)
2420 return STBTE__erase_none
;
2422 // apply layer filters, erase from top
2423 for (i
=tm
->num_layers
-1; i
>= 0; --i
) {
2426 if (tm
->layerinfo
[i
].locked
|| tm
->layerinfo
[i
].hidden
)
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
;
2435 if (allow_any
== STBTE__erase_all
)
2437 return STBTE__erase_none
;
2440 static int stbte__erase(stbte_tilemap
*tm
, int x
, int y
, int allow_any
)
2442 stbte__tileinfo
*ti
= tm
->cur_tile
>= 0 ? &tm
->tiles
[tm
->cur_tile
] : NULL
;
2445 if (allow_any
== STBTE__erase_none
)
2448 // first check if only one layer is legit
2450 if (tm
->solo_layer
>= 0)
2453 // if only one layer is legit, directly process that one for clarity
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
;
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
;
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
;
2473 return STBTE__erase_none
;
2476 // if multiple layers are legit, first scan all for brush data
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
)
2482 if (tm
->layerinfo
[i
].locked
|| tm
->layerinfo
[i
].hidden
)
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
;
2492 if (allow_any
!= STBTE__erase_any
&& allow_any
!= STBTE__erase_all
)
2493 return STBTE__erase_none
;
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)
2499 if (tm
->layerinfo
[i
].locked
|| tm
->layerinfo
[i
].hidden
)
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
;
2508 if (allow_any
== STBTE__erase_all
)
2510 return STBTE__erase_none
;
2513 static int stbte__find_tile(stbte_tilemap
*tm
, int tile_id
)
2516 for (i
=0; i
< tm
->num_tiles
; ++i
)
2517 if (tm
->tiles
[i
].id
== tile_id
)
2519 stbte__alert("Eyedropped tile that isn't in tileset");
2523 static void stbte__eyedrop(stbte_tilemap
*tm
, int x
, int y
)
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
;
2534 // if only one layer is active, query that
2536 if (tm
->solo_layer
>= 0)
2539 if (tm
->data
[y
][x
][i
] == STBTE__NO_TILE
)
2541 tm
->cur_tile
= stbte__find_tile(tm
, tm
->data
[y
][x
][i
]);
2545 // if multiple layers, continue from previous
2546 i
= stbte__ui
.eyedrop_last_layer
;
2547 for (j
=0; j
< tm
->num_layers
; ++j
) {
2549 i
= tm
->num_layers
-1;
2550 if (tm
->layerinfo
[i
].hidden
)
2552 if (tm
->data
[y
][x
][i
] == STBTE__NO_TILE
)
2554 stbte__ui
.eyedrop_last_layer
= i
;
2555 tm
->cur_tile
= stbte__find_tile(tm
, tm
->data
[y
][x
][i
]);
2560 static int stbte__should_copy_properties(stbte_tilemap
*tm
)
2563 if (tm
->propmode
== STBTE__propmode_always
)
2565 if (tm
->propmode
== STBTE__propmode_never
)
2567 if (tm
->solo_layer
>= 0 || tm
->cur_layer
>= 0)
2569 for (i
=0; i
< tm
->num_layers
; ++i
)
2570 if (tm
->layerinfo
[i
].hidden
|| tm
->layerinfo
[i
].locked
)
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
)
2580 // special case single-layer
2582 if (tm
->solo_layer
>= 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
)
2595 result
[i
] = dest
[i
];
2596 if (src
[i
] != STBTE__BG(tm
,i
))
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
)))
2610 // compute the result of dragging away from a tile
2611 static void stbte__clear_stack(stbte_tilemap
*tm
, short result
[])
2614 // special case single-layer
2616 if (tm
->solo_layer
>= 0)
2619 result
[i
] = STBTE__BG(tm
,i
);
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
);
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)
2630 static void stbte__fillrect(stbte_tilemap
*tm
, int x0
, int y0
, int x1
, int y1
, int fill
)
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
)
2641 stbte__brush(tm
, i
,j
);
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;
2649 static void stbte__select_rect(stbte_tilemap
*tm
, int x0
, int y0
, int x1
, int y1
)
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
);
2658 static void stbte__copy_properties(float *dest
, float *src
)
2661 for (i
=0; i
< STBTE_MAX_PROPERTIES
; ++i
)
2665 static void stbte__copy_cut(stbte_tilemap
*tm
, int cut
)
2668 int copy_props
= stbte__should_copy_properties(tm
);
2669 if (!stbte__ui
.has_selection
)
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");
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
;
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
)
2691 if (tm
->cur_layer
>= 0)
2692 if (tm
->cur_layer
!= n
)
2694 if (tm
->layerinfo
[n
].hidden
)
2696 if (cut
&& tm
->layerinfo
[n
].locked
)
2699 stbte__ui
.copybuffer
[p
][n
] = tm
->data
[j
][i
][n
];
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);
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
];
2710 stbte__set_link(tm
, i
,j
,-1,-1, STBTE__undo_record
);
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
;
2728 static int stbte__in_rect(int x
, int y
, int x0
, int y0
, int w
, int h
)
2730 return x
>= x0
&& x
< x0
+w
&& y
>= y0
&& y
< y0
+h
;
2733 static int stbte__in_src_rect(int x
, int y
)
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
);
2738 static int stbte__in_dest_rect(int x
, int y
, int destx
, int desty
)
2740 return stbte__in_rect(x
,y
, destx
, desty
, stbte__ui
.copy_width
, stbte__ui
.copy_height
);
2743 static void stbte__paste(stbte_tilemap
*tm
, int mapx
, int mapy
)
2745 int w
= stbte__ui
.copy_width
;
2746 int h
= stbte__ui
.copy_height
;
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)
2753 stbte__begin_undo(tm
);
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
];
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
];
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
)) {
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
);
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
]);
2798 stbte__copy_properties(tm
->props
[y
+j
][x
+i
], stbte__ui
.copyprops
[p
]);
2803 stbte__end_undo(tm
);
2806 static void stbte__drag_update(stbte_tilemap
*tm
, int mapx
, int mapy
, int copy_props
)
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
];
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
) {
2817 for (i
=0; i
< tm
->num_layers
; ++i
)
2818 temp
[i
] = tm
->data
[mapy
][mapx
][i
];
2820 stbte__clear_stack(tm
, data
);
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
)) {
2831 for (i
=0; i
< tm
->num_layers
; ++i
)
2832 temp
[i
] = tm
->data
[mapy
][mapx
][i
];
2835 stbte__paste_stack(tm
, data
, data
, tm
->data
[stbte__ui
.drag_y
+oy
][stbte__ui
.drag_x
+ox
], !stbte__ui
.shift
);
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
];
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
];
2854 #ifdef STBTE_ALLOW_LINK
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
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
2865 if (written
) // if dragged into, it gets that link
2866 k
= &tm
->link
[stbte__ui
.drag_y
+oy
][stbte__ui
.drag_x
+ox
];
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
);
2874 copied
= stbte__in_rect(k
->x
, k
->y
, stbte__ui
.drag_x
, stbte__ui
.drag_y
, w
, h
);
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
))
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
);
2892 stbte__set_link(tm
, mapx
, mapy
, k
->x
, k
->y
, STBTE__undo_record
);
2898 static void stbte__drag_place(stbte_tilemap
*tm
, int mapx
, int mapy
)
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)
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
);
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
);
2920 stbte__end_undo(tm
);
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;
2929 static void stbte__tile_paint(stbte_tilemap
*tm
, int sx
, int sy
, int mapx
, int mapy
, int layer
)
2932 int id
= STBTE__IDMAP(mapx
,mapy
);
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
];
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);
2947 } else if (stbte__ui
.dragging
) {
2949 for (i
=0; i
< tm
->num_layers
; ++i
)
2950 temp
[i
] = tm
->data
[mapy
][mapx
][i
];
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
);
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
);
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
;
2973 if ( ((mapx
>= sx
&& mapx
< ex
+1) || (mapx
>= ex
&& mapx
< sx
+1))
2974 && ((mapy
>= sy
&& mapy
< ey
+1) || (mapy
>= ey
&& mapy
< sy
+1))) {
2976 for (i
=0; i
< tm
->num_layers
; ++i
)
2977 temp
[i
] = tm
->data
[mapy
][mapx
][i
];
2979 if (stbte__ui
.active_event
== STBTE__leftdown
)
2980 stbte__brush_predict(tm
, temp
);
2982 stbte__erase_predict(tm
, temp
, STBTE__erase_any
);
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) {
2993 for (i
=0; i
< tm
->num_layers
; ++i
)
2994 temp
[i
] = tm
->data
[mapy
][mapx
][i
];
2995 stbte__brush_predict(tm
, temp
);
3002 if (i
== tm
->solo_layer
|| (!tm
->layerinfo
[i
].hidden
&& tm
->solo_layer
< 0))
3004 STBTE_DRAW_TILE(x0
,y0
, (unsigned short) data
[i
], 0, tm
->props
[mapy
][mapx
]);
3008 static void stbte__tile(stbte_tilemap
*tm
, int sx
, int sy
, int mapx
, int mapy
)
3010 int tool
= stbte__ui
.tool
;
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
)
3019 if (stbte__ui
.scrollkey
&& !STBTE__IS_MAP_ACTIVE())
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
);
3037 if (STBTE__IS_HOT(id
) && STBTE__INACTIVE()) {
3038 stbte__draw_frame(x0
-1,y0
-1,x1
+1,y1
+1, STBTE_COLOR_TILEMAP_HIGHLIGHT
);
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
]))
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
]));
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
);
3072 // just clear it no matter what, since they might click away to clear it
3075 case STBTE__rightdown
:
3076 if (STBTE__IS_HOT(id
)) {
3078 stbte__ui
.pasting
= 0;
3085 if (stbte__ui
.scrolling
) {
3086 if (stbte__ui
.event
== STBTE__leftup
) {
3088 stbte__ui
.scrolling
= 0;
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
;
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
;
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
);
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
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
);
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
;
3136 stbte__ui
.brush_state
= STBTE__erase_any
;
3140 case STBTE__rightup
:
3141 if (STBTE__IS_MAP_ACTIVE()) {
3142 stbte__end_undo(tm
);
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
;
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
);
3168 stbte__set_link(tm
, stbte__ui
.sx
, stbte__ui
.sy
, -1,-1, STBTE__undo_block
);
3169 stbte__ui
.linking
= 0;
3174 case STBTE__rightdown
:
3175 if (STBTE__IS_ACTIVE(id
)) {
3177 stbte__ui
.linking
= 0;
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
);
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
);
3198 if (STBTE__IS_MAP_ACTIVE()) {
3199 stbte__end_undo(tm
);
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
)
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
;
3225 stbte__ui
.has_selection
= 0; // no selection until it completes
3226 stbte__activate_map(mapx
,mapy
);
3230 if (STBTE__IS_MAP_ACTIVE()) {
3231 if (stbte__ui
.dragging
) {
3232 stbte__drag_place(tm
, mapx
,mapy
);
3233 stbte__ui
.dragging
= 0;
3236 stbte__select_rect(tm
, stbte__ui
.sx
, stbte__ui
.sy
, mapx
, mapy
);
3241 case STBTE__rightdown
:
3242 stbte__ui
.has_selection
= 0;
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
);
3256 if (STBTE__IS_MAP_ACTIVE()) {
3257 stbte__fillrect(tm
, stbte__ui
.sx
, stbte__ui
.sy
, mapx
, mapy
, 1);
3261 case STBTE__rightdown
:
3262 if (STBTE__INACTIVE())
3263 stbte__activate_map(mapx
,mapy
);
3265 case STBTE__rightup
:
3266 if (STBTE__IS_MAP_ACTIVE()) {
3267 stbte__fillrect(tm
, stbte__ui
.sx
, stbte__ui
.sy
, mapx
, mapy
, 0);
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
);
3287 static void stbte__start_paste(stbte_tilemap
*tm
)
3289 if (stbte__ui
.has_copy
) {
3290 stbte__ui
.pasting
= 1;
3291 stbte__activate(STBTE__ID(STBTE__toolbarB
,3));
3295 static void stbte__toolbar(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3298 int estimated_width
= 13 * STBTE__num_tool
+ 8+8+ 120+4 - 30;
3299 int x
= x0
+ w
/2 - estimated_width
/2;
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
)
3307 if (i
== STBTE__tool_showgrid
&& stbte__ui
.show_grid
)
3309 if (i
== STBTE__tool_showlinks
&& stbte__ui
.show_links
)
3311 if (i
== STBTE__tool_fill
)
3313 #ifndef STBTE_ALLOW_LINK
3314 if (i
== STBTE__tool_link
|| i
== STBTE__tool_showlinks
)
3317 if (i
== STBTE__tool_undo
&& !stbte__undo_available(tm
))
3319 if (i
== STBTE__tool_redo
&& !stbte__redo_available(tm
))
3321 if (stbte__button_icon(STBTE__ctoolbar_button
, toolchar
[i
], x
, y
, 13, STBTE__ID(STBTE__toolbarA
, i
), highlight
, disable
)) {
3323 case STBTE__tool_eyedrop
:
3324 stbte__ui
.eyedrop_last_layer
= tm
->num_layers
; // flush eyedropper state
3328 stbte__ui
.has_selection
= 0;
3330 case STBTE__tool_showlinks
:
3331 stbte__ui
.show_links
= !stbte__ui
.show_links
;
3333 case STBTE__tool_showgrid
:
3334 stbte__ui
.show_grid
= (stbte__ui
.show_grid
+1)%3;
3336 case STBTE__tool_undo
:
3339 case STBTE__tool_redo
:
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);
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);
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
);
3358 #define STBTE__TEXTCOLOR(n) stbte__color_table[n][STBTE__text][STBTE__idle]
3360 static int stbte__info_value(const char *label
, int x
, int y
, int val
, int digits
, int id
)
3362 if (stbte__ui
.event
== STBTE__paint
) {
3363 int off
= 9-stbte__get_char_width(label
[0]);
3365 sprintf(text
, label
, digits
, val
);
3366 stbte__draw_text_core(x
+off
,y
, text
, 999, STBTE__TEXTCOLOR(STBTE__cpanel
),1);
3370 if (stbte__minibutton(STBTE__cmapsize
, x
,y
, '+', STBTE__ID2(id
,1,0)))
3371 val
+= (stbte__ui
.shift
? 10 : 1);
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;
3380 static void stbte__info(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3382 int mode
= stbte__ui
.panel
[STBTE__panel_info
].mode
;
3383 int s
= 11+7*tm
->digits
+4+15;
3389 tm
->max_x
= stbte__info_value("w:%*d",x
,y
, tm
->max_x
, tm
->digits
, STBTE__ID(STBTE__info
,0));
3394 tm
->max_y
= stbte__info_value("h:%*d",x
,y
, tm
->max_y
, tm
->digits
, STBTE__ID(STBTE__info
,1));
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);
3403 stbte__info_value(in_region
? "y:%*d" : "y:",x
,y
, (stbte__ui
.hot_id
>> 7)&4095, tm
->digits
, 0);
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);
3411 static void stbte__layers(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3413 static char *propmodes
[3] = {
3414 "default", "always", "never"
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);
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
));
3436 num_rows
= (y1
-y0
)/15;
3437 #ifndef STBTE_NO_PROPS
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
) {
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
);
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
));
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;
3471 static void stbte__categories(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3474 int num_rows
= h
/ s
;
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);
3486 for (i
=0; i
< tm
->num_categories
; ++i
) {
3487 if (i
+1 - tm
->category_scroll
>= 0 && i
+1 - tm
->category_scroll
< num_rows
) {
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
);
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
));
3498 static void stbte__tile_in_palette(stbte_tilemap
*tm
, int x
, int y
, int slot
)
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
) {
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
);
3512 if (stbte__button_core(id
))
3513 tm
->cur_tile
= slot
;
3518 static void stbte__palette_of_tiles(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3521 int num_vis_rows
= (h
-6) / tm
->palette_spacing_y
;
3522 int num_columns
= (w
-2-6) / tm
->palette_spacing_x
;
3525 int x1
= x0
+w
, y1
=y0
+h
;
3529 if (num_columns
== 0)
3532 num_total_rows
= (tm
->cur_palette_count
+ num_columns
-1) / num_columns
; // ceil()
3535 row
= -tm
->palette_scroll
;
3536 for (i
=0; i
< tm
->num_tiles
; ++i
) {
3537 stbte__tileinfo
*t
= &tm
->tiles
[i
];
3539 // filter based on category
3540 if (tm
->cur_category
>= 0 && t
->category_id
!= tm
->cur_category
)
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
);
3551 if (column
== num_columns
) {
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
));
3560 static float stbte__linear_remap(float n
, float x0
, float x1
, float y0
, float y1
)
3562 return (n
-x0
)/(x1
-x0
)*(y1
-y0
) + y0
;
3565 static float stbte__saved
;
3566 static void stbte__props_panel(stbte_tilemap
*tm
, int x0
, int y0
, int w
, int h
)
3568 int x1
= x0
+w
, y1
= y0
+h
;
3570 int y
= y0
+ 5, x
= x0
+2;
3571 int slider_width
= 60;
3575 if (!stbte__is_single_selection())
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
);
3584 char *s
= STBTE_PROP_NAME(i
, data
, p
);
3585 if (s
== NULL
) s
= "";
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
);
3595 stbte__draw_text(x
+13,y
+1,s
,x1
-(x
+13)-2,STBTE__TEXTCOLOR(STBTE__cpanel
));
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
) {
3605 if (v
> b
-a
) v
= b
-a
;
3606 p
[i
] = (float) (a
+v
); // @TODO undo
3608 switch (stbte__slider(x
, slider_width
, y
+7, b
-a
, &v
, STBTE__ID(STBTE__prop_int
,i
)))
3611 stbte__saved
= p
[i
];
3614 p
[i
] = (float) (a
+v
); // @TODO undo
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
);
3624 stbte__draw_text(x
+slider_width
+2,y
+2, s
, x1
-1-(x
+slider_width
+2), STBTE__TEXTCOLOR(STBTE__cpanel
));
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
);
3633 if (p
[i
] < a
|| p
[i
] > b
) {
3635 if (p
[i
] < a
) p
[i
] = a
;
3636 if (p
[i
] > b
) p
[i
] = b
;
3639 switch (stbte__float_control(x
, y
, 50, a
, b
, c
, "%8.4f", &p
[i
], STBTE__layer
,STBTE__ID(STBTE__prop_float
,i
))) {
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
);
3651 stbte__draw_text(x
+53,y
+1, s
, x1
-1-(x
+53), STBTE__TEXTCOLOR(STBTE__cpanel
));
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)
3665 printf("static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][STBTE__num_color_states] =\n");
3668 for (k
=0; k
< STBTE__num_color_modes
; ++k
) {
3669 for (j
=0; j
< STBTE__num_color_aspects
; ++j
) {
3671 for (i
=0; i
< STBTE__num_color_states
; ++i
) {
3672 printf("0x%06x, ", stbte__color_table
[k
][j
][i
]);
3676 if (k
+1 < STBTE__num_color_modes
)
3684 static void stbte__colorpicker(int x0
, int y0
, int w
, int h
)
3686 int x1
= x0
+w
, y1
= y0
+h
, x
,y
, i
;
3695 int color
= stbte__color_table
[stbte__cp_mode
][stbte__cp_aspect
][stbte__cp_index
];
3697 if (stbte__cp_altered
&& stbte__cp_index
== STBTE__idle
)
3698 color
= stbte__save
;
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
;
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();
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]);
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);
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
];
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
;
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))
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;
3765 static void stbte__editor_traverse(stbte_tilemap
*tm
)
3767 int i
,j
,i0
,j0
,i1
,j1
,n
;
3771 if (stbte__ui
.x0
== stbte__ui
.x1
|| stbte__ui
.y0
== stbte__ui
.y1
)
3774 stbte__prepare_tileinfo(tm
);
3776 stbte__compute_panel_locations(tm
); // @OPTIMIZE: we don't need to recompute this every time
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
);
3787 // step 1: traverse all the tilemap data...
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;
3796 if (i1
> tm
->max_x
) i1
= tm
->max_x
;
3797 if (j1
> tm
->max_y
) j1
= tm
->max_y
;
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
);
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
);
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
);
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
);
3841 if (stbte__ui
.event
== STBTE__paint
) {
3842 // draw the selection border
3843 if (stbte__ui
.has_selection
) {
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
));
3852 stbte__flush_delay(); // draw a dynamic link on top of the queued links
3854 #ifdef STBTE_ALLOW_LINK
3855 if (stbte__ui
.linking
&& STBTE__IS_MAP_HOT()) {
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
;
3867 color
= STBTE_LINK_COLOR_DISALLOWED
;
3868 stbte__draw_link(x0
,y0
,x1
,y1
, color
);
3872 stbte__flush_delay();
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
);
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));
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
);
3888 case STBTE__panel_info
:
3889 stbte__info(tm
,p
->x0
,p
->y0
,p
->width
,p
->height
);
3891 case STBTE__panel_layers
:
3892 stbte__layers(tm
,p
->x0
,p
->y0
,p
->width
,p
->height
);
3894 case STBTE__panel_categories
:
3895 stbte__categories(tm
,p
->x0
,p
->y0
,p
->width
,p
->height
);
3897 case STBTE__panel_colorpick
:
3898 #ifdef STBTE__COLORPICKER
3899 stbte__colorpicker(p
->x0
,p
->y0
,p
->width
,p
->height
);
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
);
3908 case STBTE__panel_props
:
3909 stbte__props_panel(tm
,p
->x0
,p
->y0
,p
->width
,p
->height
);
3912 // draw the panel side selectors
3913 for (j
=0; j
< 2; ++j
) {
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
);
3919 case 0: p
->side
= result
> 0 ? STBTE__side_left
: STBTE__side_right
; break;
3920 case 1: p
->delta_height
+= result
; break;
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;
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
;
3935 if (i
== STBTE__side_left
)
3936 width
= stbte__ui
.left_width
, x
+= stbte__region
[i
].width
+ 1;
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
;
3944 stbte__region
[i
].retracted
= 0.0;
3946 if (i
== STBTE__side_left
)
3947 stbte__ui
.left_width
= width
;
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;
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);
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);
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;
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;
3988 static void stbte__do_event(stbte_tilemap
*tm
)
3990 stbte__ui
.next_hot_id
= 0;
3991 stbte__editor_traverse(tm
);
3992 stbte__ui
.hot_id
= stbte__ui
.next_hot_id
;
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
) {
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;
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.
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);
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
;
4027 static void stbte__set_event(int event
, int x
, int y
)
4029 stbte__ui
.event
= event
;
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
;
4040 void stbte_draw(stbte_tilemap
*tm
)
4042 stbte__ui
.event
= STBTE__paint
;
4043 stbte__editor_traverse(tm
);
4046 void stbte_mouse_move(stbte_tilemap
*tm
, int x
, int y
, int shifted
, int scrollkey
)
4048 stbte__set_event(STBTE__mousemove
, x
,y
);
4049 stbte__ui
.shift
= shifted
;
4050 stbte__ui
.scrollkey
= scrollkey
;
4051 stbte__do_event(tm
);
4054 void stbte_mouse_button(stbte_tilemap
*tm
, int x
, int y
, int right
, int down
, int shifted
, int scrollkey
)
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
;
4062 stbte__do_event(tm
);
4065 void stbte_mouse_wheel(stbte_tilemap
*tm
, int x
, int y
, int vscroll
)
4067 // not implemented yet -- need different way of hittesting
4070 void stbte_action(stbte_tilemap
*tm
, enum stbte_action 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;
4093 void stbte_tick(stbte_tilemap
*tm
, float dt
)
4095 stbte__ui
.event
= STBTE__tick
;
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
4101 void stbte_mouse_sdl(stbte_tilemap
*tm
, const void *sdl_event
, float xs
, float ys
, int xo
, int yo
)
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
);
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
);
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
);
4118 case SDL_MOUSEWHEEL
:
4119 stbte_mouse_wheel(tm
, stbte__ui
.mx
, stbte__ui
.my
, event
->wheel
.y
);
4124 STBTE__NOTUSED(sdl_event
);
4132 #endif // STB_TILEMAP_EDITOR_IMPLEMENTATION