v4k-git-backup/tools/editor/editor2.c

472 lines
18 KiB
C

/*
<!-- editor = tree of nodes. levels and objects are nodes, and their widgets are also nodes -->
<!-- you can perform actions on nodes, with or without descendants, top-bottom or bottom-top -->
<!-- these operations include load/save, undo/redo, reset, play/render, ddraw, etc -->
<!-- nodes are saved to disk as a filesystem layout: parents are folders, and leafs are files -->
<!-- network replication can be done by external tools by comparing the filesystems and by sending the resulting zipped .diff -->
- [ ] Editor: Gizmos✱, scene tree, property editor✱, load/save✱, undo/redo✱, copy/paste, on/off (vis,tick,ddraw,log), vcs.
- [ ] Editor: Scenenode pass: node singleton display, node console, node labels, node outlines✱.<!-- node == gameobj ? -->
- [ ] Editor: Debug pass: toggles on/off (billboards✱, materials, un/lit, cast shadows, wireframe, skybox✱/mie✱, fog/atmosphere, collide✱, physics).
- [ ] Editor: Level pass: volumes, triggers, platforms, level streaming.
- [ ] Editor: Edit pass: Procedural content, brushes, noise and CSG.
- [ ] Editor: GUI pass: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
*/
#include "v4k.c"
#include "editor2.h" // old editor interface
#define ui_push_hspace(px) \
(int xx = px; xx; xx = 0) \
for(struct nk_panel *layout = ui_ctx->current->layout; layout; ) \
for( xx = (layout->at_x += px, layout->bounds.w -= px, 0); layout; layout->at_x -= px, layout->bounds.w += px, layout = 0 )
// ----------------------------------------------------------------------------------------
#define expr expr2 // 3rd_lua.h
#include "3rd_eval.h"
// ----------------------------------------------------------------------------------------
#include "labs.meta/meta_reflect.c"
int *meta_changed(void *value) {
static map(void*,int) changes = 0;
do_once map_init_ptr(changes);
return map_find_or_add(changes, value, 0);
}
void reflect_ui( const reflect *r, void *value, void *userdata ) {
ui_label_icon_highlight = *meta_changed(value); // @hack: remove ui_label_icon_highlight hack
char *title = va(ICON_MD_UNDO "%s", r->info);
int changed = 0;
/**/ if( !strcmp(r->type, "int") ) changed = ui_int(title, (int*)value);
else if( !strcmp(r->type, "char") && r->is_ptr ) changed = ui_buffer(title, (char*)value, strlen((char*)value)+1);
else if( !strcmp(r->type, "string") ) changed = ui_string(title, (char**)value);
else if( !strcmp(r->type, "float") ) changed = ui_float(title, (float*)value);
else if( !strcmp(r->type, "double") ) changed = ui_double(title, (double*)value);
else if( !strcmp(r->type, "unsigned") ) changed = ui_unsigned(title, (unsigned*)value);
else if( !strcmp(r->type, "color") ) changed = ui_color4(va("%s #%02X%02X%02X%02X", title, (int)(0[(float*)value]),(int)(1[(float*)value]),(int)(2[(float*)value]),(int)(3[(float*)value])), (float*)value);
// else if( !strcmp(type, "vec3") ) ; // not supported. decays to 3 floats
else ui_label2(title, va("(%s)%s", r->type, r->name));
if( changed ) {
*meta_changed(value) = 1;
}
if( ui_label_icon_clicked_L.x >= 6 && ui_label_icon_clicked_L.x <= 26 ) { // @hack: if clicked on UNDO icon (1st icon)
*meta_changed(value) = 0;
}
}
bool reflect_parse(void *obj, const char *type, const char *val) {
/**/ if( !strcmp(type, "int") ) *((int*)obj) = eval(val);
// else if( !strcmp(r->type, "char") && r->is_ptr ) ; // @fixme: not supported, unless we do strncpy() or similar.
else if( !strcmp(type, "string") ) *((char**)obj) = stringf("%s", val);
else if( !strcmp(type, "float") ) *((float*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "double") ) *((double*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "unsigned") ) *((unsigned*)obj) = eval(val); // = v[0] == '~' ? (float)~atoi(val+1) : atof(val); // = atof(val);
else if( !strcmp(type, "color") ) *(((float*)obj)+3) = 255, sscanf(val, "%f %f %f %f", ((float*)obj)+0, ((float*)obj)+1, ((float*)obj)+2, ((float*)obj)+3);
else if( !strcmp(type, "vec3") ) sscanf(val, "%f %f %f", ((float*)obj)+0, ((float*)obj)+1, ((float*)obj)+2);
else return 0;
return 1;
}
// ----------------------------------------------------------------------------------------
typedef void(*obj_ctor)(void*);
static map(char*, obj_ctor) obj_ctors;
#define STRUCT_CTOR(type, ctor) STRUCT_CTOR(#type, (obj_ctor)ctor)
void (STRUCT_CTOR)( const char *type, obj_ctor ctor ) {
do_once map_init_str(obj_ctors);
map_find_or_add(obj_ctors, STRDUP(type), ctor);
}
bool obj_make(void *obj, const char *ini_data) { // initialize object from ini datas
char *hint = 0;
for( ini_t read = ini_from_mem(ini_data); !!read; map_free(read), read = 0) {
for each_map(read, char*, k, char*, v) {
array(char*) tokens = strsplit(k, ".");
if( array_count(tokens) != 2 ) continue;
const char *type = 0;
void *found = reflect_field( tokens[0], obj, tokens[1], &type );
if( !found ) continue;
if( reflect_parse(found, type, v) ) {
hint = tokens[0];
}
}
// constructor (post-init call)
obj_ctor *ctor = map_find(obj_ctors, hint);
if( ctor ) (*ctor)( obj );
}
return hint != 0;
}
// ----------------------------------------------------------------------------
#define POD_TYPES \
vec2i v2i; \
vec2 v2; \
vec3 v3; \
vec4 v4; \
quat q; \
char *s; \
double d; \
int64_t i; \
unsigned color; \
void *ptr; \
void (*fun)(void*); \
char *nametype; /* "name\0type" */
typedef union pod {
POD_TYPES
} pod;
typedef union var {
POD_TYPES
array(pod) array;
array(pod) pair; // array of 2: first,second. maybe used as key,value
array(pod) tuple; // array of N: first,second,third... maybe used as array of N*2: NAME1,val1,NAME2,val2, ...
map(pod,pod) map;
set(pod) set;
} var;
typedef struct node {
var k;
var v;
struct node *up;
struct node *next;
struct node *down;
} node;
char* node_name(node *n) {
return n->k.nametype;
}
char* node_type(node *n) {
return n->k.nametype + strlen(n->k.nametype) + 1;
}
char* node_set_nametype(node *n, const char *name, const char *type) {
*strchr(n->k.nametype = stringf("%s\1%s", name, type), '\1') = '\0'; // @leak
return n->k.nametype;
}
node* node_find(node *n, const char *path) {
if (!n->k.nametype) return 0;
if( !strcmp(node_name(n), path) ) return n;
node *r = 0;
if( n->next ) r = node_find(n->next, path);
if( n->down && !r ) if( !strcmp(node_name(n->down), path) ) return n->down; // r = node_find(n->down, path);
return r;
}
node* node_find_recurse(node *n, const char *path) {
array(char*) split = strsplit(path, "/");
while( n && array_count(split) ) {
n = node_find(n, split[0]);
array_pop(split);
}
return n;
}
node* node_attach_sibling(node *n, node *sibling) {
while( n->next ) n = n->next;
return n->next = sibling;
}
node* node_attach_child(node *n, node *child) {
child->up = n;
if( n->down ) return node_attach_sibling(n->down, child);
return n->down = child;
}
unsigned node_children(node *n) {
unsigned c = 0;
if( n->down ) {
n = n->down;
do ++c; while( (n = n->next) );
}
return c;
}
unsigned node_siblings(node *n) {
return n->up ? node_children( n->up->down ) : 0;
}
void node_init(node *n) {
profile_incstat("Editor.inits", +1);
if(n->next) node_init(n->next);
if(n->down) node_init(n->down);
}
void node_tick(node *n) {
profile_incstat("Editor.ticks", +1);
if(n->next) node_tick(n->next);
if(n->down) node_tick(n->down);
}
void node_draw(node *n) {
profile_incstat("Editor.draws", +1);
if(n->next) node_draw(n->next);
if(n->down) node_draw(n->down);
}
void node_quit(node *n) {
profile_incstat("Editor.quits", +1);
if(n->next) node_quit(n->next);
if(n->down) node_quit(n->down);
}
void node_edit(node *n, node *root) {
profile_incstat("Editor.edits", +1);
if( ui_collapse(va("%s %s (%u)", n->down ? ICON_MD_SOURCE : ICON_MD_FOLDER, node_name(n), node_children(n)), va("%p%p",root,n->v.ptr)) ) { // @fixme v.ptr
if( n->down ) node_edit(n->down,root);
if( reflect_has_fields( node_type(n), n->v.ptr ) ) {
for ui_push_hspace( 4 ) {
#define ICON_DOT ICON_CANCEL // ICON_MD_WIFI_1_BAR // ICON_MD_RADIO_BUTTON_UNCHECKED // ICON_MD_LENS_BLUR
static int flags[4] = {0};
char *toolbar = va("%s%s%s%s",
flags[3] ? ICON_MD_STAR : ICON_MD_STAR_OUTLINE, // ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER, // flags[3] == 0 ? ICON_MD_STAR_OUTLINE : flags[3] == 1 ? ICON_MD_STAR_HALF : ICON_MD_STAR,
flags[2] ? ICON_MD_CODE : ICON_DOT,
flags[1] ? ICON_MD_FLAG : ICON_DOT,
flags[0] ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF
);
ui_label_icon_highlight = *meta_changed(n); // @hack: remove ui_label_icon_highlight hack
char *section = va("*" ICON_MD_UNDO "%s", node_type(n));
int choice = ui_label2_toolbar(section, toolbar);
if( choice ) flags[ choice - 1 ] = (flags[ choice - 1 ] + 1 ) % ( choice == 4 ? 2/*3*/ : 2);
reflect_iterate_fields( node_type(n), n->v.ptr, reflect_ui, NULL ); // @fixme v.ptr
}
}
ui_collapse_end();
}
if(n->next) node_edit(n->next,root);
}
// ---
struct editor_t {
unsigned frame;
// root nodes
node init;
node tick;
node draw;
node edit;
node quit;
} editor;
enum { EDITOR_BUCKETS = 5 }; // init+tick+draw+edit+quit
void editor_reset() {
node_quit(&editor.quit);
editor.frame = 0;
}
void editor_frame() {
editor_init(); // old editor interface
editor_tick(); // old editor interface
editor_menubar(); // old editor interface
if( input_down(KEY_F5) ) {
editor_reset();
}
if( editor.frame++ == 0 ) {
node_init(&editor.init);
}
node_tick(&editor.tick);
node_draw(&editor.draw);
// content browser
if( ui_window("File Browser", 0) ) {
const char *file = 0;
if( ui_browse(&file, NULL) ) {
const char *sep = ifdef(win32, "\"", "'");
app_exec(va("%s %s%s%s", ifdef(win32, "start \"\"", ifdef(osx, "open", "xdg-open")), sep, file, sep));
}
ui_window_end();
}
// console/terminal window
if( 0 && ui_window("Console", 0) ) { // @fixme half-working
ui_console();
ui_window_end();
}
if( ui_window("Outliner", 0) ) {
#if 1
static char *filter = 0;
{
static int do_filter = 0;
int choice = ui_toolbar(ICON_MD_SEARCH ";" ICON_MD_REFRESH ";" ICON_MD_SD_CARD);
if( choice == 1 ) do_filter = 1;
if( do_filter ) {
ui_string(ICON_CANCEL " Filter " ICON_MD_SEARCH, &filter);
if( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 ) { // if clicked on CANCEL icon (1st icon)
do_filter = 0;
}
} else {
if( filter ) filter[0] = '\0';
}
char *filter_mask = filter && filter[0] ? va("*%s*", filter) : "*";
}
#endif
for( int c = ui_collapse(ICON_MD_FOLDER_SPECIAL " Art/", "ART"); c; ui_collapse_end(), c = 0) {
static const char *file;
static bool show_browser = 1;
if( ui_browse(&file, &show_browser) ) {
app_exec(va("%s %s", ifdef(win32, "start", ifdef(osx, "open", "xdg-open")), file));
//puts(file);
show_browser = 1;
}
}
for( int c = ui_collapse(ICON_MD_BOOKMARK " Bookmarks/", "BOOK"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_BUBBLE_CHART/*ICON_MD_SCATTER_PLOT*/ " Entities/", "ENT"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_TUNE " Components/", "COM"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_PRECISION_MANUFACTURING " Systems/", "SYS"); c; ui_collapse_end(), c = 0) {
}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Hierarchy/", "ORD"); c; ui_collapse_end(), c = 0) {
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Init/", "ORD1"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Draw/", "ORD2"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Tick/", "ORD3"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Edit/", "ORD4"); c; ui_collapse_end(), c = 0) {}
for( int c = ui_collapse(ICON_MD_ACCOUNT_TREE " Quit/", "ORD5"); c; ui_collapse_end(), c = 0) {}
// node_edit(&editor.init,&editor.init);
// node_edit(&editor.draw,&editor.draw);
// node_edit(&editor.tick,&editor.tick);
// node_edit(&editor.edit,&editor.edit);
// node_edit(&editor.quit,&editor.quit);
}
for( int c = ui_collapse(ICON_MD_PUBLIC " World/", "WORLD"); c; ui_collapse_end(), c = 0) {
node_edit(editor.edit.down,&editor.edit);
}
ui_window_end();
}
}
unsigned editor_spawn(const char *path_id, const char *keytype, void *val) {
do_once {
node_set_nametype(&editor.init, "Init Group", "init_nodes"); // @leak
node_set_nametype(&editor.tick, "Tick Group", "tick_nodes"); // @leak
node_set_nametype(&editor.draw, "Draw Group", "draw_nodes"); // @leak
node_set_nametype(&editor.edit, "Edit Group", "edit_nodes"); // @leak
node_set_nametype(&editor.quit, "Quit Group", "quit_nodes"); // @leak
}
array(char*) tokens = strsplit(path_id, "/");
char *keyname = *array_back(tokens);
array(node) n = 0;
array_resize(n, EDITOR_BUCKETS);
for( int i = 0; i < EDITOR_BUCKETS; ++i ) {
node_set_nametype(&n[i], keyname, keytype); // @leak
n[i].v.ptr = val;
}
array_pop(tokens);
char *joint = array_count(tokens) ? strjoin(tokens, "/") : "/";
node *p = 0;
p = node_find_recurse(&editor.init, joint), node_attach_child(p ? p : &editor.init, n+0);
p = node_find_recurse(&editor.tick, joint), node_attach_child(p ? p : &editor.tick, n+1);
p = node_find_recurse(&editor.draw, joint), node_attach_child(p ? p : &editor.draw, n+2);
p = node_find_recurse(&editor.edit, joint), node_attach_child(p ? p : &editor.edit, n+3);
p = node_find_recurse(&editor.quit, joint), node_attach_child(p ? p : &editor.quit, n+4);
return 0;
}
// demo -----------------------------------------------------------------------
typedef struct my_sprite {
char *filename;
vec3 position;
float tilt;
vec4 tint;
// --- private
texture_t texture_;
unsigned bgra_;
} my_sprite;
void my_sprite_ctor(my_sprite *obj) {
obj->texture_ = texture(obj->filename, TEXTURE_RGBA);
obj->bgra_ = bgraf( obj->tint.r/255.0, obj->tint.g/255.0, obj->tint.b/255.0, obj->tint.a/255.0 );
}
void my_sprite_draw(my_sprite *obj) {
obj->bgra_ = bgraf( obj->tint.r/255.0, obj->tint.g/255.0, obj->tint.b/255.0, obj->tint.a/255.0 ); // @fixme: del me
sprite( obj->texture_, &(obj->position.x), obj->tilt, obj->bgra_ );
}
int main() {
typedef char* string;
typedef vec4 color;
STRUCT( vec3, float, x, "X" );
STRUCT( vec3, float, y, "Y" );
STRUCT( vec3, float, z, "Z" );
// STRUCT( texture_t, unsigned, flags, "Flags");
// STRUCT( texture_t, string, filename, "Filename");
STRUCT( my_sprite, string, filename, "Filename" );
STRUCT( my_sprite, vec3, position, "Position" );
STRUCT( my_sprite, float, tilt, "Tilt degrees" );
STRUCT( my_sprite, color, tint, "Tint color" );
STRUCT_CTOR( my_sprite, my_sprite_ctor );
PRINTF("pod:%d, var:%d, node:%d warn\n", (int)sizeof(pod), (int)sizeof(var), (int)sizeof(node));
PRINTF("reflected:%d bytes vs real:%d bytes warn\n", reflect_sizeof("my_sprite"), (int)sizeof(my_sprite));
// cook_config("../../tools/cook.ini");
window_create(0.80, 0);
struct my_sprite spr1 = {0}, spr2 = {0}, spr3 = {0};
obj_make(&spr1,
"[my_sprite]\n"
"filename=cat.png\n"
"position=5 2 100\n"
"tilt=45 + 45 -90\n"
"tint=255 255 0\n"
);
obj_make(&spr2,
"[my_sprite]\n"
"filename=cat.png\n"
"position=1 2 100\n"
"tilt=45 + 45 -90\n"
"tint=255 0 0\n"
);
obj_make(&spr3,
"[my_sprite]\n"
"filename=cat.png\n"
"position=1 2 100\n"
"tilt=45\n"
"tint=0 0 255\n"
);
int hero1 = editor_spawn("/hero1", "my_sprite", &spr1);
int hero2 = editor_spawn("/hero2", "my_sprite", &spr2);
int hero3 = editor_spawn("/hero1/heroB", "my_sprite", &spr3);
camera_t cam = camera();
camera_enable(&cam);
while( window_swap() ) {
editor_frame();
// @fixme: this should be drawn by game, not editor!
my_sprite_draw(&spr1);
my_sprite_draw(&spr2);
my_sprite_draw(&spr3);
// spr1.tilt = 5 * sin(time_ss());
}
}