diff --git a/README.md b/README.md index 7fdfa36..4283d3a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ library | lastest version | category | description **stb_textedit.h** | 1.5 | UI | guts of a text editor for games etc implementing them from scratch **stb_dxt.h** | 1.04 | 3D graphics | Fabian "ryg" Giesen's real-time DXT compressor **stb_perlin.h** | 0.2 | 3D graphics | revised Perlin noise (3D input, 1D output) -**stb_tilemap_editor.h** | 0.20 | games | embeddable tilemap editor +**stb_tilemap_editor.h** | 0.30 | games | embeddable tilemap editor **stb_herringbone_wang_tile.h** | 0.6 | games | herringbone Wang tile map generator **stb_c_lexer.h** | 0.06 | parsing | simplify writing parsers for C-like languages **stb_divide.h** | 0.91 | math | more useful 32-bit modulus e.g. "euclidean divide" diff --git a/data/atari_8bit_font_revised.png b/data/atari_8bit_font_revised.png new file mode 100644 index 0000000..91c553c Binary files /dev/null and b/data/atari_8bit_font_revised.png differ diff --git a/stb_tilemap_editor.h b/stb_tilemap_editor.h index b4ce92b..161b0d7 100644 --- a/stb_tilemap_editor.h +++ b/stb_tilemap_editor.h @@ -1,14 +1,168 @@ -// stb_tilemap_editor.h - v0.20 - Sean Barrett - http://nothings.org/stb +// stb_tilemap_editor.h - v0.30 - Sean Barrett - http://nothings.org/stb // placed in the public domain - not copyrighted - first released 2014-09 // // Embeddable tilemap editor for C/C++ // // -// COMPILING +// TABLE OF CONTENTS +// FAQ +// How to compile/use the library +// Additional configuration macros +// API documentation +// Info on editing multiple levels +// Revision history +// Todo +// Credits +// License +// +// +// FAQ +// +// Q: What counts as a tilemap for this library? +// +// A: An array of rectangles, where each rectangle contains a small +// stack of images. +// +// Q: What are the limitations? +// +// A: Maps are limited to 4096x4096 in dimension. +// Each map square can only contain a stack of at most 32 images. +// A map can only use up to 32768 distinct image tiles. +// +// Q: How do I compile this? +// +// A: You need to #define several symbols before #including it, but only +// in one file. This will cause all the function definitions to be +// generated in that file. See the "HOW TO COMPILE" section. +// +// Q: What advantages does this have over a standalone editor? +// +// A: For one, you can integrate the editor into your game so you can +// flip between editing and testing without even switching windows. +// For another, you don't need an XML parser to get at the map data. +// +// Q: Can I live-edit my game maps? +// +// A: Not really, the editor keeps its own map representation. +// +// Q: How do I save and load maps? +// +// A: You have to do this yourself. The editor provides serialization +// functions (get & set) for reading and writing the map it holds. +// You can choose whatever format you want to store the map to on +// disk; you just need to provide functions to convert. (For example, +// I actually store the editor's map representation to disk basically +// as-is; then I have a single function that converts from the editor +// map representation to the game representation, which is used both +// to go from editor-to-game and from loaded-map-to-game.) +// +// Q: I want to have tiles change appearance based on what's +// adjacent, or other tile-display/substitution trickiness. +// +// A: You can do this when you convert from the editor's map +// representation to the game representation, but there's +// no way to show this live in the editor. +// +// Q: The editor appears to be put map location (0,0) at the top left? +// I want to use a different coordinate system in my game (e.g. y +// increasing upwards, or origin at the center). +// +// A: You can do this when you convert from the editor's map +// representation to the game representation. (Don't forget to +// translate link coordinates as well!) +// +// Q: The editor appears to put pixel (0,0) at the top left? I want +// to use a different coordinate system in my game. +// +// A: The editor defines an "editor pixel coordinate system" with +// (0,0) at the top left and requires you to display things in +// that coordinate system. You can freely remap those coordinates +// to anything you want on screen. +// +// Q: How do I scale the user interface? +// +// A: Since you do all the rendering, you can scale up all the rendering +// calls that the library makes to you. If you do, (a) you need +// to also scale up the mouse coordinates, and (b) you may want +// to scale the map display back down so that you're only scaling +// the UI and not everything. See the next question. +// +// Q: How do I scale the map display? +// +// A: Use stbte_set_spacing() to change the size that the map is displayed +// at. Note that the "callbacks" to draw tiles are used for both drawing +// the map and drawing the tile palette, so that callback may need to +// draw at two different scales. You should choose the scales to match +// You can tell them apart because the +// tile palette gets NULL for the property pointer. +// +// Q: How does object editing work? +// +// A: One way to think of this is that in the editor, you're placing +// spawners, not objects. Each spawner must be tile-aligned, because +// it's only a tile editor. Each tile (stack of layers) gets +// an associated set of properties, and it's up to you to +// determine what properties should appear for a given tile, +// based on e.g. the spawners that are in it. +// +// Q: How are properties themselves handled? +// +// A: All properties, regardless of UI behavior, are internally floats. +// Each tile has an array of floats associated with it, which is +// passed back to you when drawing the tiles so you can draw +// objects appropriately modified by the properties. +// +// Q: What if I want to have two different objects/spawners in +// one tile, both of which have their own properties? +// +// A: Make sure STBTE_MAX_PROPERTIES is large enough for the sum of +// properties in both objects, and then you have to explicitly +// map the property slot #s to the appropriate objects. They'll +// still all appear in a single property panel; there's no way +// to get multiple panels. +// +// Q: Can I do one-to-many linking? +// +// A: The library only supports one link per tile. However, you +// can have multiple tiles all link to a single tile. So, you +// can fake one-to-many linking by linking in the reverse +// direction. +// +// Q: What if I have two objects in the same tile, and they each +// need an independent link? Or I have two kinds of link associated +// with a single object? +// +// A: There is no way to do this. (Unless you can reverse one link.) +// +// Q: How does cut & paste interact with object properties & links? +// +// A: Currently the library has no idea which properties or links +// are associated with which layers of a tile. So currently, the +// library will only copy properties & links if the layer panel +// is set to allow all layers to be copied, OR if you set the +// "props" in the layer panel to "always". Similarly, you can +// set "props" to "none" so it will never copy. +// +// Q: What happens if the library gets a memory allocation failure +// while I'm editing? Will I lose my work? +// +// A: The library allocates all editor memory when you create +// the tilemap. It allocates a maximally-sized map and a +// fixed-size undo buffer (and the fixed-size copy buffer +// is static), and never allocates memory while it's running. +// So it can't fail due to running out of memory. +// +// Q: What happens if the library crashes while I'm editing? Will +// I lose my work? +// +// A: Yes. Save often. +// +// +// HOW TO COMPILE // // This header file contains both the header file and the // implementation file in one. To create the implementation, -// in one source file, define a few symbols first and then +// in one source file define a few symbols first and then // include this header: // // #define STB_TILEMAP_EDITOR_IMPLEMENTATION @@ -19,18 +173,72 @@ // // color = (r<<16)|(g<<8)|(b) // // void STBTE_DRAW_TILE(int x0, int y0, -// unsigned short id, int highlight); +// unsigned short id, int highlight, float *data); // // this draws the tile image identified by 'id' in one of several -// // highlight modes (see STBTE_drawmode_* in the header section, -// // x0,y0,highlight:int; id:unsigned short +// // highlight modes (see STBTE_drawmode_* in the header section); +// // if 'data' is NULL, it's drawing the tile in the palette; if 'data' +// // is not NULL, it's drawing a tile on the map, and that is the data +// // associated with that map tile // // #include "stb_tilemap_editor.h" // // Optionally you can define the following functions before the include; -// note these must be macros (which can just call a single function) so -// we can detect if you've defined them: +// note these must be macros (but they can just call a function) so +// this library can #ifdef to detect if you've defined them: // -// [[ support for these is not implemented yet ]] +// #define STBTE_PROP_TYPE(int n, short *tiledata, float *params) ... +// // Returns the type of the n'th property of a given tile, which +// // controls how it is edited. Legal types are: +// // 0 /* no editable property in this slot */ +// // STBTE_PROP_int /* uses a slider to adjust value */ +// // STBTE_PROP_float /* uses a weird multi-axis control */ +// // STBTE_PROP_bool /* uses a checkbox to change value */ +// // And you can bitwise-OR in the following flags: +// // STBTE_PROP_disabled +// // Note that all of these are stored as floats in the param array. +// // The integer slider is limited in precision based on the space +// // available on screen, so for wide-ranged integers you may want +// // to use floats instead. +// // +// // Since the tiledata is passed to you, you can choose which property +// // is bound to that slot based on that data. +// // +// // Changing the type of a parameter does not cause the underlying +// // value to be clamped to the type min/max except when the tile is +// // explicitly selected. +// +// #define STBTE_PROP_NAME(int n, short *tiledata, float *params) ... +// // these return a string with the name for slot #n in the float +// // property list for the tile. +// +// #define STBTE_PROP_MIN(int n, short *tiledata) ...your code here... +// #define STBTE_PROP_MAX(int n, short *tiledata) ...your code here... +// // These return the allowable range for the property values for +// // the specified slot. It is never called for boolean types. +// +// #define STBTE_PROP_FLOAT_SCALE(int n, short *tiledata, float *params) +// // This rescales the float control for a given property; by default +// // left mouse drags add integers, right mouse drags adds fractions, +// // but you can rescale this per-property. +// +// #define STBTE_FLOAT_CONTROL_GRANULARITY ... value ... +// // This returns the number of pixels of mouse motion necessary +// // to advance the object float control. Default is 4 +// +// #define STBTE_ALLOW_LINK(short *src, float *src_data, \ +// short *dest, float *dest_data) ...your code... +// // this returns true or false depending on whether you allow a link +// // to be drawn from a tile 'src' to a tile 'dest'. if you don't +// // define this, linking will not be supported +// +// #define STBTE_LINK_COLOR(short *src, float *src_data, \ +// short *dest, float *dest_data) ...your code... +// // return a color encoded as a 24-bit unsigned integer in the +// // form 0xRRGGBB. If you don't define this, default colors will +// // be used. +// +// +// [[ support for those below is not implemented yet ]] // // #define STBTE_HITTEST_TILE(x0,y0,id,mx,my) ...your code here... // // this returns true or false depending on whether the mouse @@ -39,11 +247,6 @@ // // this hittest based on the tile geometry, but if you have // // tiles whose images extend out of the tile, you'll need this. // -// #define STBTE_DRAW_ICON(x0,y0,id,highlight) ...your code here... -// // this must draw a tile image identified by 'id' but now the -// // tile image must be drawn to fit in the tile palette, which -// // means it cannot exceed your specified palette spacing. -// // ADDITIONAL CONFIGURATION // // The following symbols set static limits which determine how much @@ -54,8 +257,9 @@ // #define STBTE_MAX_TILEMAP_Y 200 // max 4096 // #define STBTE_MAX_LAYERS 8 // max 32 // #define STBTE_MAX_CATEGORIES 100 -// #define STBTE_UNDO_BUFFER_BYTES (1 << 20) // 1MB +// #define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB // #define STBTE_MAX_COPY 90000 // e.g. 300x300 +// #define STBTE_MAX_PROPERTIESERTIES 10 // max properties per tile // // API // @@ -71,21 +275,33 @@ // either approach allows cut&pasting between levels.) // // REVISION HISTORY -// -// 0.20 - 2014-09-27 - eraser tool, bugfixes, new colorscheme -// 0.10 - 2014-09-23 - initial release +// 0.30 properties release +// - properties panel for editing user-defined "object" properties +// - can link each tile to one other tile +// - keyboard interface +// - fix eraser tool bug (worked in complex cases, failed in simple) +// - undo/redo tools have visible disabled state +// - tiles on higher layers draw on top of adjacent lower-layer tiles +// 0.20 erasable release +// - eraser tool +// - fix bug when pasting into protected layer +// - better color scheme +// - internal-use color picker +// 0.10 initial release // // TODO // // Separate scroll state for each category // Implement paint bucket // Support STBTE_HITTEST_TILE above -// Support STBTE_HITTEST_ICON above // ?Cancel drags by clicking other button? - may be fixed -// Object properties (per-tile properties) // Finish support for toolbar at side // Layer name buttons grow to fill box // +// CREDITS +// +// Written by Sean Barrett, September & October 2014. +// // LICENSE // // This software has been placed in the public domain by its author. @@ -94,8 +310,6 @@ - - /////////////////////////////////////////////////////////////////////// // // HEADER SECTION @@ -113,6 +327,13 @@ enum STBTE_drawmode_emphasize = 1, }; +// these are the property types +#define STBTE_PROP_none 0 +#define STBTE_PROP_int 1 +#define STBTE_PROP_float 2 +#define STBTE_PROP_bool 3 +#define STBTE_PROP_disabled 4 + //////// // // creation @@ -165,6 +386,29 @@ extern void stbte_mouse_move(stbte_tilemap *tm, int x, int y, int shifted, int s extern void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, int shifted, int scrollkey); extern void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll); +// for keyboard, define your own mapping from keys to the following actions. +// this is totally optional, as all features are accessible with the mouse +enum stbte_action +{ + STBTE_tool_select, + STBTE_tool_brush, + STBTE_tool_erase, + STBTE_tool_rectangle, + STBTE_tool_eyedropper, + STBTE_tool_link, + STBTE_act_toggle_grid, + STBTE_act_toggle_links, + STBTE_act_undo, + STBTE_act_redo, + STBTE_act_cut, + STBTE_act_copy, + STBTE_act_paste, + STBTE_scroll_left, + STBTE_scroll_right, + STBTE_scroll_up, + STBTE_scroll_down, +}; +extern void stbte_action(stbte_tilemap *tm, enum stbte_action act); //////////////// // @@ -184,6 +428,14 @@ extern short* stbte_get_tile(stbte_tilemap *tm, int x, int y); // returns an array of shorts that is 'map_layers' in length. each short is // either one of the tile_id values from define_tile, or STBTE_EMPTY. +extern float *stbte_get_properties(stbte_tilemap *tm, int x, int y); +// get the property array associated with the tile at x,y. this is an +// array of floats that is STBTE_MAX_PROPERTIES in length; you have to +// interpret the slots according to the semantics you've chosen + +extern void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty); +// gets the link associated with the tile at x,y. + extern void stbte_set_dimensions(stbte_tilemap *tm, int max_x, int max_y); // set the dimensions of the level, overrides previous stbte_create_map() // values or anything the user has changed @@ -195,6 +447,13 @@ extern void stbte_clear_map(stbte_tilemap *tm); extern void stbte_set_tile(stbte_tilemap *tm, int x, int y, int layer, signed short tile); // tile is your tile_id from define_tile, or STBTE_EMPTY +extern void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val); +// set the value of the n'th slot of the tile at x,y + +extern void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty); +// set a link going from x,y to destx,desty. to force no link, +// use destx=desty=-1 + //////// // // optional @@ -252,9 +511,39 @@ extern void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layern #endif #ifndef STBTE_UNDO_BUFFER_BYTES -#define STBTE_UNDO_BUFFER_BYTES (1 << 20) // 1MB +#define STBTE_UNDO_BUFFER_BYTES (1 << 24) // 16 MB #endif +#ifndef STBTE_PROP_TYPE +#define STBTE__NO_PROPS +#define STBTE_PROP_TYPE(n,td,tp) 0 +#endif + +#ifndef STBTE_PROP_NAME +#define STBTE_PROP_NAME(n,td,tp) "" +#endif + +#ifndef STBTE_MAX_PROPERTIES +#define STBTE_MAX_PROPERTIES 10 +#endif + +#ifndef STBTE_PROP_MIN +#define STBTE_PROP_MIN(n,td,tp) 0 +#endif + +#ifndef STBTE_PROP_MAX +#define STBTE_PROP_MAX(n,td,tp) 100.0 +#endif + +#ifndef STBTE_PROP_FLOAT_SCALE +#define STBTE_PROP_FLOAT_SCALE(n,td,tp) 1 // default scale size +#endif + +#ifndef STBTE_FLOAT_CONTROL_GRANULARITY +#define STBTE_FLOAT_CONTROL_GRANULARITY 4 +#endif + + #define STBTE__UNDO_BUFFER_COUNT (STBTE_UNDO_BUFFER_BYTES>>1) #if STBTE_MAX_TILEMAP_X > 4096 || STBTE_MAX_TILEMAP_Y > 4096 @@ -267,7 +556,19 @@ extern void stbte_set_layername(stbte_tilemap *tm, int layer, const char *layern #error "Undo buffer size must be a power of 2" #endif -static int *stbte__colors; +#if STBTE_MAX_PROPERTIES == 0 +#define STBTE__NO_PROPS +#endif + +#ifdef STBTE__NO_PROPS +#undef STBTE_MAX_PROPERTIES +#define STBTE_MAX_PROPERTIES 1 // so we can declare arrays +#endif + +typedef struct +{ + short x,y; +} stbte__link; enum { @@ -386,6 +687,19 @@ static int stbte__color_table[STBTE__num_color_modes][STBTE__num_color_aspects][ #define STBTE_COLOR_TILEPALETTE_OUTLINE 0xffffff #define STBTE_COLOR_TILEPALETTE_BACKGROUND 0x000000 +#ifndef STBTE_LINK_COLOR +#define STBTE_LINK_COLOR(src,sp,dest,dp) 0x5030ff +#endif + +#ifndef STBTE_LINK_COLOR_DRAWING +#define STBTE_LINK_COLOR_DRAWING 0xff40ff +#endif + +#ifndef STBTE_LINK_COLOR_DISALLOWED +#define STBTE_LINK_COLOR_DISALLOWED 0x602060 +#endif + + // disabled, selected, down, over static unsigned char stbte__state_to_index[2][2][2][2] = { @@ -402,40 +716,40 @@ static unsigned char stbte__state_to_index[2][2][2][2] = #define STBTE__FONT_HEIGHT 9 static short stbte__font_offset[95+16]; -static short stbte__fontdata[762] = +static short stbte__fontdata[769] = { - 4,4,4,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, + 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, 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, 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, - 0,0,0,0,0,0,0,0,0,0,0,0,146,511,146,146,511,146,146,511,146,511,257,341,297, - 341,297,341,257,511,16,56,124,16,16,16,124,56,16,96,144,270,261,262,136,80, - 48,224,192,160,80,40,22,14,15,3,448,496,496,240,232,20,10,5,2,112,232,452, - 450,225,113,58,28,63,30,60,200,455,257,257,0,0,0,257,257,455,120,204,132, - 132,159,14,4,4,14,159,132,132,204,120,8,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,127,34,34,127,127,34,36,46,107,107, - 58,18,99,51,24,12,102,99,48,122,79,93,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,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,127,127,64,64,98,115,89,77,71,66,33, - 97,73,93,119,35,24,28,22,127,127,16,39,103,69,69,125,57,62,127,73,73,121, - 48,1,1,113,121,15,7,54,127,73,73,127,54,6,79,73,105,63,30,54,54,128,246,118, - 8,28,54,99,65,20,20,20,20,65,99,54,28,8,2,3,105,109,7,2,30,63,33,45,47,46, - 124,126,19,19,126,124,127,127,73,73,127,54,62,127,65,65,99,34,127,127,65, - 99,62,28,127,127,73,73,73,65,127,127,9,9,9,1,62,127,65,73,121,121,127,127, - 8,8,127,127,65,65,127,127,65,65,32,96,64,64,127,63,127,127,8,28,54,99,65, - 127,127,64,64,64,64,127,127,6,12,6,127,127,127,127,6,12,24,127,127,62,127, - 65,65,65,127,62,127,127,9,9,15,6,62,127,65,81,49,127,94,127,127,9,25,127, - 102,70,79,73,73,121,49,1,1,127,127,1,1,63,127,64,64,127,63,15,31,48,96,48, - 31,15,127,127,48,24,48,127,127,99,119,28,28,119,99,7,15,120,120,15,7,97,113, - 89,77,71,67,127,127,65,65,3,6,12,24,48,96,65,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,127,127,68,68,124,56,56,124, - 68,68,68,56,124,68,68,127,127,56,124,84,84,92,24,8,124,126,10,10,56,380,324, - 324,508,252,127,127,4,4,124,120,72,122,122,64,256,256,256,506,250,126,126, - 16,56,104,64,66,126,126,64,124,124,24,56,28,124,120,124,124,4,4,124,120,56, - 124,68,68,124,56,508,508,68,68,124,56,56,124,68,68,508,508,124,124,4,4,12, - 8,72,92,84,84,116,36,4,4,62,126,68,68,60,124,64,64,124,124,28,60,96,96,60, - 28,28,124,112,56,112,124,28,68,108,56,56,108,68,284,316,352,320,508,252,68, - 100,116,92,76,68,8,62,119,65,65,127,127,65,65,119,62,8,16,24,12,12,24,24, - 12,4, + 0,0,0,0,2,253,130,456,156,8,72,184,64,2,125,66,64,160,64,146,511,146,146, + 511,146,146,511,146,511,257,341,297,341,297,341,257,511,16,56,124,16,16,16, + 124,56,16,96,144,270,261,262,136,80,48,224,192,160,80,40,22,14,15,3,448,496, + 496,240,232,20,10,5,2,112,232,452,450,225,113,58,28,63,30,60,200,455,257, + 257,0,0,0,257,257,455,120,204,132,132,159,14,4,4,14,159,132,132,204,120,8, + 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, + 127,34,34,127,127,34,36,46,107,107,58,18,99,51,24,12,102,99,48,122,79,93, + 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, + 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, + 127,127,64,64,98,115,89,77,71,66,33,97,73,93,119,35,24,28,22,127,127,16,39, + 103,69,69,125,57,62,127,73,73,121,48,1,1,113,121,15,7,54,127,73,73,127,54, + 6,79,73,105,63,30,54,54,128,246,118,8,28,54,99,65,20,20,20,20,65,99,54,28, + 8,2,3,105,109,7,2,30,63,33,45,47,46,124,126,19,19,126,124,127,127,73,73,127, + 54,62,127,65,65,99,34,127,127,65,99,62,28,127,127,73,73,73,65,127,127,9,9, + 9,1,62,127,65,73,121,121,127,127,8,8,127,127,65,65,127,127,65,65,32,96,64, + 64,127,63,127,127,8,28,54,99,65,127,127,64,64,64,64,127,127,6,12,6,127,127, + 127,127,6,12,24,127,127,62,127,65,65,65,127,62,127,127,9,9,15,6,62,127,65, + 81,49,127,94,127,127,9,25,127,102,70,79,73,73,121,49,1,1,127,127,1,1,63,127, + 64,64,127,63,15,31,48,96,48,31,15,127,127,48,24,48,127,127,99,119,28,28,119, + 99,7,15,120,120,15,7,97,113,89,77,71,67,127,127,65,65,3,6,12,24,48,96,65, + 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, + 127,127,68,68,124,56,56,124,68,68,68,56,124,68,68,127,127,56,124,84,84,92, + 24,8,124,126,10,10,56,380,324,324,508,252,127,127,4,4,124,120,72,122,122, + 64,256,256,256,506,250,126,126,16,56,104,64,66,126,126,64,124,124,24,56,28, + 124,120,124,124,4,4,124,120,56,124,68,68,124,56,508,508,68,68,124,56,56,124, + 68,68,508,508,124,124,4,4,12,8,72,92,84,84,116,36,4,4,62,126,68,68,60,124, + 64,64,124,124,28,60,96,96,60,28,28,124,112,56,112,124,28,68,108,56,56,108, + 68,284,316,352,320,508,252,68,100,116,92,76,68,8,62,119,65,65,127,127,65, + 65,119,62,8,16,24,12,12,24,24,12,4, }; typedef struct @@ -458,6 +772,7 @@ enum STBTE__panel_colorpick, STBTE__panel_info, STBTE__panel_layers, + STBTE__panel_props, STBTE__panel_categories, STBTE__panel_tiles, @@ -480,8 +795,11 @@ enum STBTE__tool_rect, STBTE__tool_eyedrop, STBTE__tool_fill, + STBTE__tool_link, + + STBTE__tool_showgrid, + STBTE__tool_showlinks, - STBTE__tool_grid, STBTE__tool_undo, STBTE__tool_redo, // copy/cut/paste aren't included here because they're displayed differently @@ -490,7 +808,14 @@ enum }; // icons are stored in the 0-31 range of ASCII in the font -static int toolchar[] = { 26,24,25,20,23,22, 19,29,28, }; +static int toolchar[] = { 26,24,25,20,23,22,18, 19,17, 29,28, }; + +enum +{ + STBTE__propmode_default, + STBTE__propmode_always, + STBTE__propmode_never, +}; enum { @@ -520,23 +845,28 @@ typedef struct int x0,y0,x1,y1,color; } stbte__colorrect; +#define STBTE__MAX_DELAYRECT 256 + typedef struct { int tool, active_event; int active_id, hot_id, next_hot_id; int event; - int mx,my; + int mx,my, dx,dy; int ms_time; int shift, scrollkey; int initted; int side_extended[2]; - stbte__colorrect delayrect[1024]; + stbte__colorrect delayrect[STBTE__MAX_DELAYRECT]; int delaycount; - int show_grid; + int show_grid, show_links; int brush_state; // used to decide which kind of erasing int eyedrop_x, eyedrop_y, eyedrop_last_layer; int pasting, paste_x, paste_y; int scrolling, start_x, start_y; + int last_mouse_x, last_mouse_y; + int accum_x, accum_y; + int linking; int dragging; int drag_x, drag_y, drag_w, drag_h; int drag_offx, drag_offy, drag_dest_x, drag_dest_y; @@ -549,7 +879,13 @@ typedef struct float dt; stbte__panel panel[STBTE__num_panel]; short copybuffer[STBTE_MAX_COPY][STBTE_MAX_LAYERS]; - int copy_width,copy_height,has_copy; + float copyprops[STBTE_MAX_COPY][STBTE_MAX_PROPERTIES]; +#ifdef STBTE_ALLOW_LINK + stbte__link copylinks[STBTE_MAX_COPY]; +#endif + int copy_src_x, copy_src_y; + stbte_tilemap *copy_src; + int copy_width,copy_height,has_copy,copy_has_props; } stbte__ui_t; // there's only one UI system at a time, so we can globalize this @@ -579,6 +915,11 @@ enum struct stbte_tilemap { stbte__tiledata data[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_LAYERS]; + float props[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X][STBTE_MAX_PROPERTIES]; + #ifdef STBTE_ALLOW_LINK + stbte__link link[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X]; + int linkcount[STBTE_MAX_TILEMAP_Y][STBTE_MAX_TILEMAP_X]; + #endif int max_x, max_y, num_layers; int spacing_x, spacing_y; int palette_spacing_x, palette_spacing_y; @@ -588,12 +929,17 @@ struct stbte_tilemap int num_categories, category_scroll; stbte__tileinfo *tiles; int num_tiles, max_tiles, digits; + unsigned char undo_available_valid; + unsigned char undo_available; + unsigned char redo_available; + unsigned char padding; int cur_palette_count; int palette_scroll; int tileinfo_dirty; stbte__layer layerinfo[STBTE_MAX_LAYERS]; int has_layer_names; int layer_scroll; + int propmode; int solo_layer; int undo_pos, undo_len, redo_len; short background_tile; @@ -608,6 +954,7 @@ static void stbte__init_gui(void) int i,n; stbte__ui.initted = 1; // init UI state + stbte__ui.show_links = 1; for (i=0; i < STBTE__num_panel; ++i) { stbte__ui.panel[i].expanded = 1; // visible if not autohidden stbte__ui.panel[i].delta_height = 0; @@ -667,7 +1014,9 @@ stbte_tilemap *stbte_create_map(int map_x, int map_y, int map_layers, int spacin tm->undo_pos = 0; tm->category_scroll = 0; tm->layer_scroll = 0; + tm->propmode = 0; tm->has_layer_names = 0; + tm->undo_available_valid = 0; for (i=0; i < tm->num_layers; ++i) { tm->layerinfo[i].hidden = 0; @@ -754,7 +1103,7 @@ void stbte_get_dimensions(stbte_tilemap *tm, int *max_x, int *max_y) *max_y = tm->max_y; } -extern short* stbte_get_tile(stbte_tilemap *tm, int x, int y) +short* stbte_get_tile(stbte_tilemap *tm, int x, int y) { STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y) @@ -762,6 +1111,55 @@ extern short* stbte_get_tile(stbte_tilemap *tm, int x, int y) return tm->data[y][x]; } +float *stbte_get_properties(stbte_tilemap *tm, int x, int y) +{ + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); + if (x < 0 || x >= STBTE_MAX_TILEMAP_X || y < 0 || y >= STBTE_MAX_TILEMAP_Y) + return NULL; + return tm->props[y][x]; +} + +void stbte_get_link(stbte_tilemap *tm, int x, int y, int *destx, int *desty) +{ + int gx=-1,gy=-1; + STBTE_ASSERT(x >= 0 && x < tm->max_x && y >= 0 && y < tm->max_y); +#ifdef STBTE_ALLOW_LINK + if (x >= 0 && x < STBTE_MAX_TILEMAP_X && y >= 0 && y < STBTE_MAX_TILEMAP_Y) { + gx = tm->link[y][x].x; + gy = tm->link[y][x].y; + if (gx >= 0) + if (!STBTE_ALLOW_LINK(tm->data[y][x], tm->props[y][x], tm->data[gy][gx], tm->props[gy][gx])) + gx = gy = -1; + } +#endif + *destx = gx; + *desty = gy; +} + +void stbte_set_property(stbte_tilemap *tm, int x, int y, int n, float val) +{ + tm->props[y][x][n] = val; +} + +static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode); + +enum +{ + STBTE__undo_none, + STBTE__undo_record, + STBTE__undo_block, +}; + +void stbte_set_link(stbte_tilemap *tm, int x, int y, int destx, int desty) +{ +#ifdef STBTE_ALLOW_LINK + stbte__set_link(tm, x, y, destx, desty, STBTE__undo_none); +#else + STBTE_ASSERT(0); +#endif +} + + // returns an array of map_layers shorts. each short is either // one of the tile_id values from define_tile, or STBTE_EMPTY @@ -782,6 +1180,13 @@ void stbte_clear_map(stbte_tilemap *tm) tm->data[0][i][0] = tm->background_tile; for (j=1; j < tm->num_layers; ++j) tm->data[0][i][j] = STBTE__NO_TILE; + for (j=0; j < STBTE_MAX_PROPERTIES; ++j) + tm->props[0][i][j] = 0; + #ifdef STBTE_ALLOW_LINK + tm->link[0][i].x = -1; + tm->link[0][i].y = -1; + tm->linkcount[0][i] = 0; + #endif } } @@ -865,11 +1270,18 @@ static void stbte__prepare_tileinfo(stbte_tilemap *tm) // 2) end_of_redo_record // -2:short // -// 2) tile update +// 3) tile update // tile_id:short (-1..32767) +// x_coord:short +// y_coord:short +// layer:short (0..31) +// +// 4) property update (also used for links) +// value_hi:short +// value_lo:short // y_coord:short // x_coord:short -// layer:short (0..31) +// property:short (256+prop#) // // Since we use a circular buffer, we might overwrite the undo storage. // To detect this, before playing back commands we scan back and see @@ -877,7 +1289,17 @@ static void stbte__prepare_tileinfo(stbte_tilemap *tm) // it's wholly contained. // // When we read back through, we see them in reverse order, so -// we'll see the layer number first +// we'll see the layer number or property number first +// +// To be clearer about the circular buffer, there are two cases: +// 1. a single record is larger than the whole buffer. +// this is caught because the end_of_undo_record will +// get overwritten. +// 2. multiple records written are larger than the whole +// buffer, so some of them have been overwritten by +// the later ones. this is handled by explicitly tracking +// the undo length; we never try to parse the data that +// got overwritten // given two points, compute the length between them #define stbte__wrap(pos) ((pos) & (STBTE__UNDO_BUFFER_COUNT-1)) @@ -893,6 +1315,7 @@ static void stbte__write_undo(stbte_tilemap *tm, short value) tm->undo_pos = stbte__wrap(pos+1); tm->undo_len += (tm->undo_len < STBTE__UNDO_BUFFER_COUNT-2); tm->redo_len -= (tm->redo_len > 0); + tm->undo_available_valid = 0; } static void stbte__write_redo(stbte_tilemap *tm, short value) @@ -902,6 +1325,7 @@ static void stbte__write_redo(stbte_tilemap *tm, short value) tm->undo_pos = stbte__wrap(pos-1); tm->redo_len += (tm->redo_len < STBTE__UNDO_BUFFER_COUNT-2); tm->undo_len -= (tm->undo_len > 0); + tm->undo_available_valid = 0; } static void stbte__begin_undo(stbte_tilemap *tm) @@ -949,19 +1373,72 @@ static void stbte__redo_record(stbte_tilemap *tm, int x, int y, int i, int v) stbte__write_redo(tm, i); } -static void stbte__undo(stbte_tilemap *tm) +static float stbte__extract_float(short s0, short s1) +{ + union { float f; short s[2]; } converter; + converter.s[0] = s0; + converter.s[1] = s1; + return converter.f; +} + +static short stbte__extract_short(float f, int slot) +{ + union { float f; short s[2]; } converter; + converter.f = f; + return converter.s[slot]; +} + +static void stbte__undo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1) +{ + STBTE_ASSERT(stbte__ui.undoing); + if (stbte__ui.undoing) { + stbte__write_undo(tm, s1); + stbte__write_undo(tm, s0); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, 256+i); + } +} + +static void stbte__undo_record_prop_float(stbte_tilemap *tm, int x, int y, int i, float f) +{ + stbte__undo_record_prop(tm, x,y,i, stbte__extract_short(f,0), stbte__extract_short(f,1)); +} + +static void stbte__redo_record_prop(stbte_tilemap *tm, int x, int y, int i, short s0, short s1) +{ + stbte__write_redo(tm, s1); + stbte__write_redo(tm, s0); + stbte__write_redo(tm, x); + stbte__write_redo(tm, y); + stbte__write_redo(tm, 256+i); +} + + +static int stbte__undo_find_end(stbte_tilemap *tm) { // first scan through for the end record - int i, pos = stbte__wrap(tm->undo_pos-1), endpos; - for (i=0; i < tm->undo_len; i += 4) { + int i, pos = stbte__wrap(tm->undo_pos-1); + for (i=0; i < tm->undo_len;) { STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk); if (tm->undo_buffer[pos] == STBTE__undo_record) break; - pos = stbte__wrap(pos-4); + if (tm->undo_buffer[pos] >= 255) + pos = stbte__wrap(pos-5), i += 5; + else + pos = stbte__wrap(pos-4), i += 4; } if (i >= tm->undo_len) + return -1; + return pos; +} + +static void stbte__undo(stbte_tilemap *tm) +{ + int i, pos, endpos; + endpos = stbte__undo_find_end(tm); + if (endpos < 0) return; - endpos = pos; // we found a complete undo record pos = stbte__wrap(tm->undo_pos-1); @@ -978,29 +1455,62 @@ static void stbte__undo(stbte_tilemap *tm) y = tm->undo_buffer[stbte__wrap(pos-1)]; x = tm->undo_buffer[stbte__wrap(pos-2)]; v = tm->undo_buffer[stbte__wrap(pos-3)]; - pos = stbte__wrap(pos-4); - // write the redo entry - stbte__redo_record(tm, x, y, n, tm->data[y][x][n]); - // apply the undo entry - tm->data[y][x][n] = (short) v; + if (n >= 255) { + short s0=0,s1=0; + int v2 = tm->undo_buffer[stbte__wrap(pos-4)]; + pos = stbte__wrap(pos-5); + if (n > 255) { + float vf = stbte__extract_float(v, v2); + s0 = stbte__extract_short(tm->props[y][x][n-256], 0); + s1 = stbte__extract_short(tm->props[y][x][n-256], 1); + tm->props[y][x][n-256] = vf; + } else { +#ifdef STBTE_ALLOW_LINK + s0 = tm->link[y][x].x; + s1 = tm->link[y][x].y; + stbte__set_link(tm, x,y, v, v2, STBTE__undo_none); +#endif + } + // write the redo entry + stbte__redo_record_prop(tm, x, y, n-256, s0,s1); + // apply the undo entry + } else { + pos = stbte__wrap(pos-4); + // write the redo entry + stbte__redo_record(tm, x, y, n, tm->data[y][x][n]); + // apply the undo entry + tm->data[y][x][n] = (short) v; + } } // overwrite undo record with junk tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk; } -static void stbte__redo(stbte_tilemap *tm) +static int stbte__redo_find_end(stbte_tilemap *tm) { // first scan through for the end record - int i, pos = stbte__wrap(tm->undo_pos+1), endpos; - for (i=0; i < tm->redo_len; i += 4) { + int i, pos = stbte__wrap(tm->undo_pos+1); + for (i=0; i < tm->redo_len;) { STBTE_ASSERT(tm->undo_buffer[pos] != STBTE__undo_junk); if (tm->undo_buffer[pos] == STBTE__redo_record) break; - pos = stbte__wrap(pos+4); + if (tm->undo_buffer[pos] >= 255) + pos = stbte__wrap(pos+5), i += 5; + else + pos = stbte__wrap(pos+4), i += 4; } if (i >= tm->redo_len) - return; // this should only ever happen if redo buffer is empty - endpos = pos; + return -1; // this should only ever happen if redo buffer is empty + return pos; +} + +static void stbte__redo(stbte_tilemap *tm) +{ + // first scan through for the end record + int i, pos, endpos; + endpos = stbte__redo_find_end(tm); + if (endpos < 0) + return; // we found a complete redo record pos = stbte__wrap(tm->undo_pos+1); @@ -1014,22 +1524,113 @@ static void stbte__redo(stbte_tilemap *tm) y = tm->undo_buffer[stbte__wrap(pos+1)]; x = tm->undo_buffer[stbte__wrap(pos+2)]; v = tm->undo_buffer[stbte__wrap(pos+3)]; - pos = stbte__wrap(pos+4); - // don't use stbte__undo_record because it's guarded - stbte__write_undo(tm, tm->data[y][x][n]); - stbte__write_undo(tm, x); - stbte__write_undo(tm, y); - stbte__write_undo(tm, n); - tm->data[y][x][n] = (short) v; + if (n >= 255) { + int v2 = tm->undo_buffer[stbte__wrap(pos+4)]; + short s0=0,s1=0; + pos = stbte__wrap(pos+5); + if (n > 255) { + float vf = stbte__extract_float(v, v2); + s0 = stbte__extract_short(tm->props[y][x][n-256],0); + s1 = stbte__extract_short(tm->props[y][x][n-256],1); + tm->props[y][x][n-256] = vf; + } else { +#ifdef STBTE_ALLOW_LINK + s0 = tm->link[y][x].x; + s1 = tm->link[y][x].y; + stbte__set_link(tm, x,y,v,v2, STBTE__undo_none); +#endif + } + // don't use stbte__undo_record_prop because it's guarded + stbte__write_undo(tm, s1); + stbte__write_undo(tm, s0); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, n); + } else { + pos = stbte__wrap(pos+4); + // don't use stbte__undo_record because it's guarded + stbte__write_undo(tm, tm->data[y][x][n]); + stbte__write_undo(tm, x); + stbte__write_undo(tm, y); + stbte__write_undo(tm, n); + tm->data[y][x][n] = (short) v; + } } tm->undo_buffer[tm->undo_pos] = STBTE__undo_junk; } +// because detecting that undo is available +static void stbte__recompute_undo_available(stbte_tilemap *tm) +{ + tm->undo_available = (stbte__undo_find_end(tm) >= 0); + tm->redo_available = (stbte__redo_find_end(tm) >= 0); +} + +static int stbte__undo_available(stbte_tilemap *tm) +{ + if (!tm->undo_available_valid) + stbte__recompute_undo_available(tm); + return tm->undo_available; +} + +static int stbte__redo_available(stbte_tilemap *tm) +{ + if (!tm->undo_available_valid) + stbte__recompute_undo_available(tm); + return tm->redo_available; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef STBTE_ALLOW_LINK +static void stbte__set_link(stbte_tilemap *tm, int src_x, int src_y, int dest_x, int dest_y, int undo_mode) +{ + stbte__link *a; + STBTE_ASSERT(src_x >= 0 && src_x < STBTE_MAX_TILEMAP_X && src_y >= 0 && src_y < STBTE_MAX_TILEMAP_Y); + a = &tm->link[src_y][src_x]; + // check if it's a do nothing + if (a->x == dest_x && a->y == dest_y) + return; + if (undo_mode != STBTE__undo_none ) { + if (undo_mode == STBTE__undo_block) stbte__begin_undo(tm); + stbte__undo_record_prop(tm, src_x, src_y, -1, a->x, a->y); + if (undo_mode == STBTE__undo_block) stbte__end_undo(tm); + } + // check if there's an existing link + if (a->x >= 0) { + // decrement existing link refcount + STBTE_ASSERT(tm->linkcount[a->y][a->x] > 0); + --tm->linkcount[a->y][a->x]; + } + // increment new dest + if (dest_x >= 0) { + ++tm->linkcount[dest_y][dest_x]; + } + a->x = dest_x; + a->y = dest_y; +} +#endif + + static void stbte__draw_rect(int x0, int y0, int x1, int y1, unsigned int color) { STBTE_DRAW_RECT(x0,y0,x1,y1, color); } +static void stbte__draw_line(int x0, int y0, int x1, int y1, unsigned int color) +{ + int temp; + if (x1 < x0) temp=x0,x0=x1,x1=temp; + if (y1 < y0) temp=y0,y0=y1,y1=temp; + stbte__draw_rect(x0,y0,x1+1,y1+1,color); +} + +static void stbte__draw_link(int x0, int y0, int x1, int y1, unsigned int color) +{ + stbte__draw_line(x0,y0,x0,y1, color); + stbte__draw_line(x0,y1,x1,y1, color); +} + static void stbte__draw_frame(int x0, int y0, int x1, int y1, unsigned int color) { stbte__draw_rect(x0,y0,x1-1,y0+1,color); @@ -1110,7 +1711,7 @@ static int stbte__text_width(const char *str) static void stbte__draw_frame_delayed(int x0, int y0, int x1, int y1, int color) { - if (stbte__ui.delaycount < 1024) { + if (stbte__ui.delaycount < STBTE__MAX_DELAYRECT) { stbte__colorrect r = { x0,y0,x1,y1,color }; stbte__ui.delayrect[stbte__ui.delaycount++] = r; } @@ -1118,8 +1719,9 @@ static void stbte__draw_frame_delayed(int x0, int y0, int x1, int y1, int color) static void stbte__flush_delay(void) { - stbte__colorrect *r = stbte__ui.delayrect; + stbte__colorrect *r; int i; + r = stbte__ui.delayrect; for (i=0; i < stbte__ui.delaycount; ++i,++r) stbte__draw_frame(r->x0,r->y0,r->x1,r->y1,r->color); stbte__ui.delaycount = 0; @@ -1129,6 +1731,8 @@ static void stbte__activate(int id) { stbte__ui.active_id = id; stbte__ui.active_event = stbte__ui.event; + stbte__ui.accum_x = 0; + stbte__ui.accum_y = 0; } static int stbte__hittest(int x0, int y0, int x1, int y1, int id) @@ -1195,7 +1799,7 @@ static int stbte__button(int colormode, char *label, int x, int y, int textoff, return (stbte__button_core(id) == 1); } -static int stbte__button_icon(int colormode, char ch, int x, int y, int width, int id, int toggled) +static int stbte__button_icon(int colormode, char ch, int x, int y, int width, int id, int toggled, int disabled) { int x0=x,y0=y, x1=x+width,y1=y+STBTE__BUTTON_HEIGHT; int s = STBTE__BUTTON_INTERNAL_SPACING; @@ -1205,8 +1809,10 @@ static int stbte__button_icon(int colormode, char ch, int x, int y, int width, i if (stbte__ui.event == STBTE__paint) { char label[2] = { ch, 0 }; int pad = (9 - stbte__get_char_width(ch))/2; - stbte__draw_textbox(x0,y0,x1,y1, label,s+pad,s, colormode, STBTE__INDEX_FOR_ID(id,0,toggled)); + stbte__draw_textbox(x0,y0,x1,y1, label,s+pad,s, colormode, STBTE__INDEX_FOR_ID(id,disabled,toggled)); } + if (disabled) + return 0; return (stbte__button_core(id) == 1); } @@ -1287,6 +1893,104 @@ static int stbte__category_button(char *label, int x, int y, int width, int id, return (stbte__button_core(id) == 1); } +enum +{ + STBTE__none, + STBTE__begin, + STBTE__end, + STBTE__change, +}; + +// returns -1 if value changes, 1 at end of drag +static int stbte__slider(int x0, int w, int y, int range, int *value, int id) +{ + int x1 = x0+w; + int pos = *value * w / (range+1); + int over = stbte__hittest(x0,y-2,x1,y+3,id); + int event_mouse_move = STBTE__change; + switch (stbte__ui.event) { + case STBTE__paint: + stbte__draw_rect(x0,y,x1,y+1, 0x808080); + stbte__draw_rect(x0+pos-1,y-1,x0+pos+2,y+2, 0xffffff); + break; + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + event_mouse_move = STBTE__begin; + } + // fall through + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id)) { + int v = (stbte__ui.mx-x0)*(range+1)/w; + if (v < 0) v = 0; else if (v > range) v = range; + *value = v; + return event_mouse_move; + } + break; + case STBTE__leftup: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + return STBTE__end; + } + break; + } + return STBTE__none; +} + +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) +{ + int x1 = x0+w; + int y1 = y0+11; + int over = stbte__hittest(x0,y0,x1,y1,id); + switch (stbte__ui.event) { + case STBTE__paint: { + char text[32]; + sprintf(text, fmt ? fmt : "%6.2f", *value); + stbte__draw_textbox(x0,y0,x1,y1, text, 1,2, colormode, STBTE__INDEX_FOR_ID(id,0,0)); + break; + } + case STBTE__leftdown: + case STBTE__rightdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) + stbte__activate(id); + return STBTE__begin; + break; + case STBTE__leftup: + case STBTE__rightup: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + return STBTE__end; + } + break; + case STBTE__mousemove: + if (STBTE__IS_ACTIVE(id)) { + float v = *value, delta; + int ax = stbte__ui.accum_x/STBTE_FLOAT_CONTROL_GRANULARITY; + int ay = stbte__ui.accum_y/STBTE_FLOAT_CONTROL_GRANULARITY; + stbte__ui.accum_x -= ax*STBTE_FLOAT_CONTROL_GRANULARITY; + stbte__ui.accum_y -= ay*STBTE_FLOAT_CONTROL_GRANULARITY; + if (stbte__ui.shift) { + if (stbte__ui.active_event == STBTE__leftdown) + delta = ax * 16 + ay; + else + delta = ax / 16.0 + ay / 256.0; + } else { + if (stbte__ui.active_event == STBTE__leftdown) + delta = ax*10 + ay; + else + delta = ax * 0.1 + ay * 0.01; + } + v += delta * scale; + if (v < minv) v = minv; + if (v > maxv) v = maxv; + *value = v; + return STBTE__change; + } + break; + } + return STBTE__none; +} + static void stbte__scrollbar(int x, int y0, int y1, int *val, int v0, int v1, int num_vis, int id) { int over; @@ -1339,6 +2043,13 @@ static void stbte__compute_digits(stbte_tilemap *tm) tm->digits = 2; } +static int stbte__is_single_selection(void) +{ + return stbte__ui.has_selection + && stbte__ui.select_x0 == stbte__ui.select_x1 + && stbte__ui.select_y0 == stbte__ui.select_y1; +} + typedef struct { int width, height; @@ -1347,7 +2058,7 @@ typedef struct float retracted; } stbte__region_t; -stbte__region_t stbte__region[4]; +static stbte__region_t stbte__region[4]; #define STBTE__TOOLBAR_ICON_SIZE (9+2*2) #define STBTE__TOOLBAR_PASTE_SIZE (34+2*2) @@ -1360,12 +2071,17 @@ static void stbte__compute_panel_locations(stbte_tilemap *tm) int i, limit, w, k; int window_width = stbte__ui.x1 - stbte__ui.x0; int window_height = stbte__ui.y1 - stbte__ui.y0; - int min_width[STBTE__num_panel]={0,0,0,0,0,0}; - int height[STBTE__num_panel]={0,0,0,0,0,0}; - int panel_active[STBTE__num_panel]={1,0,1,1,1,1}; + int min_width[STBTE__num_panel]={0,0,0,0,0,0,0}; + int height[STBTE__num_panel]={0,0,0,0,0,0,0}; + int panel_active[STBTE__num_panel]={1,0,1,1,1,1,1}; int vpos[4] = { 0,0,0,0 }; stbte__panel *p = stbte__ui.panel; stbte__panel *pt = &p[STBTE__panel_toolbar]; +#ifdef STBTE__NO_PROPS + int props = 0; +#else + int props = 1; +#endif for (i=0; i < 4; ++i) { stbte__region[i].active = 0; @@ -1383,6 +2099,8 @@ static void stbte__compute_panel_locations(stbte_tilemap *tm) panel_active[STBTE__panel_colorpick ] = 1; #endif + panel_active[STBTE__panel_props ] = props && stbte__is_single_selection(); + // compute minimum widths for each panel (assuming they're on sides not top) min_width[STBTE__panel_info ] = 8 + 11 + 7*tm->digits+17+7; // estimate min width of "w:0000" min_width[STBTE__panel_colorpick ] = 120; @@ -1390,6 +2108,7 @@ static void stbte__compute_panel_locations(stbte_tilemap *tm) min_width[STBTE__panel_categories] = 4 + 42 + 5; // 42 is enough to show ~7 chars; 5 for scrollbar min_width[STBTE__panel_layers ] = 4 + 54 + 30*tm->has_layer_names; // 2 digits plus 3 buttons plus scrollbar min_width[STBTE__panel_toolbar ] = 4 + STBTE__TOOLBAR_PASTE_SIZE; // wide enough for 'Paste' button + min_width[STBTE__panel_props ] = 80; // narrowest info panel // compute minimum widths for left & right panels based on the above stbte__region[0].width = stbte__ui.left_width; @@ -1440,7 +2159,7 @@ static void stbte__compute_panel_locations(stbte_tilemap *tm) // layers limit = 6 + stbte__ui.panel[STBTE__panel_layers].delta_height; - height[STBTE__panel_layers] = (tm->num_layers > limit ? limit : tm->num_layers)*15 + 7 + (tm->has_layer_names ? 0 : 11); + height[STBTE__panel_layers] = (tm->num_layers > limit ? limit : tm->num_layers)*15 + 7 + (tm->has_layer_names ? 0 : 11) + props*13; // categories limit = 6 + stbte__ui.panel[STBTE__panel_categories].delta_height; @@ -1453,6 +2172,9 @@ static void stbte__compute_panel_locations(stbte_tilemap *tm) if (k == 0) k = 1; height[STBTE__panel_tiles] = ((tm->num_tiles+k-1)/k) * tm->palette_spacing_y + 8; + // properties panel + height[STBTE__panel_props] = 9 + STBTE_MAX_PROPERTIES*14; + // now compute the locations of all the panels for (i=0; i < STBTE__num_panel; ++i) { if (panel_active[i]) { @@ -1497,6 +2219,9 @@ enum STBTE__panel_sizer, // param panel ID STBTE__scrollbar_id, STBTE__colorpick_id, + STBTE__prop_flag, + STBTE__prop_float, + STBTE__prop_int, }; // id is: [ 24-bit data : 7-bit identifer ] @@ -1663,7 +2388,7 @@ static int stbte__erase_predict(stbte_tilemap *tm, short result[], int allow_any } } - if (allow_any != STBTE__erase_any) + if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all) return STBTE__erase_none; // apply layer filters, erase from top @@ -1684,7 +2409,6 @@ static int stbte__erase_predict(stbte_tilemap *tm, short result[], int allow_any return STBTE__erase_none; } - static int stbte__erase(stbte_tilemap *tm, int x, int y, int allow_any) { stbte__tileinfo *ti = tm->cur_tile >= 0 ? &tm->tiles[tm->cur_tile] : NULL; @@ -1737,7 +2461,7 @@ static int stbte__erase(stbte_tilemap *tm, int x, int y, int allow_any) } } - if (allow_any != STBTE__erase_any) + if (allow_any != STBTE__erase_any && allow_any != STBTE__erase_all) return STBTE__erase_none; // apply layer filters, erase from top @@ -1805,6 +2529,21 @@ static void stbte__eyedrop(stbte_tilemap *tm, int x, int y) } } +static int stbte__should_copy_properties(stbte_tilemap *tm) +{ + int i; + if (tm->propmode == STBTE__propmode_always) + return 1; + if (tm->propmode == STBTE__propmode_never) + return 0; + if (tm->solo_layer >= 0 || tm->cur_layer >= 0) + return 0; + for (i=0; i < tm->num_layers; ++i) + if (tm->layerinfo[i].hidden || tm->layerinfo[i].locked) + return 0; + return 1; +} + // compute the result of pasting into a tile non-destructively so we can preview it static void stbte__paste_stack(stbte_tilemap *tm, short result[], short dest[], short src[], int dragging) { @@ -1888,9 +2627,17 @@ static void stbte__select_rect(stbte_tilemap *tm, int x0, int y0, int x1, int y1 stbte__ui.select_y1 = (y0 < y1 ? y1 : y0); } +static void stbte__copy_properties(float *dest, float *src) +{ + int i; + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) + dest[i] = src[i]; +} + static void stbte__copy_cut(stbte_tilemap *tm, int cut) { int i,j,n,w,h,p=0; + int copy_props = stbte__should_copy_properties(tm); if (!stbte__ui.has_selection) return; w = stbte__ui.select_x1 - stbte__ui.select_x0 + 1; @@ -1927,6 +2674,14 @@ static void stbte__copy_cut(stbte_tilemap *tm, int cut) tm->data[j][i][n] = (n==0 ? tm->background_tile : -1); } } + if (copy_props) { + stbte__copy_properties(stbte__ui.copyprops[p], tm->props[j][i]); +#ifdef STBTE_ALLOW_LINK + stbte__ui.copylinks[p] = tm->link[j][i]; + if (cut) + stbte__set_link(tm, i,j,-1,-1, STBTE__undo_record); +#endif + } ++p; } } @@ -1935,7 +2690,26 @@ static void stbte__copy_cut(stbte_tilemap *tm, int cut) stbte__ui.copy_width = w; stbte__ui.copy_height = h; stbte__ui.has_copy = 1; - stbte__ui.has_selection = 0; + //stbte__ui.has_selection = 0; + stbte__ui.copy_has_props = copy_props; + stbte__ui.copy_src = tm; // used to give better semantics when copying links + stbte__ui.copy_src_x = stbte__ui.select_x0; + stbte__ui.copy_src_y = stbte__ui.select_y0; +} + +static int stbte__in_rect(int x, int y, int x0, int y0, int w, int h) +{ + return x >= x0 && x < x0+w && y >= y0 && y < y0+h; +} + +static int stbte__in_src_rect(int x, int y) +{ + return stbte__in_rect(x,y, stbte__ui.copy_src_x, stbte__ui.copy_src_y, stbte__ui.copy_width, stbte__ui.copy_height); +} + +static int stbte__in_dest_rect(int x, int y, int destx, int desty) +{ + return stbte__in_rect(x,y, destx, desty, stbte__ui.copy_width, stbte__ui.copy_height); } static void stbte__paste(stbte_tilemap *tm, int mapx, int mapy) @@ -1945,6 +2719,7 @@ static void stbte__paste(stbte_tilemap *tm, int mapx, int mapy) int i,j,k,p; int x = mapx - (w>>1); int y = mapy - (h>>1); + int copy_props = stbte__should_copy_properties(tm) && stbte__ui.copy_has_props; if (stbte__ui.has_copy == 0) return; stbte__begin_undo(tm); @@ -1965,22 +2740,52 @@ static void stbte__paste(stbte_tilemap *tm, int mapx, int mapy) } } } + if (copy_props) { +#ifdef STBTE_ALLOW_LINK + // need to decide how to paste a link, so there's a few cases + int destx = -1, desty = -1; + stbte__link *link = &stbte__ui.copylinks[p]; + + // check if link is within-rect + if (stbte__in_src_rect(link->x, link->y)) { + // new link should point to copy (but only if copy is within map) + destx = x + (link->x - stbte__ui.copy_src_x); + desty = y + (link->y - stbte__ui.copy_src_y); + } else if (tm == stbte__ui.copy_src) { + // if same map, then preserve link unless target is overwritten + if (!stbte__in_dest_rect(link->x,link->y,x,y)) { + destx = link->x; + desty = link->y; + } + } + // this is necessary for offset-copy, but also in case max_x/max_y has changed + if (destx < 0 || destx >= tm->max_x || desty < 0 || desty >= tm->max_y) + destx = -1, desty = -1; + stbte__set_link(tm, x+i, y+j, destx, desty, STBTE__undo_record); +#endif + for (k=0; k < STBTE_MAX_PROPERTIES; ++k) { + if (tm->props[y+j][x+i][k] != stbte__ui.copyprops[p][k]) + stbte__undo_record_prop_float(tm, x+i, y+j, k, tm->props[y+j][x+i][k]); + } + stbte__copy_properties(tm->props[y+j][x+i], stbte__ui.copyprops[p]); + } ++p; } } stbte__end_undo(tm); } -static void stbte__drag_update(stbte_tilemap *tm, int mapx, int mapy) +static void stbte__drag_update(stbte_tilemap *tm, int mapx, int mapy, int copy_props) { int w = stbte__ui.drag_w, h = stbte__ui.drag_h; - int ox,oy,i; + int ox,oy,i,deleted=0,written=0; short temp[STBTE_MAX_LAYERS]; short *data = NULL; if (!stbte__ui.shift) { ox = mapx - stbte__ui.drag_x; oy = mapy - stbte__ui.drag_y; if (ox >= 0 && ox < w && oy >= 0 && oy < h) { + deleted=1; for (i=0; i < tm->num_layers; ++i) temp[i] = tm->data[mapy][mapx][i]; data = temp; @@ -1989,26 +2794,83 @@ static void stbte__drag_update(stbte_tilemap *tm, int mapx, int mapy) } ox = mapx - stbte__ui.drag_dest_x; oy = mapy - stbte__ui.drag_dest_y; + // if this map square is in the target drag region if (ox >= 0 && ox < w && oy >= 0 && oy < h) { - if (data == NULL) { - for (i=0; i < tm->num_layers; ++i) - temp[i] = tm->data[mapy][mapx][i]; - data = temp; + // and the src map square is on the map + if (stbte__in_rect(stbte__ui.drag_x+ox, stbte__ui.drag_y+oy, 0, 0, tm->max_x, tm->max_y)) { + written = 1; + if (data == NULL) { + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + } + stbte__paste_stack(tm, data, data, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); + if (copy_props) { + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) { + if (tm->props[mapy][mapx][i] != tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i]) { + stbte__undo_record_prop_float(tm, mapx, mapy, i, tm->props[mapy][mapx][i]); + tm->props[mapy][mapx][i] = tm->props[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox][i]; + } + } + } } - stbte__paste_stack(tm, data, data, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); } if (data) { - for (i=0; i < tm->num_layers; ++i) + for (i=0; i < tm->num_layers; ++i) { if (tm->data[mapy][mapx][i] != data[i]) { stbte__undo_record(tm, mapx, mapy, i, tm->data[mapy][mapx][i]); tm->data[mapy][mapx][i] = data[i]; } + } } + #ifdef STBTE_ALLOW_LINK + if (copy_props) { + int overwritten=0, moved=0, copied=0; + // since this function is called on EVERY tile, we can fix up even tiles not + // involved in the move + + stbte__link *k; + // first, determine what src link ends up here + k = &tm->link[mapy][mapx]; // by default, it's the one currently here + if (deleted) // if dragged away, it's erased + k = NULL; + if (written) // if dragged into, it gets that link + k = &tm->link[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox]; + + // now check whether the *target* gets moved or overwritten + if (k && k->x >= 0) { + overwritten = stbte__in_rect(k->x, k->y, stbte__ui.drag_dest_x, stbte__ui.drag_dest_y, w, h); + if (!stbte__ui.shift) + moved = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h); + else + copied = stbte__in_rect(k->x, k->y, stbte__ui.drag_x , stbte__ui.drag_y , w, h); + } + + if (deleted || written || overwritten || moved || copied) { + // choose the final link value based on the above + if (k == NULL || k->x < 0) + stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record); + else if (moved || (copied && written)) { + // if we move the target, we update to point to the new target; + // or, if we copy the target and the source is part ofthe copy, then update to new target + int x = k->x + (stbte__ui.drag_dest_x - stbte__ui.drag_x); + int y = k->y + (stbte__ui.drag_dest_y - stbte__ui.drag_y); + if (!(x >= 0 && y >= 0 && x < tm->max_x && y < tm->max_y)) + x = -1, y = -1; + stbte__set_link(tm, mapx, mapy, x, y, STBTE__undo_record); + } else if (overwritten) { + stbte__set_link(tm, mapx, mapy, -1, -1, STBTE__undo_record); + } else + stbte__set_link(tm, mapx, mapy, k->x, k->y, STBTE__undo_record); + } + } + #endif } static void stbte__drag_place(stbte_tilemap *tm, int mapx, int mapy) { int i,j; + int copy_props = stbte__should_copy_properties(tm); int move_x = (stbte__ui.drag_dest_x - stbte__ui.drag_x); int move_y = (stbte__ui.drag_dest_y - stbte__ui.drag_y); if (move_x == 0 && move_y == 0) @@ -2021,103 +2883,109 @@ static void stbte__drag_place(stbte_tilemap *tm, int mapx, int mapy) if (move_y > 0 || (move_y == 0 && move_x > 0)) { for (j=tm->max_y-1; j >= 0; --j) for (i=tm->max_x-1; i >= 0; --i) - stbte__drag_update(tm,i,j); + stbte__drag_update(tm,i,j,copy_props); } else { for (j=0; j < tm->max_y; ++j) for (i=0; i < tm->max_x; ++i) - stbte__drag_update(tm,i,j); + stbte__drag_update(tm,i,j,copy_props); } stbte__end_undo(tm); stbte__ui.has_selection = 1; stbte__ui.select_x0 = stbte__ui.drag_dest_x; stbte__ui.select_y0 = stbte__ui.drag_dest_y; - stbte__ui.select_x1 = stbte__ui.select_x0 + stbte__ui.drag_w; - stbte__ui.select_y1 = stbte__ui.select_y0 + stbte__ui.drag_h; + stbte__ui.select_x1 = stbte__ui.select_x0 + stbte__ui.drag_w - 1; + stbte__ui.select_y1 = stbte__ui.select_y0 + stbte__ui.drag_h - 1; } +static void stbte__tile_paint(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy, int layer) +{ + int i; + int id = STBTE__IDMAP(mapx,mapy); + int x0=sx, y0=sy; + int x1=sx+tm->spacing_x, y1=sy+tm->spacing_y; + int over = stbte__hittest(x0,y0,x1,y1, id); + short *data = tm->data[mapy][mapx]; + short temp[STBTE_MAX_LAYERS]; + + if (STBTE__IS_MAP_HOT()) { + if (stbte__ui.pasting) { + int ox = mapx - stbte__ui.paste_x; + int oy = mapy - stbte__ui.paste_y; + if (ox >= 0 && ox < stbte__ui.copy_width && oy >= 0 && oy < stbte__ui.copy_height) { + stbte__paste_stack(tm, temp, tm->data[mapy][mapx], stbte__ui.copybuffer[oy*stbte__ui.copy_width+ox], 0); + data = temp; + } + } else if (stbte__ui.dragging) { + int ox,oy; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + + // if it's in the source area, remove things unless shift-dragging + ox = mapx - stbte__ui.drag_x; + oy = mapy - stbte__ui.drag_y; + if (!stbte__ui.shift && ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { + stbte__clear_stack(tm, temp); + } + + ox = mapx - stbte__ui.drag_dest_x; + oy = mapy - stbte__ui.drag_dest_y; + if (ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { + stbte__paste_stack(tm, temp, temp, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); + } + } else if (STBTE__IS_MAP_ACTIVE()) { + if (stbte__ui.tool == STBTE__tool_rect) { + if ((stbte__ui.ms_time & 511) < 380) { + int ex = ((stbte__ui.hot_id >> 19) & 4095); + int ey = ((stbte__ui.hot_id >> 7) & 4095); + int sx = stbte__ui.sx; + int sy = stbte__ui.sy; + + if ( ((mapx >= sx && mapx < ex+1) || (mapx >= ex && mapx < sx+1)) + && ((mapy >= sy && mapy < ey+1) || (mapy >= ey && mapy < sy+1))) { + int i; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + data = temp; + if (stbte__ui.active_event == STBTE__leftdown) + stbte__brush_predict(tm, temp); + else + stbte__erase_predict(tm, temp, STBTE__erase_any); + } + } + } + } + } + + if (STBTE__IS_HOT(id) && STBTE__INACTIVE() && !stbte__ui.pasting) { + if (stbte__ui.tool == STBTE__tool_brush) { + if ((stbte__ui.ms_time & 511) < 300) { + data = temp; + for (i=0; i < tm->num_layers; ++i) + temp[i] = tm->data[mapy][mapx][i]; + stbte__brush_predict(tm, temp); + } + } + } + + { + i = layer; + if (i == tm->solo_layer || (!tm->layerinfo[i].hidden && tm->solo_layer < 0)) + if (data[i] >= 0) + STBTE_DRAW_TILE(x0,y0, (unsigned short) data[i], 0, tm->props[mapy][mapx]); + } +} static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy) { int tool = stbte__ui.tool; - int i; int x0=sx, y0=sy; int x1=sx+tm->spacing_x, y1=sy+tm->spacing_y; int id = STBTE__IDMAP(mapx,mapy); int over = stbte__hittest(x0,y0,x1,y1, id); switch (stbte__ui.event) { case STBTE__paint: { - short *data = tm->data[mapy][mapx]; - short temp[STBTE_MAX_LAYERS]; - - if (STBTE__IS_MAP_HOT()) { - if (stbte__ui.pasting) { - int ox = mapx - stbte__ui.paste_x; - int oy = mapy - stbte__ui.paste_y; - if (ox >= 0 && ox < stbte__ui.copy_width && oy >= 0 && oy < stbte__ui.copy_height) { - stbte__paste_stack(tm, temp, tm->data[mapy][mapx], stbte__ui.copybuffer[oy*stbte__ui.copy_width+ox], 0); - data = temp; - } - } else if (stbte__ui.dragging) { - int ox,oy; - for (i=0; i < tm->num_layers; ++i) - temp[i] = tm->data[mapy][mapx][i]; - data = temp; - - // if it's in the source area, remove things unless shift-dragging - ox = mapx - stbte__ui.drag_x; - oy = mapy - stbte__ui.drag_y; - if (!stbte__ui.shift && ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { - stbte__clear_stack(tm, temp); - } - - ox = mapx - stbte__ui.drag_dest_x; - oy = mapy - stbte__ui.drag_dest_y; - if (ox >= 0 && ox < stbte__ui.drag_w && oy >= 0 && oy < stbte__ui.drag_h) { - stbte__paste_stack(tm, temp, temp, tm->data[stbte__ui.drag_y+oy][stbte__ui.drag_x+ox], !stbte__ui.shift); - } - } else if (STBTE__IS_MAP_ACTIVE()) { - if (stbte__ui.tool == STBTE__tool_rect) { - if ((stbte__ui.ms_time & 511) < 380) { - int ex = ((stbte__ui.hot_id >> 19) & 4095); - int ey = ((stbte__ui.hot_id >> 7) & 4095); - int sx = stbte__ui.sx; - int sy = stbte__ui.sy; - - if ( ((mapx >= sx && mapx < ex+1) || (mapx >= ex && mapx < sx+1)) - && ((mapy >= sy && mapy < ey+1) || (mapy >= ey && mapy < sy+1))) { - int i; - for (i=0; i < tm->num_layers; ++i) - temp[i] = tm->data[mapy][mapx][i]; - data = temp; - if (stbte__ui.active_event == STBTE__leftdown) - stbte__brush_predict(tm, temp); - else - stbte__erase_predict(tm, temp, STBTE__erase_any); - } - } - } - } - } - - if (STBTE__IS_HOT(id) && STBTE__INACTIVE() && !stbte__ui.pasting) { - if (stbte__ui.tool == STBTE__tool_brush) { - if ((stbte__ui.ms_time & 511) < 300) { - data = temp; - for (i=0; i < tm->num_layers; ++i) - temp[i] = tm->data[mapy][mapx][i]; - stbte__brush_predict(tm, temp); - } - } - } - - for (i=0; i < tm->num_layers; ++i) { - if (i == tm->solo_layer || (!tm->layerinfo[i].hidden && tm->solo_layer < 0)) - if (data[i] >= 0) - STBTE_DRAW_TILE(x0,y0, (unsigned short) data[i], 0); - if (i == 0 && stbte__ui.show_grid==1) - stbte__draw_halfframe(x0,y0,x0+tm->spacing_x, y0+tm->spacing_y, STBTE_COLOR_GRID); - } if (stbte__ui.pasting || stbte__ui.dragging || stbte__ui.scrolling) break; if (stbte__ui.scrollkey && !STBTE__IS_MAP_ACTIVE()) @@ -2135,12 +3003,30 @@ static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy) ry0 -= tm->spacing_y/2; rx1 += tm->spacing_x/2; ry1 += tm->spacing_y/2; - stbte__draw_frame_delayed(rx0-1,ry0-1,rx1+1,ry1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); + stbte__draw_frame(rx0-1,ry0-1,rx1+1,ry1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); break; } if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { - stbte__draw_frame_delayed(x0-1,y0-1,x1+1,y1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); + stbte__draw_frame(x0-1,y0-1,x1+1,y1+1, STBTE_COLOR_TILEMAP_HIGHLIGHT); } +#ifdef STBTE_ALLOW_LINK + if (stbte__ui.show_links && tm->link[mapy][mapx].x >= 0) { + int tx = tm->link[mapy][mapx].x; + int ty = tm->link[mapy][mapx].y; + int lx0,ly0,lx1,ly1; + if (STBTE_ALLOW_LINK(tm->data[mapy][mapx], tm->props[mapy][mapx], + tm->data[ty ][tx ], tm->props[ty ][tx ])) + { + lx0 = x0 + (tm->spacing_x >> 1) - 1; + ly0 = y0 + (tm->spacing_y >> 1) - 1; + lx1 = lx0 + (tx - mapx) * tm->spacing_x + 2; + ly1 = ly0 + (ty - mapy) * tm->spacing_y + 2; + stbte__draw_link(lx0,ly0,lx1,ly1, + STBTE_LINK_COLOR(tm->data[mapy][mapx], tm->props[mapy][mapx], + tm->data[ty ][tx ], tm->props[ty ][tx])); + } + } +#endif break; } } @@ -2232,6 +3118,41 @@ static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy) } break; +#ifdef STBTE_ALLOW_LINK + case STBTE__tool_link: + switch (stbte__ui.event) { + case STBTE__leftdown: + if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) { + stbte__activate(id); + stbte__ui.linking = 1; + stbte__ui.sx = mapx; + stbte__ui.sy = mapy; + // @TODO: undo + } + break; + case STBTE__leftup: + if (STBTE__IS_HOT(id) && STBTE__IS_MAP_ACTIVE()) { + if ((mapx != stbte__ui.sx || mapy != stbte__ui.sy) && + STBTE_ALLOW_LINK(tm->data[stbte__ui.sy][stbte__ui.sx], tm->props[stbte__ui.sy][stbte__ui.sx], + tm->data[mapy][mapx], tm->props[mapy][mapx])) + stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, mapx, mapy, STBTE__undo_block); + else + stbte__set_link(tm, stbte__ui.sx, stbte__ui.sy, -1,-1, STBTE__undo_block); + stbte__ui.linking = 0; + stbte__activate(0); + } + break; + + case STBTE__rightdown: + if (STBTE__IS_ACTIVE(id)) { + stbte__activate(0); + stbte__ui.linking = 0; + } + break; + } + break; +#endif + case STBTE__tool_erase: switch (stbte__ui.event) { case STBTE__mousemove: @@ -2335,23 +3256,41 @@ static void stbte__tile(stbte_tilemap *tm, int sx, int sy, int mapx, int mapy) } } +static void stbte__start_paste(stbte_tilemap *tm) +{ + if (stbte__ui.has_copy) { + stbte__ui.pasting = 1; + stbte__activate(STBTE__ID(STBTE__toolbarB,3)); + } +} + static void stbte__toolbar(stbte_tilemap *tm, int x0, int y0, int w, int h) { int i; - int estimated_width = 13 * STBTE__num_tool + 8+8+ 120+4; + int estimated_width = 13 * STBTE__num_tool + 8+8+ 120+4 - 30; int x = x0 + w/2 - estimated_width/2; int y = y0+1; for (i=0; i < STBTE__num_tool; ++i) { - int highlight=0; + int highlight=0, disable=0; highlight = (stbte__ui.tool == i); - if (i == STBTE__tool_undo || i == STBTE__tool_grid) + if (i == STBTE__tool_undo || i == STBTE__tool_showgrid) x += 8; - if (i == STBTE__tool_grid && stbte__ui.show_grid) - highlight=1; + if (i == STBTE__tool_showgrid && stbte__ui.show_grid) + highlight = 1; + if (i == STBTE__tool_showlinks && stbte__ui.show_links) + highlight = 1; if (i == STBTE__tool_fill) continue; - if (stbte__button_icon(STBTE__ctoolbar_button, toolchar[i], x, y, 13, STBTE__ID(STBTE__toolbarA, i), highlight)) { + #ifndef STBTE_ALLOW_LINK + if (i == STBTE__tool_link || i == STBTE__tool_showlinks) + disable = 1; + #endif + if (i == STBTE__tool_undo && !stbte__undo_available(tm)) + disable = 1; + if (i == STBTE__tool_redo && !stbte__redo_available(tm)) + disable = 1; + if (stbte__button_icon(STBTE__ctoolbar_button, toolchar[i], x, y, 13, STBTE__ID(STBTE__toolbarA, i), highlight, disable)) { switch (i) { case STBTE__tool_eyedrop: stbte__ui.eyedrop_last_layer = tm->num_layers; // flush eyedropper state @@ -2360,7 +3299,10 @@ static void stbte__toolbar(stbte_tilemap *tm, int x0, int y0, int w, int h) stbte__ui.tool = i; stbte__ui.has_selection = 0; break; - case STBTE__tool_grid: + case STBTE__tool_showlinks: + stbte__ui.show_links = !stbte__ui.show_links; + break; + case STBTE__tool_showgrid: stbte__ui.show_grid = (stbte__ui.show_grid+1)%3; break; case STBTE__tool_undo: @@ -2375,22 +3317,14 @@ static void stbte__toolbar(stbte_tilemap *tm, int x0, int y0, int w, int h) } x += 8; - if (stbte__button(STBTE__ctoolbar_button, "cut" , x, y,10, 40, STBTE__ID(STBTE__toolbarB,0), 0, !stbte__ui.has_selection)) { - if (stbte__ui.has_selection) - stbte__copy_cut(tm, 1); - } + if (stbte__button(STBTE__ctoolbar_button, "cut" , x, y,10, 40, STBTE__ID(STBTE__toolbarB,0), 0, !stbte__ui.has_selection)) + stbte__copy_cut(tm, 1); x += 42; - if (stbte__button(STBTE__ctoolbar_button, "copy" , x, y, 5, 40, STBTE__ID(STBTE__toolbarB,1), 0, !stbte__ui.has_selection)) { - if (stbte__ui.has_selection) - stbte__copy_cut(tm, 0); - } + if (stbte__button(STBTE__ctoolbar_button, "copy" , x, y, 5, 40, STBTE__ID(STBTE__toolbarB,1), 0, !stbte__ui.has_selection)) + stbte__copy_cut(tm, 0); x += 42; - if (stbte__button(STBTE__ctoolbar_button, "paste", x, y, 0, 40, STBTE__ID(STBTE__toolbarB,2), stbte__ui.pasting, !stbte__ui.has_copy)) { - if (stbte__ui.has_copy) { - stbte__ui.pasting = 1; - stbte__activate(STBTE__ID(STBTE__toolbarB,3)); - } - } + if (stbte__button(STBTE__ctoolbar_button, "paste", x, y, 0, 40, STBTE__ID(STBTE__toolbarB,2), stbte__ui.pasting, !stbte__ui.has_copy)) + stbte__start_paste(tm); } #define STBTE__TEXTCOLOR(n) stbte__color_table[n][STBTE__text][STBTE__idle] @@ -2443,15 +3377,18 @@ static void stbte__info(stbte_tilemap *tm, int x0, int y0, int w, int h) x = x0+2; stbte__draw_text(x,y,"brush:",40,STBTE__TEXTCOLOR(STBTE__cpanel)); if (tm->cur_tile >= 0) - STBTE_DRAW_TILE(x+43,y-3,tm->tiles[tm->cur_tile].id,1); + STBTE_DRAW_TILE(x+43,y-3,tm->tiles[tm->cur_tile].id,1,0); } static void stbte__layers(stbte_tilemap *tm, int x0, int y0, int w, int h) { - int i, y; + int i, y, n; int x1 = x0+w; int y1 = y0+h; int xoff = tm->has_layer_names ? 50 : 20; + static char *propmodes[3] = { + "default", "always", "never" + }; int num_rows; x0 += 2; y0 += 5; @@ -2462,6 +3399,9 @@ static void stbte__layers(stbte_tilemap *tm, int x0, int y0, int w, int h) y0 += 11; } num_rows = (y1-y0)/15; +#ifndef STBTE_NO_PROPS + --num_rows; +#endif y = y0; for (i=0; i < tm->num_layers; ++i) { char text[3], *str = (char *) tm->layerinfo[i].name; @@ -2482,7 +3422,15 @@ static void stbte__layers(stbte_tilemap *tm, int x0, int y0, int w, int h) y += 15; } } - stbte__scrollbar(x1-4, y0,y1-2, &tm->layer_scroll, 0, tm->num_layers, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__layer)); + stbte__scrollbar(x1-4, y0,y-2, &tm->layer_scroll, 0, tm->num_layers, num_rows, STBTE__ID(STBTE__scrollbar_id, STBTE__layer)); +#ifndef STBTE_NO_PROPS + n = stbte__text_width("prop:")+2; + stbte__draw_text(x0,y+2, "prop:", w, STBTE__TEXTCOLOR(STBTE__cpanel)); + i = w - n - 4; + if (i > 45) i = 45; + if (stbte__button(STBTE__clayer_button, propmodes[tm->propmode], x0+n,y,0,i, STBTE__ID(STBTE__layer,256), 0,0)) + tm->propmode = (tm->propmode+1)%3; +#endif } static void stbte__categories(stbte_tilemap *tm, int x0, int y0, int w, int h) @@ -2521,7 +3469,7 @@ static void stbte__tile_in_palette(stbte_tilemap *tm, int x, int y, int slot) switch (stbte__ui.event) { case STBTE__paint: stbte__draw_rect(x,y,x+tm->palette_spacing_x-1,y+tm->palette_spacing_x-1, STBTE_COLOR_TILEPALETTE_BACKGROUND); - STBTE_DRAW_TILE(x,y,t->id, slot == tm->cur_tile); + STBTE_DRAW_TILE(x,y,t->id, slot == tm->cur_tile,0); if (slot == tm->cur_tile) stbte__draw_frame_delayed(x-1,y-1,x+tm->palette_spacing_x,y+tm->palette_spacing_y, STBTE_COLOR_TILEPALETTE_OUTLINE); break; @@ -2574,8 +3522,107 @@ static void stbte__palette_of_tiles(stbte_tilemap *tm, int x0, int y0, int w, in 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)); } -static int stbte__cp_mode, stbte__cp_aspect, stbte__cp_state, stbte__cp_index, stbte__save, stbte__cp_altered, stbte__color_copy; +static float stbte__linear_remap(float n, float x0, float x1, float y0, float y1) +{ + return (n-x0)/(x1-x0)*(y1-y0) + y0; +} +static float stbte__saved; +static void stbte__props_panel(stbte_tilemap *tm, int x0, int y0, int w, int h) +{ + int x1 = x0+w, y1 = y0+h; + int i; + int y = y0 + 5, x = x0+2; + int slider_width = 60; + int mx,my; + float *p; + short *data; + if (!stbte__is_single_selection()) + return; + mx = stbte__ui.select_x0; + my = stbte__ui.select_y0; + p = tm->props[my][mx]; + data = tm->data[my][mx]; + for (i=0; i < STBTE_MAX_PROPERTIES; ++i) { + unsigned int n = STBTE_PROP_TYPE(i, data, p); + if (n) { + char *s = STBTE_PROP_NAME(i, data, p); + if (s == NULL) s = ""; + switch (n & 3) { + case STBTE_PROP_bool: { + int flag = (int) p[i]; + if (stbte__layerbutton(x,y, flag ? 'x' : ' ', STBTE__ID(STBTE__prop_flag,i), flag, 0, 2)) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i,flag); + p[i] = !flag; + stbte__end_undo(tm); + } + stbte__draw_text(x+13,y+1,s,x1-(x+13)-2,STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 13; + break; + } + case STBTE_PROP_int: { + int a = (int) STBTE_PROP_MIN(i,data,p); + int b = (int) STBTE_PROP_MAX(i,data,p); + int v = (int) p[i] - a; + if (a+v != p[i] || v < 0 || v > b-a) { + if (v < 0) v = 0; + if (v > b-a) v = b-a; + p[i] = a+v; // @TODO undo + } + switch (stbte__slider(x, slider_width, y+7, b-a, &v, STBTE__ID(STBTE__prop_int,i))) + { + case STBTE__begin: + stbte__saved = p[i]; + // fallthrough + case STBTE__change: + p[i] = a+v; // @TODO undo + break; + case STBTE__end: + if (p[i] != stbte__saved) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i,stbte__saved); + stbte__end_undo(tm); + } + break; + } + stbte__draw_text(x+slider_width+2,y+2, s, x1-1-(x+slider_width+2), STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 12; + break; + } + case STBTE_PROP_float: { + float a = (float) STBTE_PROP_MIN(i, data,p); + float b = (float) STBTE_PROP_MAX(i, data,p); + float c = STBTE_PROP_FLOAT_SCALE(i, data, p); + float old; + if (p[i] < a || p[i] > b) { + // @TODO undo + if (p[i] < a) p[i] = a; + if (p[i] > b) p[i] = b; + } + old = p[i]; + switch (stbte__float_control(x, y, 50, a, b, c, "%8.4f", &p[i], STBTE__layer,STBTE__ID(STBTE__prop_float,i))) { + case STBTE__begin: + stbte__saved = old; + break; + case STBTE__end: + if (stbte__saved != p[i]) { + stbte__begin_undo(tm); + stbte__undo_record_prop_float(tm,mx,my,i, stbte__saved); + stbte__end_undo(tm); + } + break; + } + stbte__draw_text(x+53,y+1, s, x1-1-(x+53), STBTE__TEXTCOLOR(STBTE__cpanel)); + y += 12; + break; + } + } + } + } +} + +static int stbte__cp_mode, stbte__cp_aspect, stbte__cp_state, stbte__cp_index, stbte__save, stbte__cp_altered, stbte__color_copy; #ifdef STBTE__COLORPICKER static void stbte__dump_colorstate(void) { @@ -2599,36 +3646,6 @@ static void stbte__dump_colorstate(void) printf("};\n"); } -static void stbte__slider(int x0, int w, int y, int *value, int id) -{ - int x1 = x0+w; - int pos = *value * w / 256; - int over = stbte__hittest(x0,y-2,x1,y+3,id); - switch (stbte__ui.event) { - case STBTE__paint: - stbte__draw_rect(x0,y,x1,y+1, 0x808080); - stbte__draw_rect(x0+pos-1,y-1,x0+pos+2,y+2, 0xffffff); - break; - case STBTE__leftdown: - if (STBTE__IS_HOT(id) && STBTE__INACTIVE()) - stbte__activate(id); - // fall through - case STBTE__mousemove: - if (STBTE__IS_ACTIVE(id)) { - int v = (stbte__ui.mx-x0)*256/w; - if (v < 0) v = 0; else if (v > 255) v = 255; - *value = v; - } - break; - case STBTE__leftup: - if (STBTE__IS_ACTIVE(id)) { - stbte__activate(0); - stbte__dump_colorstate(); - } - break; - } -} - static void stbte__colorpicker(int x0, int y0, int w, int h) { int x1 = x0+w, y1 = y0+h, x,y, i; @@ -2652,7 +3669,8 @@ static void stbte__colorpicker(int x0, int y0, int w, int h) rgb[0] = color >> 16; rgb[1] = (color>>8)&255; rgb[2] = color & 255; for (i=0; i < 3; ++i) { - stbte__slider(x+8,64, y, rgb+i, STBTE__ID2(STBTE__colorpick_id,3,i)); + if (stbte__slider(x+8,64, y, 255, rgb+i, STBTE__ID2(STBTE__colorpick_id,3,i)) > 0) + stbte__dump_colorstate(); y += 15; } if (stbte__ui.event != STBTE__paint && stbte__ui.event != STBTE__tick) @@ -2711,7 +3729,7 @@ static void stbte__colorpicker(int x0, int y0, int w, int h) static void stbte__editor_traverse(stbte_tilemap *tm) { - int i,j; + int i,j,i0,j0,i1,j1,n; if (tm == NULL) return; @@ -2732,31 +3750,61 @@ static void stbte__editor_traverse(stbte_tilemap *tm) } // step 1: traverse all the tilemap data... - // @OPTIMIZE crop to only region visible between UI widgets -- also necessary to avoid painting under it, etc - // @OPTIMIZE crop to visible region by computing correct i,j range - for (j=0; j < tm->max_y; ++j) { - int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y; - if (y + tm->spacing_y < stbte__ui.y0 || y > stbte__ui.y1) - continue; - for (i=0; i < tm->max_x; ++i) { - int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x; - if (x + tm->spacing_x >= stbte__ui.x0 && x < stbte__ui.x1) - stbte__tile(tm, x, y, i, j); + + i0 = (tm->scroll_x - tm->spacing_x) / tm->spacing_x; + j0 = (tm->scroll_y - tm->spacing_y) / tm->spacing_y; + i1 = (tm->scroll_x + stbte__ui.x1 - stbte__ui.x0) / tm->spacing_x + 1; + j1 = (tm->scroll_y + stbte__ui.y1 - stbte__ui.y0) / tm->spacing_y + 1; + + if (i0 < 0) i0 = 0; + if (j0 < 0) j0 = 0; + if (i1 > tm->max_x) i1 = tm->max_x; + if (j1 > tm->max_y) j1 = tm->max_y; + + if (stbte__ui.event == STBTE__paint) { + // draw all of layer 0, then all of layer 1, etc, instead of old + // way which drew entire stack of each tile at once + for (n=0; n < tm->num_layers; ++n) { + for (j=j0; j < j1; ++j) { + for (i=i0; i < i1; ++i) { + int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y; + stbte__tile_paint(tm, x, y, i, j, n); + } + } + if (n == 0 && stbte__ui.show_grid == 1) { + int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y; + for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x) + stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID); + for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y) + stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID); + } } } - // draw grid on top of everything - if (stbte__ui.show_grid == 2) { - int x = stbte__ui.x0 - tm->scroll_x; - int y = stbte__ui.y0 - tm->scroll_y; - for (j=0; j < tm->max_y; ++j, y += tm->spacing_y) - stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID); - for (i=0; i < tm->max_x; ++i, x += tm->spacing_x) - stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID); + if (stbte__ui.event == STBTE__paint) { + // draw grid on top of everything except UI + if (stbte__ui.show_grid == 2) { + int x = stbte__ui.x0 + i0 * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j0 * tm->spacing_y - tm->scroll_y; + for (i=0; x < stbte__ui.x1 && i <= i1; ++i, x += tm->spacing_x) + stbte__draw_rect(x, stbte__ui.y0, x+1, stbte__ui.y1, STBTE_COLOR_GRID); + for (j=0; y < stbte__ui.y1 && j <= j1; ++j, y += tm->spacing_y) + stbte__draw_rect(stbte__ui.x0, y, stbte__ui.x1, y+1, STBTE_COLOR_GRID); + } + } + + for (j=j0; j < j1; ++j) { + for (i=i0; i < i1; ++i) { + int x = stbte__ui.x0 + i * tm->spacing_x - tm->scroll_x; + int y = stbte__ui.y0 + j * tm->spacing_y - tm->scroll_y; + stbte__tile(tm, x, y, i, j); + } } - // draw the selection border if (stbte__ui.event == STBTE__paint) { + // draw the selection border if (stbte__ui.has_selection) { int x0,y0,x1,y1; x0 = stbte__ui.x0 + (stbte__ui.select_x0 ) * tm->spacing_x - tm->scroll_x; @@ -2765,6 +3813,26 @@ static void stbte__editor_traverse(stbte_tilemap *tm) y1 = stbte__ui.y0 + (stbte__ui.select_y1 + 1) * tm->spacing_y - tm->scroll_y + 1; stbte__draw_frame(x0,y0,x1,y1, (stbte__ui.ms_time & 256 ? STBTE_COLOR_SELECTION_OUTLINE1 : STBTE_COLOR_SELECTION_OUTLINE2)); } + + stbte__flush_delay(); // draw a dynamic link on top of the queued links + + #ifdef STBTE_ALLOW_LINK + if (stbte__ui.linking && STBTE__IS_MAP_HOT()) { + int x0,y0,x1,y1; + int color; + int ex = ((stbte__ui.hot_id >> 19) & 4095); + int ey = ((stbte__ui.hot_id >> 7) & 4095); + x0 = stbte__ui.x0 + (stbte__ui.sx ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)+1; + y0 = stbte__ui.y0 + (stbte__ui.sy ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)+1; + x1 = stbte__ui.x0 + (ex ) * tm->spacing_x - tm->scroll_x + (tm->spacing_x>>1)-1; + y1 = stbte__ui.y0 + (ey ) * tm->spacing_y - tm->scroll_y + (tm->spacing_y>>1)-1; + 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])) + color = STBTE_LINK_COLOR_DRAWING; + else + color = STBTE_LINK_COLOR_DISALLOWED; + stbte__draw_link(x0,y0,x1,y1, color); + } + #endif } stbte__flush_delay(); @@ -2802,6 +3870,9 @@ static void stbte__editor_traverse(stbte_tilemap *tm) 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]); stbte__palette_of_tiles(tm,p->x0,p->y0,p->width,p->height); break; + case STBTE__panel_props: + stbte__props_panel(tm,p->x0,p->y0,p->width,p->height); + break; } // draw the panel side selectors for (j=0; j < 2; ++j) { @@ -2860,6 +3931,11 @@ static void stbte__editor_traverse(stbte_tilemap *tm) stbte__draw_text (x-w/2,y-4, stbte__ui.alert_msg, w+1, 0xff8040); } +#ifdef STBTE_SHOW_CURSOR + if (stbte__ui.event == STBTE__paint) + stbte__draw_bitmap(stbte__ui.mx, stbte__ui.my, stbte__get_char_width(26), stbte__get_char_bitmap(26), 0xe0e0e0); +#endif + if (stbte__ui.event == STBTE__tick && stbte__ui.alert_msg) { stbte__ui.alert_timer -= stbte__ui.dt; if (stbte__ui.alert_timer < 0) { @@ -2890,6 +3966,7 @@ static void stbte__do_event(stbte_tilemap *tm) stbte__end_undo(tm); stbte__ui.scrolling = 0; stbte__ui.dragging = 0; + stbte__ui.linking = 0; } } } @@ -2917,6 +3994,12 @@ static void stbte__set_event(int event, int x, int y) stbte__ui.event = event; stbte__ui.mx = x; stbte__ui.my = y; + stbte__ui.dx = x - stbte__ui.last_mouse_x; + stbte__ui.dy = y - stbte__ui.last_mouse_y; + stbte__ui.last_mouse_x = x; + stbte__ui.last_mouse_y = y; + stbte__ui.accum_x += stbte__ui.dx; + stbte__ui.accum_y += stbte__ui.dy; } void stbte_draw(stbte_tilemap *tm) @@ -2946,7 +4029,30 @@ void stbte_mouse_button(stbte_tilemap *tm, int x, int y, int right, int down, in void stbte_mouse_wheel(stbte_tilemap *tm, int x, int y, int vscroll) { + // not implemented yet -- need different way of hittesting +} +void stbte_action(stbte_tilemap *tm, enum stbte_action act) +{ + switch (act) { + case STBTE_tool_select: stbte__ui.tool = STBTE__tool_select; break; + case STBTE_tool_brush: stbte__ui.tool = STBTE__tool_brush; break; + case STBTE_tool_erase: stbte__ui.tool = STBTE__tool_erase; break; + case STBTE_tool_rectangle: stbte__ui.tool = STBTE__tool_rect; break; + case STBTE_tool_eyedropper: stbte__ui.tool = STBTE__tool_eyedrop; break; + case STBTE_tool_link: stbte__ui.tool = STBTE__tool_link; break; + case STBTE_act_toggle_grid: stbte__ui.show_grid = (stbte__ui.show_grid+1) % 3; break; + case STBTE_act_toggle_links: stbte__ui.show_links ^= 1; break; + case STBTE_act_undo: stbte__undo(tm); break; + case STBTE_act_redo: stbte__redo(tm); break; + case STBTE_act_cut: stbte__copy_cut(tm, 1); break; + case STBTE_act_copy: stbte__copy_cut(tm, 0); break; + case STBTE_act_paste: stbte__start_paste(tm); break; + case STBTE_scroll_left: tm->scroll_x -= tm->spacing_x; break; + case STBTE_scroll_right: tm->scroll_x += tm->spacing_x; break; + case STBTE_scroll_up: tm->scroll_y -= tm->spacing_y; break; + case STBTE_scroll_down: tm->scroll_y += tm->spacing_y; break; + } } void stbte_tick(stbte_tilemap *tm, float dt) diff --git a/tests/tilemap_editor_integration_example.c b/tests/tilemap_editor_integration_example.c new file mode 100644 index 0000000..ea5dee7 --- /dev/null +++ b/tests/tilemap_editor_integration_example.c @@ -0,0 +1,193 @@ +// This isn't compilable as-is, as it was extracted from a working +// integration-in-a-game and makes reference to symbols from that game. + +#include +#include +#include "game.h" +#include "SDL.h" +#include "stb_tilemap_editor.h" + +extern void editor_draw_tile(int x, int y, unsigned short tile, int mode, float *props); +extern void editor_draw_rect(int x0, int y0, int x1, int y1, unsigned char r, unsigned char g, unsigned char b); + +static int is_platform(short *tiles); +static unsigned int prop_type(int n, short *tiles); +static char *prop_name(int n, short *tiles); +static float prop_range(int n, short *tiles, int is_max); +static int allow_link(short *src, short *dest); + +#define STBTE_MAX_PROPERTIES 8 + +#define STBTE_PROP_TYPE(n, tiledata, p) prop_type(n,tiledata) +#define STBTE_PROP_NAME(n, tiledata, p) prop_name(n,tiledata) +#define STBTE_PROP_MIN(n, tiledata, p) prop_range(n,tiledata,0) +#define STBTE_PROP_MAX(n, tiledata, p) prop_range(n,tiledata,1) +#define STBTE_PROP_FLOAT_SCALE(n,td,p) (0.1) + +#define STBTE_ALLOW_LINK(srctile, srcprop, desttile, destprop) \ + allow_link(srctile, desttile) + +#define STBTE_LINK_COLOR(srctile, srcprop, desttile, destprop) \ + (is_platform(srctile) ? 0xff80ff : 0x808040) + +#define STBTE_DRAW_RECT(x0,y0,x1,y1,c) \ + editor_draw_rect(x0,y0,x1,y1,(c)>>16,((c)>>8)&255,(c)&255) + +#define STBTE_DRAW_TILE(x,y,id,highlight,props) \ + editor_draw_tile(x,y,id,highlight,props) + + + +#define STB_TILEMAP_EDITOR_IMPLEMENTATION +#include "stb_tilemap_editor.h" + +stbte_tilemap *edit_map; + +void editor_key(enum stbte_action act) +{ + stbte_action(edit_map, act); +} + +void editor_process_sdl_event(SDL_Event *e) +{ + switch (e->type) { + case SDL_MOUSEMOTION: + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEWHEEL: + stbte_mouse_sdl(edit_map, e, 1.0f/editor_scale,1.0f/editor_scale,0,0); + break; + + case SDL_KEYDOWN: + if (in_editor) { + switch (e->key.keysym.sym) { + case SDLK_RIGHT: editor_key(STBTE_scroll_right); break; + case SDLK_LEFT : editor_key(STBTE_scroll_left ); break; + case SDLK_UP : editor_key(STBTE_scroll_up ); break; + case SDLK_DOWN : editor_key(STBTE_scroll_down ); break; + } + switch (e->key.keysym.scancode) { + case SDL_SCANCODE_S: editor_key(STBTE_tool_select); break; + case SDL_SCANCODE_B: editor_key(STBTE_tool_brush ); break; + case SDL_SCANCODE_E: editor_key(STBTE_tool_erase ); break; + case SDL_SCANCODE_R: editor_key(STBTE_tool_rectangle ); break; + case SDL_SCANCODE_I: editor_key(STBTE_tool_eyedropper); break; + case SDL_SCANCODE_L: editor_key(STBTE_tool_link); break; + case SDL_SCANCODE_G: editor_key(STBTE_act_toggle_grid); break; + } + if ((e->key.keysym.mod & KMOD_CTRL) && !(e->key.keysym.mod & ~KMOD_CTRL)) { + switch (e->key.keysym.scancode) { + case SDL_SCANCODE_X: editor_key(STBTE_act_cut ); break; + case SDL_SCANCODE_C: editor_key(STBTE_act_copy ); break; + case SDL_SCANCODE_V: editor_key(STBTE_act_paste); break; + case SDL_SCANCODE_Z: editor_key(STBTE_act_undo ); break; + case SDL_SCANCODE_Y: editor_key(STBTE_act_redo ); break; + } + } + } + break; + } +} + +void editor_init(void) +{ + int i; + edit_map = stbte_create_map(20,14, 8, 16,16, 100); + + stbte_set_background_tile(edit_map, T_empty); + + for (i=0; i < T__num_types; ++i) { + if (i != T_reserved1 && i != T_entry && i != T_doorframe) + stbte_define_tile(edit_map, 0+i, 1, "Background"); + } + stbte_define_tile(edit_map, 256+O_player , 8, "Char"); + stbte_define_tile(edit_map, 256+O_robot , 8, "Char"); + for (i=O_lockeddoor; i < O__num_types-2; ++i) + if (i == O_platform || i == O_vplatform) + stbte_define_tile(edit_map, 256+i, 4, "Object"); + else + stbte_define_tile(edit_map, 256+i, 2, "Object"); + + //stbte_set_layername(edit_map, 0, "background"); + //stbte_set_layername(edit_map, 1, "objects"); + //stbte_set_layername(edit_map, 2, "platforms"); + //stbte_set_layername(edit_map, 3, "characters"); +} + +static int is_platform(short *tiles) +{ + // platforms are only on layer #2 + return tiles[2] == 256 + O_platform || tiles[2] == 256 + O_vplatform; +} + +static int is_object(short *tiles) +{ + return (tiles[1] >= 256 || tiles[2] >= 256 || tiles[3] >= 256); +} + +static unsigned int prop_type(int n, short *tiles) +{ + if (is_platform(tiles)) { + static unsigned int platform_types[STBTE_MAX_PROPERTIES] = { + STBTE_PROP_bool, // phantom + STBTE_PROP_int, // x_adjust + STBTE_PROP_int, // y_adjust + STBTE_PROP_float, // width + STBTE_PROP_float, // lspeed + STBTE_PROP_float, // rspeed + STBTE_PROP_bool, // autoreturn + STBTE_PROP_bool, // one-shot + // remainder get 0, means 'no property in this slot' + }; + return platform_types[n]; + } else if (is_object(tiles)) { + if (n == 0) + return STBTE_PROP_bool; + } + return 0; +} + +static char *prop_name(int n, short *tiles) +{ + if (is_platform(tiles)) { + static char *platform_vars[STBTE_MAX_PROPERTIES] = { + "phantom", + "x_adjust", + "y_adjust", + "width", + "lspeed", + "rspeed", + "autoreturn", + "one-shot", + }; + return platform_vars[n]; + } + return "phantom"; +} + +static float prop_range(int n, short *tiles, int is_max) +{ + if (is_platform(tiles)) { + static float ranges[8][2] = { + { 0, 1 }, // phantom-flag, range is ignored + { -15, 15 }, // x_adjust + { -15, 15 }, // y_adjust + { 0, 6 }, // width + { 0, 10 }, // lspeed + { 0, 10 }, // rspeed + { 0, 1 }, // autoreturn, range is ignored + { 0, 1 }, // one-shot, range is ignored + }; + return ranges[n][is_max]; + } + return 0; +} + +static int allow_link(short *src, short *dest) +{ + if (is_platform(src)) + return dest[1] == 256+O_lever; + if (src[1] == 256+O_endpoint) + return is_platform(dest); + return 0; +}