sync fwk
parent
85ce116f94
commit
5850a5d918
|
@ -1,724 +0,0 @@
|
||||||
// ## Editor long-term plan
|
|
||||||
// - 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 diff zipped
|
|
||||||
//
|
|
||||||
// ## Editor roadmap
|
|
||||||
// - Gizmos✱, scene tree, property editor✱, load/save✱, undo/redo✱, copy/paste, on/off (vis,tick,ddraw,log), vcs.
|
|
||||||
// - Scenenode pass: node singleton display, node console, node labels, node outlines✱.<!-- node == gameobj ? -->
|
|
||||||
// - Render pass: billboards✱, materials, un/lit, cast shadows, wireframe, skybox✱/mie✱, fog/atmosphere
|
|
||||||
// - Level pass: volumes, triggers, platforms, level streaming, collide✱, physics
|
|
||||||
// - Edit pass: Procedural content, brushes, noise and CSG.
|
|
||||||
// - GUI pass: timeline and data tracks, node graphs. <!-- worthy: will be reused into materials, animgraphs and blueprints -->
|
|
||||||
|
|
||||||
// ## Alt plan
|
|
||||||
// editor is a database + window/tile manager + ui toolkit; all network driven.
|
|
||||||
// to be precise, editor is a dumb app and ...
|
|
||||||
// - does not know a thing about what it stores.
|
|
||||||
// - does not know how to render the game graphics.
|
|
||||||
// - does not know how to run the game logic.
|
|
||||||
//
|
|
||||||
// the editor will create a canvas for your game to render.
|
|
||||||
// your game will be responsible to tick the logic and render the window inside the editor.
|
|
||||||
//
|
|
||||||
// that being said, editor...
|
|
||||||
// - can store datas hierarchically.
|
|
||||||
// - can perform diffs and merges, and version the datas into repositories.
|
|
||||||
// - can be instructed to render UI on top of game and window views.
|
|
||||||
// - can download new .natvis and plugins quickly.
|
|
||||||
// - can dump whole project in a filesystem form (zip).
|
|
||||||
|
|
||||||
// - editor reflects database contents up-to-date.
|
|
||||||
// - database can be queried and modified via OSC(UDP) commands.
|
|
||||||
|
|
||||||
// editor database uses one table, and stores two kind of payload types:
|
|
||||||
// - classes: defines typename and dna. class names are prefixed by '@'
|
|
||||||
// - instances: defines typename and datas. instance names are as-is, not prefixed.
|
|
||||||
//
|
|
||||||
// every save contains 5Ws: what, who, when, where, how,
|
|
||||||
// every save can be diffed/merged.
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#define EDITOR_VERSION "2023.10"
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct editor_bind_t {
|
|
||||||
const char *command;
|
|
||||||
const char *bindings;
|
|
||||||
void (*fn)();
|
|
||||||
} editor_bind_t;
|
|
||||||
|
|
||||||
array(editor_bind_t) editor_binds;
|
|
||||||
|
|
||||||
#define EDITOR_BIND(CMD,KEYS,...) void macro(editor_bind_##CMD##_fn_)() { __VA_ARGS__ }; AUTORUN { array_push(editor_binds, ((editor_bind_t){#CMD,KEYS,macro(editor_bind_##CMD##_fn_)}) ); }
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef void (*editor_no_property)(void *);
|
|
||||||
array(void*) editor_persist_kv;
|
|
||||||
array(editor_no_property) editor_no_properties;
|
|
||||||
|
|
||||||
#define EDITOR_PROPERTY(property_name,T,defaults) \
|
|
||||||
typedef map(void*,T) editor_##property_name##_map_t; \
|
|
||||||
editor_##property_name##_map_t *editor_##property_name##_map() { \
|
|
||||||
static editor_##property_name##_map_t map = 0; do_once map_init_ptr(map); \
|
|
||||||
return ↦ \
|
|
||||||
} \
|
|
||||||
T editor_##property_name(const void *obj) { \
|
|
||||||
return *map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
|
|
||||||
} \
|
|
||||||
void editor_set##property_name(const void *obj, T value) { \
|
|
||||||
*map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) value)) = ((T) value); \
|
|
||||||
} \
|
|
||||||
void editor_alt##property_name(const void *obj) { \
|
|
||||||
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
|
|
||||||
*found = (T)(uintptr_t)!(*found); \
|
|
||||||
} \
|
|
||||||
void editor_no##property_name(void *obj) { \
|
|
||||||
T* found = map_find_or_add(*editor_##property_name##_map(), (void*)obj, ((T) defaults)); \
|
|
||||||
map_erase(*editor_##property_name##_map(), (void*)obj); \
|
|
||||||
} \
|
|
||||||
AUTORUN { array_push(editor_persist_kv, #T); array_push(editor_persist_kv, editor_##property_name##_map()); array_push(editor_no_properties, editor_no##property_name); }
|
|
||||||
|
|
||||||
EDITOR_PROPERTY(open, int, 0); // whether object is tree opened in tree editor
|
|
||||||
EDITOR_PROPERTY(selected, int, 0); // whether object is displaying a contextual popup or not
|
|
||||||
EDITOR_PROPERTY(changed, int, 0); // whether object is displaying a contextual popup or not
|
|
||||||
EDITOR_PROPERTY(popup, int, 0); // whether object is displaying a contextual popup or not
|
|
||||||
EDITOR_PROPERTY(visible, int, 0);
|
|
||||||
EDITOR_PROPERTY(script, int, 0);
|
|
||||||
EDITOR_PROPERTY(event, int, 0);
|
|
||||||
EDITOR_PROPERTY(iconinstance, char*, 0);
|
|
||||||
EDITOR_PROPERTY(iconclass, char*, 0);
|
|
||||||
EDITOR_PROPERTY(treeoffsety, int, 0);
|
|
||||||
// new prop: breakpoint: request to break on any given node
|
|
||||||
// new prop: persist: objects with this property will be saved on disk
|
|
||||||
|
|
||||||
void editor_destroy_properties(void *o) {
|
|
||||||
for each_array(editor_no_properties,editor_no_property,fn) {
|
|
||||||
fn(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void editor_load_on_boot(void) {
|
|
||||||
puts("@todo: load editor");
|
|
||||||
}
|
|
||||||
void editor_save_on_quit(void) {
|
|
||||||
puts("@todo: save editor");
|
|
||||||
}
|
|
||||||
AUTORUN {
|
|
||||||
editor_load_on_boot();
|
|
||||||
(atexit)(editor_save_on_quit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef int(*subeditor)(int mode);
|
|
||||||
|
|
||||||
struct editor_t {
|
|
||||||
// time
|
|
||||||
unsigned frame;
|
|
||||||
double t, dt, slomo;
|
|
||||||
// controls
|
|
||||||
int transparent;
|
|
||||||
int attached;
|
|
||||||
int active; // focus? does_grabinput instead?
|
|
||||||
int key;
|
|
||||||
vec2 mouse; // 2d coord for ray/picking
|
|
||||||
bool gamepad; // mask instead? |1|2|4|8
|
|
||||||
int hz_high, hz_medium, hz_low;
|
|
||||||
int filter;
|
|
||||||
bool battery; // battery mode: low fps
|
|
||||||
bool unlit;
|
|
||||||
bool ddraw;
|
|
||||||
// event root nodes
|
|
||||||
obj* root;
|
|
||||||
obj* on_init;
|
|
||||||
obj* on_tick;
|
|
||||||
obj* on_draw;
|
|
||||||
obj* on_edit;
|
|
||||||
obj* on_quit;
|
|
||||||
// all of them (hierarchical)
|
|
||||||
array(obj*) objs; // @todo:set() world?
|
|
||||||
// all of them (flat)
|
|
||||||
set(obj*) world;
|
|
||||||
//
|
|
||||||
array(char*) cmds;
|
|
||||||
// subeditors
|
|
||||||
array(subeditor) subeditors;
|
|
||||||
} editor = {
|
|
||||||
.active = 1,
|
|
||||||
.gamepad = 1,
|
|
||||||
.hz_high = 60, .hz_medium = 18, .hz_low = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
EDITOR_PANEL,
|
|
||||||
EDITOR_WINDOW,
|
|
||||||
EDITOR_WINDOW_NK,
|
|
||||||
EDITOR_WINDOW_NK_SMALL,
|
|
||||||
};
|
|
||||||
|
|
||||||
int editor_begin(const char *title, int mode) {
|
|
||||||
if( mode == 0 ) return ui_panel(title, PANEL_OPEN);
|
|
||||||
if( mode == 1 ) return ui_window(title, 0);
|
|
||||||
|
|
||||||
int ww = window_width(), w = ww * 0.66;
|
|
||||||
int hh = window_height(), h = hh * 0.66;
|
|
||||||
|
|
||||||
struct nk_rect position = { (ww-w)/2,(hh-h)/2, w,h };
|
|
||||||
nk_flags win_flags = NK_WINDOW_TITLE | NK_WINDOW_BORDER |
|
|
||||||
NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE |
|
|
||||||
NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE |
|
|
||||||
// NK_WINDOW_SCALE_LEFT|NK_WINDOW_SCALE_TOP| //< @fixme: move this logic into nuklear
|
|
||||||
// NK_WINDOW_MAXIMIZABLE | NK_WINDOW_PINNABLE |
|
|
||||||
0; // NK_WINDOW_SCROLL_AUTO_HIDE;
|
|
||||||
|
|
||||||
if( mode == 3 ) {
|
|
||||||
mode = 2, position.x = input(MOUSE_X), position.w = w/3, win_flags =
|
|
||||||
NK_WINDOW_TITLE|NK_WINDOW_CLOSABLE|
|
|
||||||
NK_WINDOW_SCALABLE|NK_WINDOW_MOVABLE| //< nuklear requires these two to `remember` popup rects
|
|
||||||
0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mode == 2 || mode == 3 )
|
|
||||||
if (nk_begin(ui_ctx, title, position, win_flags))
|
|
||||||
return 1;
|
|
||||||
else
|
|
||||||
return nk_end(ui_ctx), 0;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int editor_end(int mode) {
|
|
||||||
if( mode == 0 ) return ui_panel_end();
|
|
||||||
if( mode == 1 ) return ui_window_end();
|
|
||||||
if( mode == 2 ) nk_end(ui_ctx);
|
|
||||||
if( mode == 3 ) nk_end(ui_ctx);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0 // deprecate
|
|
||||||
bool editor_active() {
|
|
||||||
return ui_hover() || ui_active() || gizmo_active() ? editor.active : 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int editor_filter() {
|
|
||||||
if( editor.filter ) {
|
|
||||||
if (nk_begin(ui_ctx, "Filter", nk_rect(window_width()-window_width()*0.33,32, window_width()*0.33, 40),
|
|
||||||
NK_WINDOW_NO_SCROLLBAR)) {
|
|
||||||
|
|
||||||
char *bak = ui_filter; ui_filter = 0;
|
|
||||||
ui_string(ICON_MD_CLOSE " Filter " ICON_MD_SEARCH, &bak);
|
|
||||||
ui_filter = bak;
|
|
||||||
|
|
||||||
if( input(KEY_ESC) || ( ui_label_icon_clicked_L.x > 0 && ui_label_icon_clicked_L.x <= 24 )) {
|
|
||||||
if( ui_filter ) ui_filter[0] = '\0';
|
|
||||||
editor.filter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nk_end(ui_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return editor.filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
int editor_select_(void *o, const char *mask) {
|
|
||||||
int matches = 0;
|
|
||||||
int off = mask[0] == '!', inv = mask[0] == '~';
|
|
||||||
int match = strmatchi(obj_type(o), mask+off+inv) || strmatchi(obj_name(o), mask+off+inv);
|
|
||||||
if( match ) {
|
|
||||||
editor_setselected(o, inv ? editor_selected(o) ^ 1 : !off);
|
|
||||||
++matches;
|
|
||||||
}
|
|
||||||
for each_objchild(o, obj*, oo) {
|
|
||||||
matches += editor_select_(oo, mask);
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
void editor_select(const char *mask) {
|
|
||||||
for each_array( editor.objs, obj*, o )
|
|
||||||
editor_select_(o, mask);
|
|
||||||
}
|
|
||||||
void editor_unselect() { // same than editor_select("!**");
|
|
||||||
for each_map_ptr(*editor_selected_map(), void*,o, int, k) {
|
|
||||||
if( *k ) *k = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void editor_select_aabb(aabb box) {
|
|
||||||
int is_inv = input_held(KEY_CTRL);
|
|
||||||
int is_add = input_held(KEY_SHIFT);
|
|
||||||
if( !is_inv && !is_add ) editor_unselect();
|
|
||||||
|
|
||||||
aabb item = {0};
|
|
||||||
for each_set_ptr( editor.world, obj*, o ) {
|
|
||||||
if( obj_hasmethod(*o,aabb) && obj_aabb(*o, &item) ) {
|
|
||||||
if( aabb_test_aabb(item, box) ) {
|
|
||||||
if( is_inv )
|
|
||||||
editor_altselected(*o);
|
|
||||||
else
|
|
||||||
editor_setselected(*o, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static obj* active_ = 0;
|
|
||||||
static void editor_selectgroup_(obj *o, obj *first, obj *last) {
|
|
||||||
// printf("%s (looking for %s in [%s..%s])\n", obj_name(o), active_ ? obj_name(active_) : "", obj_name(first), obj_name(last));
|
|
||||||
if( !active_ ) if( o == first || o == last ) active_ = o == first ? last : first;
|
|
||||||
if( active_ ) editor_setselected(o, 1);
|
|
||||||
if( o == active_ ) active_ = 0;
|
|
||||||
for each_objchild(o, obj*, oo) {
|
|
||||||
editor_selectgroup_(oo, first, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void editor_selectgroup(obj *first, obj *last) {
|
|
||||||
if( last ) {
|
|
||||||
if( !first ) first = array_count(editor.objs) ? editor.objs[0] : NULL;
|
|
||||||
if( !first ) editor_setselected(last, 1);
|
|
||||||
else {
|
|
||||||
active_ = 0;
|
|
||||||
for each_array(editor.objs,obj*,o) {
|
|
||||||
editor_selectgroup_(o, first, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static obj *find_any_selected_(obj *o) {
|
|
||||||
if( editor_selected(o) ) return o;
|
|
||||||
for each_objchild(o,obj*,oo) {
|
|
||||||
obj *ooo = find_any_selected_(oo);
|
|
||||||
if( ooo )
|
|
||||||
return ooo;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void* editor_first_selected() {
|
|
||||||
for each_array(editor.objs,obj*,o) {
|
|
||||||
obj *oo = find_any_selected_(o);
|
|
||||||
// if( oo ) printf("1st found: %s\n", obj_name(oo));
|
|
||||||
if( oo ) return oo;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static obj *find_last_selected_(obj *o) {
|
|
||||||
void *last = 0;
|
|
||||||
if( editor_selected(o) ) last = o;
|
|
||||||
for each_objchild(o,obj*,oo) {
|
|
||||||
obj *ooo = find_last_selected_(oo);
|
|
||||||
if( ooo )
|
|
||||||
last = ooo;
|
|
||||||
}
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
void* editor_last_selected() {
|
|
||||||
void *last = 0;
|
|
||||||
for each_array(editor.objs,obj*,o) {
|
|
||||||
obj *oo = find_last_selected_(o);
|
|
||||||
// if( oo ) printf("last found: %s\n", obj_name(oo));
|
|
||||||
if( oo ) last = oo;
|
|
||||||
}
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void editor_addtoworld(obj *o) {
|
|
||||||
set_find_or_add(editor.world, o);
|
|
||||||
for each_objchild(o, obj*, oo) {
|
|
||||||
editor_addtoworld(oo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void editor_watch(const void *o) {
|
|
||||||
array_push(editor.objs, (obj*)o);
|
|
||||||
obj_push(o); // save state
|
|
||||||
|
|
||||||
editor_addtoworld((obj*)o);
|
|
||||||
}
|
|
||||||
void* editor_spawn(const char *ini) { // deprecate?
|
|
||||||
obj *o = obj_make(ini);
|
|
||||||
editor_watch(o);
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
void editor_spawn1() {
|
|
||||||
obj *selected = editor_first_selected();
|
|
||||||
obj *o = selected ? obj_make(obj_saveini(selected)) : obj_new(obj);
|
|
||||||
if( selected ) obj_attach(selected, o), editor_setopen(selected, 1);
|
|
||||||
else
|
|
||||||
editor_watch(o);
|
|
||||||
|
|
||||||
editor_unselect();
|
|
||||||
editor_setselected(o, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef set(obj*) set_objp_t;
|
|
||||||
static
|
|
||||||
void editor_glob_recurse(set_objp_t*list, obj *o) {
|
|
||||||
set_find_or_add(*list, o);
|
|
||||||
for each_objchild(o,obj*,oo) {
|
|
||||||
editor_glob_recurse(list, oo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void editor_destroy_selected() {
|
|
||||||
set_objp_t list = 0;
|
|
||||||
set_init_ptr(list);
|
|
||||||
for each_map_ptr(*editor_selected_map(), obj*,o, int,selected) {
|
|
||||||
if( *selected ) { editor_glob_recurse(&list, *o); }
|
|
||||||
}
|
|
||||||
for each_set(list, obj*, o) {
|
|
||||||
obj_detach(o);
|
|
||||||
}
|
|
||||||
for each_set(list, obj*, o) {
|
|
||||||
// printf("deleting %p %s\n", o, obj_name(o));
|
|
||||||
// remove from watched items
|
|
||||||
for (int i = 0, end = array_count(editor.objs); i < end; ++i) {
|
|
||||||
if (editor.objs[i] == o) {
|
|
||||||
editor.objs[i] = 0;
|
|
||||||
array_erase_slow(editor.objs, i);
|
|
||||||
--end;
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// delete from world
|
|
||||||
set_erase(editor.world, o);
|
|
||||||
// delete properties + obj
|
|
||||||
editor_destroy_properties(o);
|
|
||||||
obj_free(o);
|
|
||||||
}
|
|
||||||
set_free(list);
|
|
||||||
}
|
|
||||||
void editor_inspect(obj *o) {
|
|
||||||
ui_section(va("%s (%s)", obj_type(o), obj_name(o)));
|
|
||||||
|
|
||||||
if( obj_hasmethod(o, menu) ) {
|
|
||||||
obj_menu(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
for each_objmember(o,TYPE,NAME,PTR) {
|
|
||||||
if( !editor_changed(PTR) ) {
|
|
||||||
obj_push(o);
|
|
||||||
}
|
|
||||||
ui_label_icon_highlight = editor_changed(PTR); // @hack: remove ui_label_icon_highlight hack
|
|
||||||
char *label = va(ICON_MD_UNDO "%s", NAME);
|
|
||||||
int changed = 0;
|
|
||||||
/**/ if( !strcmp(TYPE,"float") ) changed = ui_float(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"int") ) changed = ui_int(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"vec2") ) changed = ui_float2(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"vec3") ) changed = ui_float3(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"vec4") ) changed = ui_float4(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"color") ) changed = ui_color4(label, PTR);
|
|
||||||
else if( !strcmp(TYPE,"char*") ) changed = ui_string(label, PTR);
|
|
||||||
else ui_label2(label, va("(%s)", TYPE)); // INFO instead of (TYPE)?
|
|
||||||
if( changed ) {
|
|
||||||
editor_setchanged(PTR, 1);
|
|
||||||
}
|
|
||||||
if( ui_label_icon_highlight )
|
|
||||||
if( ui_label_icon_clicked_L.x >= 6 && ui_label_icon_clicked_L.x <= 26 ) { // @hack: if clicked on UNDO icon (1st icon)
|
|
||||||
editor_setchanged(PTR, 0);
|
|
||||||
}
|
|
||||||
if( !editor_changed(PTR) ) {
|
|
||||||
obj_pop(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
// tty
|
|
||||||
|
|
||||||
static thread_mutex_t *console_lock;
|
|
||||||
static array(char*) editor_jobs;
|
|
||||||
int editor_send(const char *cmd) { // return job-id
|
|
||||||
int skip = strspn(cmd, " \t\r\n");
|
|
||||||
char *buf = STRDUP(cmd + skip);
|
|
||||||
strswap(buf, "\r\n", "");
|
|
||||||
int jobid;
|
|
||||||
do_threadlock(console_lock) {
|
|
||||||
array_push(editor_jobs, buf);
|
|
||||||
jobid = array_count(editor_jobs) - 1;
|
|
||||||
}
|
|
||||||
return jobid;
|
|
||||||
}
|
|
||||||
const char* editor_recv(int jobid, double timeout_ss) {
|
|
||||||
char *answer = 0;
|
|
||||||
|
|
||||||
while(!answer && timeout_ss >= 0 ) {
|
|
||||||
do_threadlock(console_lock) {
|
|
||||||
if( editor_jobs[jobid][0] == '\0' )
|
|
||||||
answer = editor_jobs[jobid];
|
|
||||||
}
|
|
||||||
timeout_ss -= 0.1;
|
|
||||||
if( timeout_ss > 0 ) sleep_ms(100); // thread_yield()
|
|
||||||
}
|
|
||||||
|
|
||||||
return answer + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// plain and ctrl keys
|
|
||||||
EDITOR_BIND(play, "down(F5)", { window_pause(0); /* if(!editor.slomo) editor.active = 0; */ editor.slomo = 1; } );
|
|
||||||
EDITOR_BIND(stop, "down(ESC)", { if(editor.t > 0) { window_pause(1), editor.frame = 0, editor.t = 0, editor.dt = 0, editor.slomo = 0, editor.active = 1; editor_select("**"); editor_destroy_selected(); }} );
|
|
||||||
EDITOR_BIND(eject, "down(F1)", { /*window_pause(!editor.active); editor.slomo = !!editor.active;*/ editor.active ^= 1; } );
|
|
||||||
EDITOR_BIND(pause, "(held(CTRL) & down(P)) | down(PAUSE)", { window_pause( window_has_pause() ^ 1 ); } );
|
|
||||||
EDITOR_BIND(frame, "held(CTRL) & down(LEFT)", { window_pause(1); editor.frame++, editor.t += (editor.dt = 1/60.f); } );
|
|
||||||
EDITOR_BIND(slomo, "held(CTRL) & down(RIGHT)", { window_pause(0); editor.slomo = maxf(fmod(editor.slomo * 2, 16), 0.125); } );
|
|
||||||
EDITOR_BIND(reload, "held(CTRL) & down(F5)", { window_reload(); } );
|
|
||||||
EDITOR_BIND(filter, "held(CTRL) & down(F)", { editor.filter ^= 1; } );
|
|
||||||
|
|
||||||
// alt keys
|
|
||||||
EDITOR_BIND(quit, "held(ALT) & down(F4)", { record_stop(), exit(0); } );
|
|
||||||
EDITOR_BIND(mute, "held(ALT) & down(M)", { audio_volume_master( 1 ^ !!audio_volume_master(-1) ); } );
|
|
||||||
EDITOR_BIND(gamepad, "held(ALT) & down(G)", { editor.gamepad ^= 1; } );
|
|
||||||
EDITOR_BIND(transparent, "held(ALT) & down(T)", { editor.transparent ^= 1; } );
|
|
||||||
EDITOR_BIND(record, "held(ALT) & down(Z)", { if(record_active()) record_stop(), ui_notify(va("Video recorded"), date_string()); else { char *name = file_counter(va("%s.mp4",app_name())); app_beep(), window_record(name); } } );
|
|
||||||
EDITOR_BIND(screenshot, "held(ALT) & down(S)", { char *name = file_counter(va("%s.png",app_name())); window_screenshot(name), ui_notify(va("Screenshot: %s", name), date_string()); } );
|
|
||||||
EDITOR_BIND(battery, "held(ALT) & down(B)", { editor.battery ^= 1; } );
|
|
||||||
EDITOR_BIND(outliner, "held(ALT) & down(O)", { ui_show("Outliner", ui_visible("Outliner") ^ true); } );
|
|
||||||
EDITOR_BIND(profiler, "held(ALT) & down(P)", { ui_show("Profiler", profiler_enable(ui_visible("Profiler") ^ true)); } );
|
|
||||||
EDITOR_BIND(fullscreen, "(held(ALT)&down(ENTER))|down(F11)",{ record_stop(), window_fullscreen( window_has_fullscreen() ^ 1 ); } ); // close any recording before framebuffer resizing, which would corrupt video stream
|
|
||||||
EDITOR_BIND(unlit, "held(ALT) & down(U)", { editor.unlit ^= 1; } );
|
|
||||||
EDITOR_BIND(ddraw, "held(ALT) & down(D)", { editor.ddraw ^= 1; } );
|
|
||||||
|
|
||||||
void editor_pump() {
|
|
||||||
for each_array(editor_binds,editor_bind_t,b) {
|
|
||||||
if( input_eval(b.bindings) ) {
|
|
||||||
editor_send(b.command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
do_threadlock(console_lock) {
|
|
||||||
for each_array_ptr(editor_jobs,char*,cmd) {
|
|
||||||
if( (*cmd)[0] ) {
|
|
||||||
int found = 0;
|
|
||||||
for each_array(editor_binds,editor_bind_t,b) {
|
|
||||||
if( !strcmpi(b.command, *cmd)) {
|
|
||||||
b.fn();
|
|
||||||
found = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !found ) {
|
|
||||||
// alert(va("Editor: could not handle `%s` command.", *cmd));
|
|
||||||
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Err\n"); (*cmd)[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if( (*cmd)[0] ) {
|
|
||||||
(*cmd)[0] = '\0'; strcatf(&(*cmd), "\1%s\n", "Ok\n"); (*cmd)[0] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void editor_symbol(int x, int y, const char *sym) {
|
|
||||||
#define FONT_SYMBOLS FONT_FACE2
|
|
||||||
#define FONT_WHITE FONT_COLOR1
|
|
||||||
#define FONT_YELLOW FONT_COLOR2
|
|
||||||
#define FONT_ORANGE FONT_COLOR3
|
|
||||||
#define FONT_CYAN FONT_COLOR4
|
|
||||||
// style: atlas size, unicode ranges and 6 font faces max
|
|
||||||
do_once font_face(FONT_SYMBOLS, "MaterialIconsSharp-Regular.otf", 24.f, FONT_EM|FONT_2048);
|
|
||||||
// style: 10 colors max
|
|
||||||
do_once font_color(FONT_WHITE, WHITE);
|
|
||||||
do_once font_color(FONT_YELLOW, YELLOW);
|
|
||||||
do_once font_color(FONT_CYAN, CYAN);
|
|
||||||
do_once font_color(FONT_ORANGE, ORANGE);
|
|
||||||
font_goto(x,y);
|
|
||||||
font_print(va(FONT_SYMBOLS FONT_WHITE FONT_H1 "%s", sym));
|
|
||||||
}
|
|
||||||
|
|
||||||
void editor_frame( void (*game)(unsigned, float, double) ) {
|
|
||||||
do_once {
|
|
||||||
set_init_ptr(editor.world);
|
|
||||||
//set_init_ptr(editor.selection);
|
|
||||||
profiler_enable( false );
|
|
||||||
|
|
||||||
window_pause( true );
|
|
||||||
window_cursor_shape(CURSOR_SW_AUTO);
|
|
||||||
|
|
||||||
fx_load("editorOutline.fs");
|
|
||||||
fx_enable(0, 1);
|
|
||||||
|
|
||||||
obj_setname(editor.root = obj_new(obj), "Signals");
|
|
||||||
obj_setname(editor.on_init = obj_new(obj), "onInit");
|
|
||||||
obj_setname(editor.on_tick = obj_new(obj), "onTick");
|
|
||||||
obj_setname(editor.on_draw = obj_new(obj), "onDraw");
|
|
||||||
obj_setname(editor.on_edit = obj_new(obj), "onEdit");
|
|
||||||
obj_setname(editor.on_quit = obj_new(obj), "onQuit");
|
|
||||||
|
|
||||||
obj_attach(editor.root, editor.on_init);
|
|
||||||
obj_attach(editor.root, editor.on_tick);
|
|
||||||
obj_attach(editor.root, editor.on_draw);
|
|
||||||
obj_attach(editor.root, editor.on_edit);
|
|
||||||
obj_attach(editor.root, editor.on_quit);
|
|
||||||
|
|
||||||
editor_seticoninstance(editor.root, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
editor_seticoninstance(editor.on_init, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
editor_seticoninstance(editor.on_tick, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
editor_seticoninstance(editor.on_draw, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
editor_seticoninstance(editor.on_edit, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
editor_seticoninstance(editor.on_quit, ICON_MDI_SIGNAL_VARIANT);
|
|
||||||
|
|
||||||
editor_seticonclass(obj_type(editor.root), ICON_MDI_CUBE_OUTLINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// game tick
|
|
||||||
game(editor.frame, editor.dt, editor.t);
|
|
||||||
|
|
||||||
// timing
|
|
||||||
editor.dt = clampf(window_delta(), 0, 1/60.f) * !window_has_pause() * editor.slomo;
|
|
||||||
editor.t += editor.dt;
|
|
||||||
editor.frame += !window_has_pause();
|
|
||||||
editor.frame += !editor.frame;
|
|
||||||
|
|
||||||
// process inputs & messages
|
|
||||||
editor_pump();
|
|
||||||
|
|
||||||
// adaptive framerate
|
|
||||||
int app_on_background = !window_has_focus();
|
|
||||||
int hz = app_on_background ? editor.hz_low : editor.battery ? editor.hz_medium : editor.hz_high;
|
|
||||||
window_fps_lock( hz < 5 ? 5 : hz );
|
|
||||||
|
|
||||||
// draw menubar
|
|
||||||
static int stats_mode = 1;
|
|
||||||
static double last_fps = 0; if(!window_has_pause()) last_fps = window_fps();
|
|
||||||
const char *STATS = va("x%4.3f %03d.%03dss %02dF %s",
|
|
||||||
editor.slomo, (int)editor.t, (int)(1000 * (editor.t - (int)editor.t)),
|
|
||||||
(editor.frame-1) % ((int)window_fps_target() + !(int)window_fps_target()),
|
|
||||||
stats_mode == 1 ? va("%5.2f/%dfps", last_fps, (int)window_fps_target()) : stats_mode == 0 ? "0/0 KiB" : xstats());
|
|
||||||
const char *ICON_PL4Y = window_has_pause() ? ICON_MDI_PLAY : ICON_MDI_PAUSE;
|
|
||||||
const char *ICON_SKIP = window_has_pause() ? ICON_MDI_STEP_FORWARD/*ICON_MDI_SKIP_NEXT*/ : ICON_MDI_FAST_FORWARD;
|
|
||||||
|
|
||||||
int ingame = !editor.active;
|
|
||||||
UI_MENU(14, \
|
|
||||||
if(ingame) ui_disable(); \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_FILE_TREE, editor_send("scene")) \
|
|
||||||
if(ingame) ui_enable(); \
|
|
||||||
UI_MENU_ITEM(ICON_PL4Y, if(editor.t == 0) editor_send("eject"); editor_send(window_has_pause() ? "play" : "pause")) \
|
|
||||||
UI_MENU_ITEM(ICON_SKIP, editor_send(window_has_pause() ? "frame" : "slomo")) \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_STOP, editor_send("stop")) \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_EJECT, editor_send("eject")) \
|
|
||||||
UI_MENU_ITEM(STATS, stats_mode = (++stats_mode) % 3) \
|
|
||||||
UI_MENU_ALIGN_RIGHT(32+32+32+32+32+32+34) \
|
|
||||||
if(ingame) ui_disable(); \
|
|
||||||
UI_MENU_ITEM(ICON_MD_FOLDER_SPECIAL, editor_send("browser")) \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_CHART_TIMELINE, editor_send("timeline")) \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_CONSOLE, editor_send("console")) \
|
|
||||||
UI_MENU_ITEM(ICON_MDI_GRAPH, editor_send("nodes")) \
|
|
||||||
UI_MENU_ITEM(ICON_MD_SEARCH, editor_send("filter")) \
|
|
||||||
UI_MENU_POPUP(ICON_MD_SETTINGS, vec2(0.33,1.00), ui_debug()) \
|
|
||||||
if(ingame) ui_enable(); \
|
|
||||||
UI_MENU_ITEM(ICON_MD_CLOSE, editor_send("quit")) \
|
|
||||||
);
|
|
||||||
|
|
||||||
if( !editor.active ) return;
|
|
||||||
|
|
||||||
// draw edit view (gizmos, position markers, etc).
|
|
||||||
for each_set_ptr(editor.world,obj*,o) {
|
|
||||||
if( obj_hasmethod(*o,edit) ) {
|
|
||||||
obj_edit(*o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw silhouettes
|
|
||||||
sprite_flush();
|
|
||||||
fx_begin();
|
|
||||||
for each_map_ptr(*editor_selected_map(),void*,o,int,selected) {
|
|
||||||
if( !*selected ) continue;
|
|
||||||
if( obj_hasmethod(*o,draw) ) {
|
|
||||||
obj_draw(*o);
|
|
||||||
}
|
|
||||||
if( obj_hasmethod(*o,edit) ) {
|
|
||||||
obj_edit(*o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sprite_flush();
|
|
||||||
fx_end();
|
|
||||||
|
|
||||||
// draw box selection
|
|
||||||
if( !ui_active() ) { //< check that we're not moving a window
|
|
||||||
static vec2 from = {0}, to = {0};
|
|
||||||
if( input_down(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y)), from = to;
|
|
||||||
if( input(MOUSE_L) ) to = vec2(input(MOUSE_X), input(MOUSE_Y));
|
|
||||||
if( len2sq(sub2(from,to)) > 0 ) {
|
|
||||||
vec2 a = min2(from, to), b = max2(from, to);
|
|
||||||
ddraw_push_2d();
|
|
||||||
ddraw_color_push(YELLOW);
|
|
||||||
ddraw_line( vec3(a.x,a.y,0),vec3(b.x-1,a.y,0) );
|
|
||||||
ddraw_line( vec3(b.x,a.y,0),vec3(b.x,b.y-1,0) );
|
|
||||||
ddraw_line( vec3(b.x,b.y,0),vec3(a.x-1,b.y,0) );
|
|
||||||
ddraw_line( vec3(a.x,b.y,0),vec3(a.x,a.y-1,0) );
|
|
||||||
ddraw_color_pop();
|
|
||||||
ddraw_pop_2d();
|
|
||||||
}
|
|
||||||
if( input_up(MOUSE_L) ) {
|
|
||||||
vec2 a = min2(from, to), b = max2(from, to);
|
|
||||||
from = to = vec2(0,0);
|
|
||||||
|
|
||||||
editor_select_aabb(aabb(vec3(a.x,a.y,0),vec3(b.x,b.y,0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw mouse aabb
|
|
||||||
aabb mouse = { vec3(input(MOUSE_X),input(MOUSE_Y),0), vec3(input(MOUSE_X),input(MOUSE_Y),1)};
|
|
||||||
if( 1 ) {
|
|
||||||
ddraw_color_push(YELLOW);
|
|
||||||
ddraw_push_2d();
|
|
||||||
ddraw_aabb(mouse.min, mouse.max);
|
|
||||||
ddraw_pop_2d();
|
|
||||||
ddraw_color_pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// tick mouse aabb selection and contextual tab (RMB)
|
|
||||||
aabb box = {0};
|
|
||||||
for each_set(editor.world,obj*,o) {
|
|
||||||
if( !obj_hasmethod(o, aabb) ) continue;
|
|
||||||
if( !obj_aabb(o, &box) ) continue;
|
|
||||||
|
|
||||||
// trigger contextual inspector
|
|
||||||
if( input_down(MOUSE_R) ) {
|
|
||||||
int is_selected = editor_selected(o);
|
|
||||||
editor_setpopup(o, is_selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw contextual inspector
|
|
||||||
if( editor_popup(o) ) {
|
|
||||||
if( editor_begin(va("%s (%s)", obj_name(o), obj_type(o)),EDITOR_WINDOW_NK_SMALL) ) {
|
|
||||||
ui_label2(obj_name(o), obj_type(o));
|
|
||||||
editor_inspect(o);
|
|
||||||
editor_end(EDITOR_WINDOW_NK_SMALL);
|
|
||||||
} else {
|
|
||||||
editor_setpopup(o, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// draw subeditors
|
|
||||||
static int preferred_window_mode = EDITOR_WINDOW;
|
|
||||||
static struct nk_color bak, *on = 0; do_once bak = ui_ctx->style.window.fixed_background.data.color; // ui_ctx->style.window.fixed_background.data.color = !!(on = (on ? NULL : &bak)) ? AS_NKCOLOR(0) : bak; };
|
|
||||||
if( editor.transparent ) ui_ctx->style.window.fixed_background.data.color = AS_NKCOLOR(0);
|
|
||||||
for each_array(editor.subeditors, subeditor, fn) {
|
|
||||||
fn(preferred_window_mode);
|
|
||||||
}
|
|
||||||
ui_ctx->style.window.fixed_background.data.color = bak;
|
|
||||||
|
|
||||||
// draw ui filter (note: render at end-of-frame, so it's hopefully on-top)
|
|
||||||
editor_filter();
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "v4k_editor1_scene.h"
|
|
||||||
#include "v4k_editor2_browser.h"
|
|
||||||
#include "v4k_editor3_timeline.h"
|
|
||||||
#include "v4k_editor4_console.h"
|
|
||||||
#include "v4k_editor5_nodes.h"
|
|
|
@ -1,184 +0,0 @@
|
||||||
#define SCENE_ICON ICON_MDI_FILE_TREE
|
|
||||||
#define SCENE_TITLE "Scene " SCENE_ICON
|
|
||||||
|
|
||||||
EDITOR_BIND(scene, "held(CTRL)&down(1)", { ui_show(SCENE_TITLE, ui_visible(SCENE_TITLE) ^ true); });
|
|
||||||
|
|
||||||
EDITOR_PROPERTY(bookmarked, int, 0);
|
|
||||||
|
|
||||||
EDITOR_BIND(node_new, "down(INS)", { editor_spawn1(); } );
|
|
||||||
EDITOR_BIND(node_del, "down(DEL)", { editor_destroy_selected(); } );
|
|
||||||
EDITOR_BIND(node_save, "held(CTRL)&down(S)", { puts("@todo"); } );
|
|
||||||
EDITOR_BIND(scene_save, "held(CTRL)&down(S)&held(SHIFT)",{ puts("@todo"); } );
|
|
||||||
EDITOR_BIND(select_all, "held(CTRL) & down(A)", { editor_select("**"); } );
|
|
||||||
EDITOR_BIND(select_none, "held(CTRL) & down(D)", { editor_select("!**"); } );
|
|
||||||
EDITOR_BIND(select_invert, "held(CTRL) & down(I)", { editor_select("~**"); } );
|
|
||||||
EDITOR_BIND(bookmark, "held(CTRL) & down(B)", { editor_selected_map_t *map = editor_selected_map(); \
|
|
||||||
int on = 0; \
|
|
||||||
for each_map_ptr(*map,void*,o,int,selected) if(*selected) on |= !editor_bookmarked(*o); \
|
|
||||||
for each_map_ptr(*map,void*,o,int,selected) if(*selected) editor_setbookmarked(*o, on); \
|
|
||||||
} );
|
|
||||||
|
|
||||||
enum {
|
|
||||||
SCENE_RECURSE = 1,
|
|
||||||
SCENE_SELECTION = 2,
|
|
||||||
SCENE_CHECKBOX = 4,
|
|
||||||
SCENE_INDENT = 8,
|
|
||||||
SCENE_ALL = ~0u
|
|
||||||
};
|
|
||||||
|
|
||||||
static
|
|
||||||
void editor_scene_(obj *o, unsigned flags) {
|
|
||||||
static unsigned tabs = ~0u;
|
|
||||||
++tabs;
|
|
||||||
|
|
||||||
if( o ) {
|
|
||||||
unsigned do_tags = 1;
|
|
||||||
unsigned do_indent = !!(flags & SCENE_INDENT);
|
|
||||||
unsigned do_checkbox = !!(flags & SCENE_CHECKBOX);
|
|
||||||
unsigned do_recurse = !!(flags & SCENE_RECURSE);
|
|
||||||
unsigned do_selection = !!(flags & SCENE_SELECTION);
|
|
||||||
|
|
||||||
nk_layout_row_dynamic(ui_ctx, 25, 1);
|
|
||||||
|
|
||||||
const char *objicon = editor_iconinstance(o);
|
|
||||||
if(!objicon) objicon = editor_iconclass(obj_type(o));
|
|
||||||
if(!objicon) objicon = ICON_MDI_CUBE_OUTLINE;
|
|
||||||
|
|
||||||
const char *objname = va("%s (%s)", obj_type(o), obj_name(o));
|
|
||||||
|
|
||||||
const char *objchevron =
|
|
||||||
!do_recurse || array_count(*obj_children(o)) <= 1 ? ICON_MDI_CIRCLE_SMALL :
|
|
||||||
editor_open(o) ? ICON_MDI_CHEVRON_DOWN : ICON_MDI_CHEVRON_RIGHT;
|
|
||||||
|
|
||||||
char *label = va("%*s%s%s %s", do_indent*(4+2*tabs), "", objchevron, objicon, objname);
|
|
||||||
|
|
||||||
const char *iconsL =
|
|
||||||
//editor_selected(o) ? ICON_MD_CHECK_BOX : ICON_MD_CHECK_BOX_OUTLINE_BLANK;
|
|
||||||
editor_selected(o) ? ICON_MDI_CHECKBOX_MARKED : ICON_MDI_CHECKBOX_BLANK_OUTLINE;
|
|
||||||
|
|
||||||
const char *iconsR = va("%s%s%s",
|
|
||||||
editor_script(o) ? ICON_MDI_SCRIPT : ICON_MDI_CIRCLE_SMALL,
|
|
||||||
editor_event(o) ? ICON_MDI_CALENDAR : ICON_MDI_CIRCLE_SMALL,
|
|
||||||
editor_visible(o) ? ICON_MDI_EYE_OUTLINE : ICON_MDI_EYE_CLOSED );
|
|
||||||
|
|
||||||
UI_TOOLBAR_OVERLAY_DECLARE(int choiceL, choiceR);
|
|
||||||
|
|
||||||
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
|
|
||||||
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
|
|
||||||
|
|
||||||
int clicked = nk_hovered_text(ui_ctx, label, strlen(label), NK_TEXT_LEFT, editor_selected(o));
|
|
||||||
if( clicked && nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,bounds.w*0.66,bounds.h })) )
|
|
||||||
editor_altselected( o );
|
|
||||||
|
|
||||||
vec2i offset_in_tree = {0};
|
|
||||||
|
|
||||||
if( do_indent ) {
|
|
||||||
float thickness = 2.f;
|
|
||||||
struct nk_color color = {255,255,255,64};
|
|
||||||
|
|
||||||
int offsx = 30;
|
|
||||||
int spacx = 10;
|
|
||||||
int lenx = (tabs+1)*spacx;
|
|
||||||
int halfy = bounds.h / 2;
|
|
||||||
int offsy = halfy + 2;
|
|
||||||
|
|
||||||
offset_in_tree = vec2i(bounds.x+offsx+lenx-spacx,bounds.y+offsy);
|
|
||||||
|
|
||||||
editor_settreeoffsety(o, offset_in_tree.y);
|
|
||||||
|
|
||||||
for( obj *p = obj_parent(o); p ; p = 0 )
|
|
||||||
nk_stroke_line(canvas, offset_in_tree.x-6,offset_in_tree.y, offset_in_tree.x-spacx,offset_in_tree.y, thickness, color),
|
|
||||||
nk_stroke_line(canvas, offset_in_tree.x-spacx,offset_in_tree.y,offset_in_tree.x-spacx,editor_treeoffsety(p)+4, thickness, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ui_contextual() ) {
|
|
||||||
API int editor_send(const char *);
|
|
||||||
|
|
||||||
int choice = ui_label(ICON_MD_BOOKMARK_ADDED "Toggle bookmarks (CTRL+B)");
|
|
||||||
if( choice & 1 ) editor_send("bookmark");
|
|
||||||
|
|
||||||
ui_contextual_end(!!choice);
|
|
||||||
}
|
|
||||||
|
|
||||||
UI_TOOLBAR_OVERLAY(choiceL,iconsL,nk_rgba_f(1,1,1,do_checkbox*ui_alpha*0.65),NK_TEXT_LEFT);
|
|
||||||
|
|
||||||
if( do_tags )
|
|
||||||
UI_TOOLBAR_OVERLAY(choiceR,iconsR,nk_rgba_f(1,1,1,ui_alpha*0.65),NK_TEXT_RIGHT);
|
|
||||||
|
|
||||||
if( choiceR == 3 ) editor_altscript( o );
|
|
||||||
if( choiceR == 2 ) editor_altevent( o);
|
|
||||||
if( choiceR == 1 ) editor_altvisible( o );
|
|
||||||
|
|
||||||
if( do_recurse && editor_open(o) ) {
|
|
||||||
for each_objchild(o,obj*,oo) {
|
|
||||||
editor_scene_(oo,flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( clicked && !choiceL && !choiceR ) {
|
|
||||||
int is_picking = input(KEY_CTRL);
|
|
||||||
if( !is_picking ) {
|
|
||||||
if( input(KEY_SHIFT) ) {
|
|
||||||
editor_selectgroup( editor_first_selected(), editor_last_selected() );
|
|
||||||
} else {
|
|
||||||
editor_unselect();
|
|
||||||
editor_setselected(o, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for( obj *p = obj_parent(o); p; p = obj_parent(p) ) {
|
|
||||||
editor_setopen(p, 1);
|
|
||||||
}
|
|
||||||
if( nk_input_is_mouse_hovering_rect(&ui_ctx->input, ((struct nk_rect) { bounds.x,bounds.y,offset_in_tree.x-bounds.x+UI_ICON_FONTSIZE/2,bounds.h })) ) {
|
|
||||||
editor_altopen( o );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
--tabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
int editor_scene(int window_mode) {
|
|
||||||
window_mode = EDITOR_WINDOW; // force window
|
|
||||||
|
|
||||||
if( editor_begin(SCENE_TITLE, window_mode)) {
|
|
||||||
// #define HELP ICON_MDI_INFORMATION_OUTLINE "@-A\n-B\n-C\n" ";"
|
|
||||||
int choice = ui_toolbar(ICON_MDI_PLUS "@New node (CTRL+N);" ICON_MDI_DOWNLOAD "@Save node (CTRL+S);" ICON_MDI_DOWNLOAD "@Save scene (SHIFT+CTRL+S);" ICON_MD_BOOKMARK_ADDED "@Toggle Bookmark (CTRL+B);");
|
|
||||||
if( choice == 1 ) editor_send("node_new");
|
|
||||||
if( choice == 2 ) editor_send("node_save");
|
|
||||||
if( choice == 3 ) editor_send("scene_save");
|
|
||||||
if( choice == 4 ) editor_send("bookmark");
|
|
||||||
|
|
||||||
array(obj*) bookmarks = 0;
|
|
||||||
for each_map_ptr(*editor_bookmarked_map(), void*,o,int,bookmarked) {
|
|
||||||
if( *bookmarked ) {
|
|
||||||
array_push(bookmarks, *o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( ui_collapse("!" ICON_MD_BOOKMARK "Bookmarks", "DEBUG:BOOKMARK")) {
|
|
||||||
for each_array( bookmarks, obj*, o )
|
|
||||||
editor_scene_( o, SCENE_ALL & ~(SCENE_RECURSE|SCENE_INDENT|SCENE_CHECKBOX) );
|
|
||||||
ui_collapse_end();
|
|
||||||
}
|
|
||||||
array_free(bookmarks);
|
|
||||||
|
|
||||||
editor_scene_( editor.root, SCENE_ALL );
|
|
||||||
|
|
||||||
for each_array( editor.objs, obj*, o )
|
|
||||||
editor_scene_( o, SCENE_ALL );
|
|
||||||
|
|
||||||
ui_separator();
|
|
||||||
|
|
||||||
// edit selection
|
|
||||||
for each_map(*editor_selected_map(), void*,o, int, k) {
|
|
||||||
if( k ) editor_inspect(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_end(window_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTORUN {
|
|
||||||
array_push(editor.subeditors, editor_scene);
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#define BROWSER_ICON ICON_MD_FOLDER_SPECIAL
|
|
||||||
#define BROWSER_TITLE "Browser " BROWSER_ICON
|
|
||||||
|
|
||||||
EDITOR_BIND(browser, "held(CTRL)&down(2)", { ui_show(BROWSER_TITLE, ui_visible(BROWSER_TITLE) ^ true); });
|
|
||||||
|
|
||||||
int editor_browser(int window_mode) {
|
|
||||||
window_mode = EDITOR_WINDOW; // force window
|
|
||||||
if( editor_begin(BROWSER_TITLE, window_mode) ) {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
editor_end(window_mode);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTORUN {
|
|
||||||
array_push(editor.subeditors, editor_browser);
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
#define TIMELINE_ICON ICON_MDI_CHART_TIMELINE
|
|
||||||
#define TIMELINE_TITLE "Timeline " TIMELINE_ICON
|
|
||||||
|
|
||||||
EDITOR_BIND(timeline, "held(CTRL)&down(3)", { ui_show(TIMELINE_TITLE, ui_visible(TIMELINE_TITLE) ^ true); });
|
|
||||||
|
|
||||||
int ui_tween(const char *label, tween_t *t) {
|
|
||||||
if( ui_filter && ui_filter[0] ) if( !strstr(label, ui_filter) ) return 0;
|
|
||||||
|
|
||||||
int expand_keys = label[0] == '!'; label += expand_keys;
|
|
||||||
const char *id = label;
|
|
||||||
if( strchr(id, '@') ) *strchr((char*)(id = (const char*)va("%s", label)), '@') = '\0';
|
|
||||||
|
|
||||||
enum { LABEL_SPACING = 250 };
|
|
||||||
enum { ROUNDING = 0 };
|
|
||||||
enum { THICKNESS = 1 };
|
|
||||||
enum { PIXELS_PER_SECOND = 60 };
|
|
||||||
enum { KEY_WIDTH = 5, KEY_HEIGHT = 5 };
|
|
||||||
enum { TIMELINE_HEIGHT = 25 };
|
|
||||||
enum { MARKER1_HEIGHT = 5, MARKER10_HEIGHT = 20, MARKER5_HEIGHT = (MARKER1_HEIGHT + MARKER10_HEIGHT) / 2 };
|
|
||||||
unsigned base_color = WHITE;
|
|
||||||
unsigned time_color = YELLOW;
|
|
||||||
unsigned duration_color = ORANGE;
|
|
||||||
unsigned key_color = GREEN;
|
|
||||||
|
|
||||||
int changed = 0;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// two rows with height:30 composed of three widgets
|
|
||||||
nk_layout_row_template_begin(ui_ctx, 30);
|
|
||||||
nk_layout_row_template_push_variable(ui_ctx, t->duration * PIXELS_PER_SECOND); // min 80px. can grow
|
|
||||||
nk_layout_row_template_end(ui_ctx);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char *sid = va("%s.%d", id, 0);
|
|
||||||
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
|
|
||||||
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
|
|
||||||
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
|
|
||||||
|
|
||||||
ui_label(label);
|
|
||||||
|
|
||||||
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
|
|
||||||
struct nk_rect bounds; nk_layout_peek(&bounds, ui_ctx);
|
|
||||||
bounds.y -= 30;
|
|
||||||
|
|
||||||
struct nk_rect baseline = bounds; baseline.y += 30/2;
|
|
||||||
baseline.x += LABEL_SPACING;
|
|
||||||
baseline.w -= LABEL_SPACING;
|
|
||||||
|
|
||||||
// tween duration
|
|
||||||
{
|
|
||||||
struct nk_rect pos = baseline;
|
|
||||||
pos.w = pos.x + t->duration * PIXELS_PER_SECOND;
|
|
||||||
pos.y -= TIMELINE_HEIGHT/2;
|
|
||||||
pos.h = TIMELINE_HEIGHT;
|
|
||||||
nk_stroke_rect(canvas, pos, ROUNDING, THICKNESS*2, AS_NKCOLOR(duration_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// tween ranges
|
|
||||||
for(int i = 0, end = array_count(t->keyframes) - 1; i < end; ++i) {
|
|
||||||
tween_keyframe_t *k = t->keyframes + i;
|
|
||||||
tween_keyframe_t *next = k + 1;
|
|
||||||
|
|
||||||
struct nk_rect pos = baseline;
|
|
||||||
pos.x += k->t * PIXELS_PER_SECOND;
|
|
||||||
pos.w = (next->t - k->t) * PIXELS_PER_SECOND;
|
|
||||||
pos.y -= TIMELINE_HEIGHT/2;
|
|
||||||
pos.h = TIMELINE_HEIGHT;
|
|
||||||
|
|
||||||
char *sid = va("%s.%d", id, i);
|
|
||||||
uint64_t hash = 14695981039346656037ULL, mult = 0x100000001b3ULL;
|
|
||||||
for(int i = 0; sid[i]; ++i) hash = (hash ^ sid[i]) * mult;
|
|
||||||
ui_hue = (hash & 0x3F) / (float)0x3F; ui_hue += !ui_hue;
|
|
||||||
|
|
||||||
struct nk_color c = nk_hsva_f(ui_hue, 0.75f, 0.8f, ui_alpha);
|
|
||||||
nk_fill_rect(canvas, pos, ROUNDING, k->ease == EASE_NOP ? AS_NKCOLOR(0) : c); // AS_NKCOLOR(track_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// horizontal line
|
|
||||||
nk_stroke_line(canvas, baseline.x, baseline.y, baseline.x+baseline.w,baseline.y, THICKNESS, AS_NKCOLOR(base_color));
|
|
||||||
|
|
||||||
// unit, 5-unit and 10-unit markers
|
|
||||||
for( float i = 0, j = 0; i < baseline.w; i += PIXELS_PER_SECOND/10, ++j ) {
|
|
||||||
int len = !((int)j%10) ? MARKER10_HEIGHT : !((int)j%5) ? MARKER5_HEIGHT : MARKER1_HEIGHT;
|
|
||||||
nk_stroke_line(canvas, baseline.x+i, baseline.y-len, baseline.x+i, baseline.y+len, THICKNESS, AS_NKCOLOR(base_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// time marker
|
|
||||||
float px = t->time * PIXELS_PER_SECOND;
|
|
||||||
nk_stroke_line(canvas, baseline.x+px, bounds.y, baseline.x+px, bounds.y+bounds.h, THICKNESS*2, AS_NKCOLOR(time_color));
|
|
||||||
nk_draw_symbol(canvas, NK_SYMBOL_TRIANGLE_DOWN, ((struct nk_rect){ baseline.x+px-4,bounds.y-4-8,8,8}), /*bg*/AS_NKCOLOR(0), /*fg*/AS_NKCOLOR(time_color), 0.f/*border_width*/, ui_ctx->style.font);
|
|
||||||
|
|
||||||
// key markers
|
|
||||||
for each_array_ptr(t->keyframes, tween_keyframe_t, k) {
|
|
||||||
struct nk_rect pos = baseline;
|
|
||||||
pos.x += k->t * PIXELS_PER_SECOND;
|
|
||||||
|
|
||||||
vec2 romboid[] = {
|
|
||||||
{pos.x-KEY_WIDTH,pos.y}, {pos.x,pos.y-KEY_HEIGHT},
|
|
||||||
{pos.x+KEY_WIDTH,pos.y}, {pos.x,pos.y+KEY_HEIGHT}
|
|
||||||
};
|
|
||||||
|
|
||||||
nk_fill_polygon(canvas, (float*)romboid, countof(romboid), AS_NKCOLOR(key_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
// keys ui
|
|
||||||
if( expand_keys )
|
|
||||||
for(int i = 0, end = array_count(t->keyframes); i < end; ++i) {
|
|
||||||
tween_keyframe_t *k = t->keyframes + i;
|
|
||||||
if( ui_collapse(va("Key %d", i), va("%s.%d", id, i))) {
|
|
||||||
changed |= ui_float("Time", &k->t);
|
|
||||||
changed |= ui_float3("Value", &k->v.x);
|
|
||||||
changed |= ui_list("Ease", ease_enums(), EASE_NUM, &k->ease );
|
|
||||||
ui_collapse_end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
tween_t* rand_tween() {
|
|
||||||
tween_t demo = tween();
|
|
||||||
int num_keys = randi(2,8);
|
|
||||||
double t = 0;
|
|
||||||
for( int i = 0; i < num_keys; ++i) {
|
|
||||||
tween_setkey(&demo, t, scale3(vec3(randf(),randf(),randf()),randi(-5,5)), randi(0,EASE_NUM) );
|
|
||||||
t += randi(1,5) / ((float)(1 << randi(0,2)));
|
|
||||||
}
|
|
||||||
tween_t *p = CALLOC(1, sizeof(tween_t));
|
|
||||||
memcpy(p, &demo, sizeof(tween_t));
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
int editor_timeline(int window_mode) {
|
|
||||||
static array(tween_t*) tweens = 0;
|
|
||||||
|
|
||||||
do_once {
|
|
||||||
array_push(tweens, rand_tween());
|
|
||||||
}
|
|
||||||
|
|
||||||
if( editor.t == 0 )
|
|
||||||
for each_array(tweens, tween_t*,t) {
|
|
||||||
tween_reset(t);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
for each_array(tweens, tween_t*,t) {
|
|
||||||
tween_update(t, editor.dt);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *selected = NULL;
|
|
||||||
if( editor_begin(TIMELINE_TITLE, window_mode) ) {
|
|
||||||
|
|
||||||
int choice = ui_toolbar(ICON_MDI_PLUS ";" ICON_MDI_MINUS );
|
|
||||||
if( choice == 1 ) array_push(tweens, rand_tween());
|
|
||||||
if( choice == 2 && selected ) {
|
|
||||||
int target = -1;
|
|
||||||
for( int i = 0, end = array_count(tweens); i < end; ++i ) if( tweens[i] == selected ) { target = i; break; }
|
|
||||||
if( target >= 0 ) { array_erase_slow(tweens, target); selected = NULL; }
|
|
||||||
}
|
|
||||||
|
|
||||||
for each_array(tweens, tween_t*,t) {
|
|
||||||
ui_tween(va("%s%p@%05.2fs Value: %s", t == selected ? "!":"", t, t->time, ftoa3(t->result)), t);
|
|
||||||
if(ui_label_icon_clicked_L.x) selected = (t != selected) ? t : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_end(window_mode);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTORUN {
|
|
||||||
array_push(editor.subeditors, editor_timeline);
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
#define CONSOLE_ICON ICON_MDI_CONSOLE
|
|
||||||
#define CONSOLE_TITLE "Console " CONSOLE_ICON
|
|
||||||
|
|
||||||
EDITOR_BIND(console, "held(CTRL)&down(4)", { ui_show(CONSOLE_TITLE, ui_visible(CONSOLE_TITLE) ^ true); });
|
|
||||||
|
|
||||||
int editor_console(int window_mode) {
|
|
||||||
if( editor_begin(CONSOLE_TITLE, window_mode) ) {
|
|
||||||
|
|
||||||
// peek complete window space
|
|
||||||
struct nk_rect bounds = nk_window_get_content_region(ui_ctx);
|
|
||||||
|
|
||||||
enum { CONSOLE_LINE_HEIGHT = 20 };
|
|
||||||
static array(char*) lines = 0;
|
|
||||||
do_once {
|
|
||||||
array_push(lines, stringf("> Editor v%s. Type `%s` for help.", EDITOR_VERSION, ""));
|
|
||||||
}
|
|
||||||
int max_lines = (bounds.h - UI_ROW_HEIGHT) / (CONSOLE_LINE_HEIGHT * 2);
|
|
||||||
if( max_lines >= 1 ) {
|
|
||||||
nk_layout_row_static(ui_ctx, bounds.h - UI_ROW_HEIGHT, bounds.w, 1);
|
|
||||||
if(nk_group_begin(ui_ctx, "console.group", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER)) {
|
|
||||||
nk_layout_row_static(ui_ctx, CONSOLE_LINE_HEIGHT, bounds.w, 1);
|
|
||||||
for( int i = array_count(lines); i < max_lines; ++i )
|
|
||||||
array_push_front(lines, 0);
|
|
||||||
for( int i = array_count(lines) - max_lines; i < array_count(lines); ++i ) {
|
|
||||||
if( !lines[i] ) {
|
|
||||||
#if 0 // debug
|
|
||||||
nk_label_wrap(ui_ctx, va("%d.A/%d",i+1,max_lines));
|
|
||||||
nk_label_wrap(ui_ctx, va("%d.B/%d",i+1,max_lines));
|
|
||||||
#else
|
|
||||||
nk_label_wrap(ui_ctx, "");
|
|
||||||
nk_label_wrap(ui_ctx, "");
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
nk_label_wrap(ui_ctx, lines[i]);
|
|
||||||
const char *answer = isdigit(*lines[i]) ? editor_recv( atoi(lines[i]), 0 ) : NULL;
|
|
||||||
nk_label_wrap(ui_ctx, answer ? answer : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nk_group_end(ui_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static char *cmd = 0;
|
|
||||||
if( ui_string(NULL, &cmd) ) {
|
|
||||||
int jobid = editor_send(cmd);
|
|
||||||
array_push(lines, stringf("%d> %s", jobid, cmd));
|
|
||||||
cmd[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_end(window_mode);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTORUN {
|
|
||||||
array_push(editor.subeditors, editor_console);
|
|
||||||
}
|
|
|
@ -1,881 +0,0 @@
|
||||||
#define NODES_ICON ICON_MDI_GRAPH
|
|
||||||
#define NODES_TITLE "Nodes " NODES_ICON
|
|
||||||
|
|
||||||
EDITOR_BIND(nodes, "held(CTRL)&down(5)", { ui_show(NODES_TITLE, ui_visible(NODES_TITLE) ^ true); });
|
|
||||||
|
|
||||||
/*
|
|
||||||
A basic node-based UI built with Nuklear.
|
|
||||||
Builds on the node editor example included in Nuklear v1.00, with the aim of
|
|
||||||
being used as a prototype for implementing a functioning node editor.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Nodes of different types. Currently their implementations are #included in
|
|
||||||
the main file, but they could easily be turned into eg. a plugin system.
|
|
||||||
- Pins/pins of different types -- currently float values and colors.
|
|
||||||
- Adding and removing nodes.
|
|
||||||
- Linking nodes, with validation (one link per input, only link similar pins).
|
|
||||||
- Detaching and moving links.
|
|
||||||
- Evaluation of output values of connected nodes.
|
|
||||||
- Memory management based on fixed size arrays for links and node pointers
|
|
||||||
- Multiple node types
|
|
||||||
- Multiple pin types
|
|
||||||
- Linking between pins of the same type
|
|
||||||
- Detaching and reattaching links
|
|
||||||
- Getting value from linked node if pin is connected
|
|
||||||
|
|
||||||
Todo:
|
|
||||||
- Complete pin types.
|
|
||||||
- Allow dragging from output to input pin.
|
|
||||||
- Cut link by CTRL+clicking input pin.
|
|
||||||
- Cut link by drawing intersect line on a link.
|
|
||||||
- Group elemnts together with mouse, or LSHIFT+clicking.
|
|
||||||
- Drag groups.
|
|
||||||
- DEL elements.
|
|
||||||
- DEL groups.
|
|
||||||
- CTRL-C/CTRL-V/CTRL-X elements.
|
|
||||||
- CTRL-C/CTRL-V/CTRL-X groups.
|
|
||||||
- CTRL-Z,CTRL-Y.
|
|
||||||
- CTRL-N.
|
|
||||||
- CTRL-L,CTRL-S.
|
|
||||||
- CTRL-F.
|
|
||||||
- CTRL-Wheel Zooming.
|
|
||||||
- Allow to extend node types from Lua.
|
|
||||||
|
|
||||||
Extra todo:
|
|
||||||
- Execution Flow (see: nk_stroke_triangle, nk_fill_triangle)
|
|
||||||
- Complete missing nodes (see: nk_draw_image, nk_draw_text)
|
|
||||||
- Right-click could visualize node/board diagram as Lua script.
|
|
||||||
- Once that done, copy/pasting scripts should work within editor.
|
|
||||||
|
|
||||||
Sources:
|
|
||||||
- https://github.com/Immediate-Mode-UI/Nuklear/pull/561
|
|
||||||
- https://github.com/vurtun/nuklear/blob/master/demo/node_editor.c
|
|
||||||
*/
|
|
||||||
|
|
||||||
typedef enum pin_type_t {
|
|
||||||
type_flow,
|
|
||||||
type_int,type_float,
|
|
||||||
type_block,type_texture,type_image,
|
|
||||||
type_color,
|
|
||||||
/*
|
|
||||||
type_bool,
|
|
||||||
type_char, type_string,
|
|
||||||
type_int2, type_int3, type_int4,
|
|
||||||
type_float2, type_float3, type_float4,
|
|
||||||
type_array, type_map,
|
|
||||||
*/
|
|
||||||
|
|
||||||
type_total
|
|
||||||
} pin_type_t;
|
|
||||||
|
|
||||||
struct node_pin {
|
|
||||||
pin_type_t pin_type;
|
|
||||||
nk_bool is_connected;
|
|
||||||
struct node* connected_node;
|
|
||||||
int connected_pin;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node {
|
|
||||||
int ID;
|
|
||||||
char name[32];
|
|
||||||
struct nk_rect bounds;
|
|
||||||
int input_count;
|
|
||||||
int output_count;
|
|
||||||
struct node_pin *inputs;
|
|
||||||
struct node_pin *outputs;
|
|
||||||
struct {
|
|
||||||
float in_padding_x;
|
|
||||||
float in_padding_y;
|
|
||||||
float in_spacing_y;
|
|
||||||
float out_padding_x;
|
|
||||||
float out_padding_y;
|
|
||||||
float out_spacing_y;
|
|
||||||
} pin_spacing; /* Maybe this should be called "node_layout" and include the bounds? */
|
|
||||||
struct node *next; /* Z ordering only */
|
|
||||||
struct node *prev; /* Z ordering only */
|
|
||||||
|
|
||||||
void* (*eval_func)(struct node*, int oIndex);
|
|
||||||
void (*display_func)(struct nk_context*, struct node*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_link {
|
|
||||||
struct node* input_node;
|
|
||||||
int input_pin;
|
|
||||||
struct node* output_node;
|
|
||||||
int output_pin;
|
|
||||||
nk_bool is_active;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_linking {
|
|
||||||
int active;
|
|
||||||
struct node *node;
|
|
||||||
int input_id;
|
|
||||||
int input_pin;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_editor {
|
|
||||||
int initialized;
|
|
||||||
struct node *node_buf[32];
|
|
||||||
struct node_link links[64];
|
|
||||||
struct node *output_node;
|
|
||||||
struct node *begin;
|
|
||||||
struct node *end;
|
|
||||||
int node_count;
|
|
||||||
int link_count;
|
|
||||||
struct nk_rect bounds;
|
|
||||||
struct node *selected;
|
|
||||||
int show_grid;
|
|
||||||
struct nk_vec2 scrolling;
|
|
||||||
struct node_linking linking;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* === PROTOTYPES === */
|
|
||||||
/* The node implementations need these two functions. */
|
|
||||||
/* These could/should go in a header file along with the node and node_pin structs and be #included in the node implementations */
|
|
||||||
|
|
||||||
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count);
|
|
||||||
void* node_editor_eval_connected(struct node *node, int input_pin_number);
|
|
||||||
/* ================== */
|
|
||||||
|
|
||||||
/* === NODE TYPE IMPLEMENTATIONS === */
|
|
||||||
|
|
||||||
#define NODE_DEFAULT_ROW_HEIGHT 25
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------
|
|
||||||
// #include "node_output.h"
|
|
||||||
|
|
||||||
struct node_type_output {
|
|
||||||
struct node node;
|
|
||||||
struct nk_colorf input_val;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct nk_colorf *node_output_get(struct node* node) {
|
|
||||||
struct node_type_output *output_node = (struct node_type_output*)node;
|
|
||||||
if (!node->inputs[0].is_connected) {
|
|
||||||
struct nk_colorf black = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
output_node->input_val = black;
|
|
||||||
}
|
|
||||||
return &output_node->input_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_output_display(struct nk_context *ctx, struct node *node) {
|
|
||||||
if (node->inputs[0].is_connected) {
|
|
||||||
struct node_type_output *output_node = (struct node_type_output*)node;
|
|
||||||
output_node->input_val = *(struct nk_colorf*)node_editor_eval_connected(node, 0);
|
|
||||||
nk_layout_row_dynamic(ctx, 60, 1);
|
|
||||||
nk_button_color(ctx, nk_rgba_cf(output_node->input_val));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct node* node_output_create(struct node_editor *editor, struct nk_vec2 position) {
|
|
||||||
struct node_type_output *output_node = (struct node_type_output*)node_editor_add(editor, sizeof(struct node_type_output), "Output", nk_rect(position.x, position.y, 100, 100), 1, 0);
|
|
||||||
if (output_node){
|
|
||||||
output_node->node.inputs[0].pin_type = type_color;
|
|
||||||
output_node->node.display_func = node_output_display;
|
|
||||||
}
|
|
||||||
return (struct node*)output_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------
|
|
||||||
// #include "node_float.h"
|
|
||||||
|
|
||||||
struct node_type_float {
|
|
||||||
struct node node;
|
|
||||||
float output_val;
|
|
||||||
};
|
|
||||||
|
|
||||||
static float *node_float_eval(struct node* node, int oIndex) {
|
|
||||||
struct node_type_float *float_node = (struct node_type_float*)node;
|
|
||||||
NK_ASSERT(oIndex == 0);
|
|
||||||
return &float_node->output_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_float_draw(struct nk_context *ctx, struct node *node) {
|
|
||||||
struct node_type_float *float_node = (struct node_type_float*)node;
|
|
||||||
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
|
|
||||||
float_node->output_val = nk_propertyf(ctx, "#Value:", 0.0f, float_node->output_val, 1.0f, 0.01f, 0.01f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_float_create(struct node_editor *editor, struct nk_vec2 position) {
|
|
||||||
struct node_type_float *float_node = (struct node_type_float*)node_editor_add(editor, sizeof(struct node_type_float), "Float", nk_rect(position.x, position.y, 180, 75), 0, 1);
|
|
||||||
if (float_node)
|
|
||||||
{
|
|
||||||
float_node->output_val = 1.0f;
|
|
||||||
float_node->node.display_func = node_float_draw;
|
|
||||||
float_node->node.eval_func = (void*(*)(struct node*, int)) node_float_eval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------
|
|
||||||
// #include "node_color.h"
|
|
||||||
|
|
||||||
struct node_type_color {
|
|
||||||
struct node node;
|
|
||||||
float input_val[4];
|
|
||||||
struct nk_colorf output_val;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct nk_colorf *node_color_eval(struct node* node, int oIndex)
|
|
||||||
{
|
|
||||||
struct node_type_color *color_node = (struct node_type_color*)node;
|
|
||||||
NK_ASSERT(oIndex == 0); /* only one output connector */
|
|
||||||
|
|
||||||
return &color_node->output_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void node_color_draw(struct nk_context *ctx, struct node *node)
|
|
||||||
{
|
|
||||||
struct node_type_color *color_node = (struct node_type_color*)node;
|
|
||||||
float eval_result; /* Get the values from connected nodes into this so the inputs revert on disconnect */
|
|
||||||
const char* labels[4] = {"#R:","#G:","#B:","#A:"};
|
|
||||||
float color_val[4]; /* Because we can't just loop through the struct... */
|
|
||||||
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
|
|
||||||
nk_button_color(ctx, nk_rgba_cf(color_node->output_val));
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
if (color_node->node.inputs[i].is_connected) {
|
|
||||||
eval_result = *(float*)node_editor_eval_connected(node, i);
|
|
||||||
eval_result = nk_propertyf(ctx, labels[i], eval_result, eval_result, eval_result, 0.01f, 0.01f);
|
|
||||||
color_val[i] = eval_result;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
color_node->input_val[i] = nk_propertyf(ctx, labels[i], 0.0f, color_node->input_val[i], 1.0f, 0.01f, 0.01f);
|
|
||||||
color_val[i] = color_node->input_val[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
color_node->output_val.r = color_val[0];
|
|
||||||
color_node->output_val.g = color_val[1];
|
|
||||||
color_node->output_val.b = color_val[2];
|
|
||||||
color_node->output_val.a = color_val[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_color_create(struct node_editor *editor, struct nk_vec2 position)
|
|
||||||
{
|
|
||||||
struct node_type_color *color_node = (struct node_type_color*)node_editor_add(editor, sizeof(struct node_type_color), "Color", nk_rect(position.x, position.y, 180, 190), 4, 1);
|
|
||||||
if (color_node)
|
|
||||||
{
|
|
||||||
const struct nk_colorf black = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
||||||
|
|
||||||
for (int i = 0; i < color_node->node.input_count; i++)
|
|
||||||
color_node->node.inputs[i].pin_type = type_float;
|
|
||||||
color_node->node.outputs[0].pin_type = type_color;
|
|
||||||
|
|
||||||
color_node->node.pin_spacing.in_padding_y += NODE_DEFAULT_ROW_HEIGHT;
|
|
||||||
|
|
||||||
color_node->input_val[0] = 0.0f;
|
|
||||||
color_node->input_val[1] = 0.0f;
|
|
||||||
color_node->input_val[2] = 0.0f;
|
|
||||||
color_node->input_val[3] = 1.0f;
|
|
||||||
|
|
||||||
color_node->output_val = black;
|
|
||||||
|
|
||||||
color_node->node.display_func = node_color_draw;
|
|
||||||
color_node->node.eval_func = (void*(*)(struct node*, int)) node_color_eval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------
|
|
||||||
// #include "node_blend.h"
|
|
||||||
|
|
||||||
struct node_type_blend {
|
|
||||||
struct node node;
|
|
||||||
struct nk_colorf input_val[2];
|
|
||||||
struct nk_colorf output_val;
|
|
||||||
float blend_val;
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct nk_colorf *node_blend_eval(struct node *node, int oIndex) {
|
|
||||||
struct node_type_blend* blend_node = (struct node_type_blend*)node;
|
|
||||||
return &blend_node->output_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_blend_display(struct nk_context *ctx, struct node *node) {
|
|
||||||
struct node_type_blend *blend_node = (struct node_type_blend*)node;
|
|
||||||
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
float blend_amnt;
|
|
||||||
|
|
||||||
nk_layout_row_dynamic(ctx, NODE_DEFAULT_ROW_HEIGHT, 1);
|
|
||||||
for (int i = 0; i < 2; i++){
|
|
||||||
if(node->inputs[i].is_connected) {
|
|
||||||
blend_node->input_val[i] = *(struct nk_colorf*)node_editor_eval_connected(node, i);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
blend_node->input_val[i] = blank;
|
|
||||||
}
|
|
||||||
nk_button_color(ctx, nk_rgba_cf(blend_node->input_val[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->inputs[2].is_connected) {
|
|
||||||
blend_amnt = *(float*)node_editor_eval_connected(node, 2);
|
|
||||||
blend_amnt = nk_propertyf(ctx, "#Blend", blend_amnt, blend_amnt, blend_amnt, 0.01f, 0.01f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
blend_node->blend_val = nk_propertyf(ctx, "#Blend", 0.0f, blend_node->blend_val, 1.0f, 0.01f, 0.01f);
|
|
||||||
blend_amnt = blend_node->blend_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(node->inputs[0].is_connected && node->inputs[1].is_connected) {
|
|
||||||
blend_node->output_val.r = blend_node->input_val[0].r * (1.0f-blend_amnt) + blend_node->input_val[1].r * blend_amnt;
|
|
||||||
blend_node->output_val.g = blend_node->input_val[0].g * (1.0f-blend_amnt) + blend_node->input_val[1].g * blend_amnt;
|
|
||||||
blend_node->output_val.b = blend_node->input_val[0].b * (1.0f-blend_amnt) + blend_node->input_val[1].b * blend_amnt;
|
|
||||||
blend_node->output_val.a = blend_node->input_val[0].a * (1.0f-blend_amnt) + blend_node->input_val[1].a * blend_amnt;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
blend_node->output_val = blank;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_blend_create(struct node_editor *editor, struct nk_vec2 position) {
|
|
||||||
struct node_type_blend* blend_node = (struct node_type_blend*)node_editor_add(editor, sizeof(struct node_type_blend), "Blend", nk_rect(position.x, position.y, 180, 130), 3, 1);
|
|
||||||
if (blend_node) {
|
|
||||||
const struct nk_colorf blank = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
||||||
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
|
|
||||||
blend_node->node.inputs[i].pin_type = type_color;
|
|
||||||
blend_node->node.outputs[0].pin_type = type_color;
|
|
||||||
|
|
||||||
// blend_node->node.pin_spacing.in_padding_y = 42.0f;
|
|
||||||
// blend_node->node.pin_spacing.in_spacing_y = 29.0f;
|
|
||||||
|
|
||||||
for (int i = 0; i < (int)NK_LEN(blend_node->input_val); i++)
|
|
||||||
blend_node->input_val[i] = blank;
|
|
||||||
blend_node->output_val = blank;
|
|
||||||
|
|
||||||
blend_node->blend_val = 0.5f;
|
|
||||||
|
|
||||||
blend_node->node.display_func = node_blend_display;
|
|
||||||
blend_node->node.eval_func = (void*(*)(struct node*, int)) node_blend_eval;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================================= */
|
|
||||||
|
|
||||||
#define NK_RGB3(r,g,b) {r,g,b,255}
|
|
||||||
#define BG_COLOR ((struct nk_color){60,60,60,192}) // nk_rgba(0,0,0,192)
|
|
||||||
|
|
||||||
static
|
|
||||||
struct editor_node_style {
|
|
||||||
int pin_type;
|
|
||||||
const char *shape;
|
|
||||||
struct nk_color color_idle;
|
|
||||||
struct nk_color color_hover;
|
|
||||||
} styles[] = {
|
|
||||||
// order matters:
|
|
||||||
{ type_flow, "triangle_right", NK_RGB3(200,200,200), NK_RGB3(255,255,255) }, // if .num_links == 0
|
|
||||||
{ type_int, "circle", NK_RGB3(33,227,175), NK_RGB3(135,239,195) },
|
|
||||||
{ type_float, "circle", NK_RGB3(156,253,65), NK_RGB3(144,225,137) },
|
|
||||||
{ type_block, "circle", NK_RGB3(6,165,239), NK_RGB3(137,196,247) },
|
|
||||||
{ type_texture, "circle", NK_RGB3(148,0,0), NK_RGB3(183,137,137) },
|
|
||||||
{ type_image, "circle", NK_RGB3(200,130,255), NK_RGB3(220,170,255) },
|
|
||||||
{ type_color, "circle", NK_RGB3(252,200,35), NK_RGB3(255,217,140) },
|
|
||||||
};
|
|
||||||
|
|
||||||
#define COLOR_FLOW_HI styles[type_flow].color_hover
|
|
||||||
#define COLOR_FLOW_LO styles[type_flow].color_idle
|
|
||||||
|
|
||||||
#define GRID_SIZE 64.0f
|
|
||||||
#define GRID_COLOR ((struct nk_color)NK_RGB3(80,80,120))
|
|
||||||
#define GRID_THICKNESS 1.0f
|
|
||||||
|
|
||||||
// 4 colors: top-left, top-right, bottom-right, bottom-left
|
|
||||||
#define GRID_BG_COLORS ((struct nk_color){30,30,30,255}), ((struct nk_color){40,20,0,255}), ((struct nk_color){30,30,30,255}), ((struct nk_color){20,30,40,255})
|
|
||||||
|
|
||||||
#define LINK_THICKNESS 1.0f
|
|
||||||
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
|
|
||||||
vec2 a = (POINT_A); \
|
|
||||||
vec2 b = (POINT_B); \
|
|
||||||
nk_stroke_line(canvas, a.x, a.y, b.x, b.y, LINK_THICKNESS, COLOR); \
|
|
||||||
} while(0)
|
|
||||||
#undef LINK_DRAW
|
|
||||||
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
|
|
||||||
vec2 a = (POINT_A); \
|
|
||||||
vec2 b = (POINT_B); \
|
|
||||||
nk_stroke_curve(canvas, a.x, a.y, a.x+50, a.y, b.x-50, b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
|
|
||||||
} while(0)
|
|
||||||
#undef LINK_DRAW
|
|
||||||
#define LINK_DRAW(POINT_A,POINT_B,COLOR) do { \
|
|
||||||
vec2 a = (POINT_A); \
|
|
||||||
vec2 b = (POINT_B); \
|
|
||||||
float dist2 = len2( sub2( ptr2(&b.x), ptr2(&a.x) ) ); \
|
|
||||||
vec2 mid_a = mix2( ptr2(&a.x), ptr2(&b.x), 0.25 ); mid_a.y += dist2/2; \
|
|
||||||
vec2 mid_b = mix2( ptr2(&a.x), ptr2(&b.x), 0.75 ); mid_b.y += dist2/3; \
|
|
||||||
nk_stroke_curve(canvas, a.x, a.y, mid_a.x, mid_a.y, mid_b.x, mid_b.y, b.x, b.y, LINK_THICKNESS, COLOR); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
|
|
||||||
#define PIN_RADIUS 12
|
|
||||||
#define PIN_THICKNESS 1.0f
|
|
||||||
#define PIN_DRAW(PIN_ADDR,POINT,RADIUS) do { \
|
|
||||||
circle.x = (POINT).x - (RADIUS) / 2; \
|
|
||||||
circle.y = (POINT).y - (RADIUS) / 2; \
|
|
||||||
circle.w = circle.h = (RADIUS); \
|
|
||||||
struct nk_color color = node_get_type_color((PIN_ADDR).pin_type); \
|
|
||||||
if((PIN_ADDR).is_connected) \
|
|
||||||
nk_fill_circle(canvas, circle, color); \
|
|
||||||
else \
|
|
||||||
nk_stroke_circle(canvas, circle, PIN_THICKNESS, color); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
|
|
||||||
static struct nk_color node_get_type_color(unsigned pin_type) {
|
|
||||||
for( int i = 0; i < type_total; ++i )
|
|
||||||
if( styles[i].pin_type == pin_type )
|
|
||||||
return styles[i].color_idle;
|
|
||||||
return ((struct nk_color)NK_RGB3(255,0,255));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_editor_push(struct node_editor *editor, struct node *node) {
|
|
||||||
if (!editor->begin) {
|
|
||||||
node->next = NULL;
|
|
||||||
node->prev = NULL;
|
|
||||||
editor->begin = node;
|
|
||||||
editor->end = node;
|
|
||||||
} else {
|
|
||||||
node->prev = editor->end;
|
|
||||||
if (editor->end)
|
|
||||||
editor->end->next = node;
|
|
||||||
node->next = NULL;
|
|
||||||
editor->end = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_editor_pop(struct node_editor *editor, struct node *node) {
|
|
||||||
if (node->next)
|
|
||||||
node->next->prev = node->prev;
|
|
||||||
if (node->prev)
|
|
||||||
node->prev->next = node->next;
|
|
||||||
if (editor->end == node)
|
|
||||||
editor->end = node->prev;
|
|
||||||
if (editor->begin == node)
|
|
||||||
editor->begin = node->next;
|
|
||||||
node->next = NULL;
|
|
||||||
node->prev = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct node* node_editor_find_by_id(struct node_editor *editor, int ID) {
|
|
||||||
struct node *iter = editor->begin;
|
|
||||||
while (iter) {
|
|
||||||
if (iter->ID == ID)
|
|
||||||
return iter;
|
|
||||||
iter = iter->next;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct node_link* node_editor_find_link_by_output(struct node_editor *editor, struct node *output_node, int node_input_connector) {
|
|
||||||
for( int i = 0; i < editor->link_count; i++ ) {
|
|
||||||
if (editor->links[i].output_node == output_node &&
|
|
||||||
editor->links[i].output_pin == node_input_connector &&
|
|
||||||
editor->links[i].is_active == nk_true) {
|
|
||||||
return &editor->links[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct node_link* node_editor_find_link_by_input(struct node_editor *editor, struct node *input_node, int node_output_connector) {
|
|
||||||
for( int i = 0; i < editor->link_count; i++ ) {
|
|
||||||
if (editor->links[i].input_node == input_node &&
|
|
||||||
editor->links[i].input_pin == node_output_connector &&
|
|
||||||
editor->links[i].is_active == nk_true) {
|
|
||||||
return &editor->links[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_editor_delete_link(struct node_link *link) {
|
|
||||||
link->is_active = nk_false;
|
|
||||||
link->input_node->outputs[link->input_pin].is_connected = nk_false;
|
|
||||||
link->output_node->inputs[link->output_pin].is_connected = nk_false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct node* node_editor_add(struct node_editor *editor, size_t nodeSize, const char *name, struct nk_rect bounds, int in_count, int out_count) {
|
|
||||||
static int IDs = 0;
|
|
||||||
struct node *node = NULL;
|
|
||||||
|
|
||||||
if ((nk_size)editor->node_count < NK_LEN(editor->node_buf)) {
|
|
||||||
/* node_buf has unused pins */
|
|
||||||
node = MALLOC(nodeSize);
|
|
||||||
editor->node_buf[editor->node_count++] = node;
|
|
||||||
node->ID = IDs++;
|
|
||||||
} else {
|
|
||||||
/* check for freed up pins in node_buf */
|
|
||||||
for (int i = 0; i < editor->node_count; i++) {
|
|
||||||
if (editor->node_buf[i] == NULL) {
|
|
||||||
node = MALLOC(nodeSize);
|
|
||||||
editor->node_buf[i] = node;
|
|
||||||
node->ID = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node == NULL) {
|
|
||||||
puts("Node creation failed");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
node->bounds = bounds;
|
|
||||||
|
|
||||||
node->input_count = in_count;
|
|
||||||
node->output_count = out_count;
|
|
||||||
|
|
||||||
node->inputs = MALLOC(node->input_count * sizeof(struct node_pin));
|
|
||||||
node->outputs = MALLOC(node->output_count * sizeof(struct node_pin));
|
|
||||||
|
|
||||||
for (int i = 0; i < node->input_count; i++) {
|
|
||||||
node->inputs[i].is_connected = nk_false;
|
|
||||||
node->inputs[i].pin_type = type_float; /* default pin type */
|
|
||||||
}
|
|
||||||
for (int i = 0; i < node->output_count; i++) {
|
|
||||||
node->outputs[i].is_connected = nk_false;
|
|
||||||
node->outputs[i].pin_type = type_float; /* default pin type */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* default pin spacing */
|
|
||||||
node->pin_spacing.in_padding_x = 2;
|
|
||||||
node->pin_spacing.in_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
|
|
||||||
node->pin_spacing.in_spacing_y = 25; // row height+border
|
|
||||||
node->pin_spacing.out_padding_x = 3;
|
|
||||||
node->pin_spacing.out_padding_y = 32 + 25/2 + 6; // titlebar height + next half row + adjust
|
|
||||||
node->pin_spacing.out_spacing_y = 25; // row height+border
|
|
||||||
|
|
||||||
strcpy(node->name, name);
|
|
||||||
node_editor_push(editor, node);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *node_editor_eval_connected(struct node* node, int input_pin_number) {
|
|
||||||
NK_ASSERT(node->inputs[input_pin_number].is_connected);
|
|
||||||
return node->inputs[input_pin_number].connected_node->eval_func(node->inputs[input_pin_number].connected_node, node->inputs[input_pin_number].connected_pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_editor_link(struct node_editor *editor, struct node *in_node, int in_pin, struct node *out_node, int out_pin) {
|
|
||||||
/* Confusingly, in and out nodes/pins here refer to the inputs and outputs OF THE LINK ITSELF, not the nodes */
|
|
||||||
struct node_link *link = NULL;
|
|
||||||
|
|
||||||
if ((nk_size)editor->link_count < NK_LEN(editor->links)) {
|
|
||||||
link = &editor->links[editor->link_count++];
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < (int)NK_LEN(editor->links); i++)
|
|
||||||
{
|
|
||||||
if (editor->links[i].is_active == nk_false) {
|
|
||||||
link = &editor->links[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (link) {
|
|
||||||
out_node->inputs[out_pin].is_connected = nk_true;
|
|
||||||
in_node->outputs[in_pin].is_connected = nk_true;
|
|
||||||
out_node->inputs[out_pin].connected_node = in_node;
|
|
||||||
out_node->inputs[out_pin].connected_pin = in_pin;
|
|
||||||
|
|
||||||
link->input_node = in_node;
|
|
||||||
link->input_pin = in_pin;
|
|
||||||
link->output_node = out_node;
|
|
||||||
link->output_pin = out_pin;
|
|
||||||
link->is_active = nk_true;
|
|
||||||
} else {
|
|
||||||
puts("Too many links");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void node_editor_init(struct node_editor *editor) {
|
|
||||||
if (editor->initialized) return;
|
|
||||||
|
|
||||||
struct nk_rect total_space = nk_window_get_content_region(ui_ctx);
|
|
||||||
struct nk_vec2 output_node_position = { total_space.w*2/3, total_space.h/3 };
|
|
||||||
struct nk_vec2 color_node_position = { total_space.w*1/4, total_space.h/3 };
|
|
||||||
|
|
||||||
memset(editor, 0, sizeof(*editor));
|
|
||||||
|
|
||||||
editor->output_node = node_output_create(editor, output_node_position);
|
|
||||||
node_color_create(editor, color_node_position);
|
|
||||||
editor->show_grid = nk_true;
|
|
||||||
|
|
||||||
editor->initialized = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int node_editor(struct node_editor *editor) {
|
|
||||||
int n = 0;
|
|
||||||
struct nk_rect total_space;
|
|
||||||
const struct nk_input *in = &ui_ctx->input;
|
|
||||||
struct nk_command_buffer *canvas = nk_window_get_canvas(ui_ctx);
|
|
||||||
struct node *updated = 0;
|
|
||||||
|
|
||||||
node_editor_init(editor);
|
|
||||||
|
|
||||||
{
|
|
||||||
/* allocate complete window space */
|
|
||||||
total_space = nk_window_get_content_region(ui_ctx);
|
|
||||||
nk_layout_space_begin(ui_ctx, NK_STATIC, total_space.h, editor->node_count);
|
|
||||||
{
|
|
||||||
struct node *it = editor->begin;
|
|
||||||
struct nk_rect size = nk_layout_space_bounds(ui_ctx);
|
|
||||||
struct nk_panel *nodePanel = 0;
|
|
||||||
|
|
||||||
//nk_fill_rect(canvas, size, 0/*rounding*/, ((struct nk_color){30,30,30,255})); // 20,30,40,255
|
|
||||||
nk_fill_rect_multi_color(canvas, size, GRID_BG_COLORS);
|
|
||||||
|
|
||||||
if (editor->show_grid) {
|
|
||||||
/* display grid */
|
|
||||||
for (float x = (float)fmod(size.x - editor->scrolling.x, GRID_SIZE); x < size.w; x += GRID_SIZE)
|
|
||||||
nk_stroke_line(canvas, x+size.x, size.y, x+size.x, size.y+size.h, GRID_THICKNESS, GRID_COLOR);
|
|
||||||
for (float y = (float)fmod(size.y - editor->scrolling.y, GRID_SIZE); y < size.h; y += GRID_SIZE)
|
|
||||||
nk_stroke_line(canvas, size.x, y+size.y, size.x+size.w, y+size.y, GRID_THICKNESS, GRID_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* execute each node as a movable group */
|
|
||||||
/* loop through nodes */
|
|
||||||
while (it) {
|
|
||||||
/* Output node window should not have a close button */
|
|
||||||
nk_flags nodePanel_flags = NK_WINDOW_MOVABLE|NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER|NK_WINDOW_TITLE;
|
|
||||||
if (it != editor->output_node)
|
|
||||||
nodePanel_flags |= NK_WINDOW_CLOSABLE;
|
|
||||||
|
|
||||||
/* calculate scrolled node window position and size */
|
|
||||||
nk_layout_space_push(ui_ctx, nk_rect(it->bounds.x - editor->scrolling.x,
|
|
||||||
it->bounds.y - editor->scrolling.y, it->bounds.w, it->bounds.h));
|
|
||||||
|
|
||||||
/* execute node window */
|
|
||||||
char *name = va(" " ICON_MD_MENU " %s",it->name); //< @r-lyeh added some spacing+icon because of our UI customizations
|
|
||||||
|
|
||||||
struct nk_color bak = ui_ctx->style.window.fixed_background.data.color;
|
|
||||||
ui_ctx->style.window.fixed_background.data.color = BG_COLOR;
|
|
||||||
|
|
||||||
if (nk_group_begin(ui_ctx, name, nodePanel_flags))
|
|
||||||
{
|
|
||||||
/* always have last selected node on top */
|
|
||||||
|
|
||||||
nodePanel = nk_window_get_panel(ui_ctx);
|
|
||||||
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nodePanel->bounds) &&
|
|
||||||
(!(it->prev && nk_input_mouse_clicked(in, NK_BUTTON_LEFT,
|
|
||||||
nk_layout_space_rect_to_screen(ui_ctx, nodePanel->bounds)))) &&
|
|
||||||
editor->end != it)
|
|
||||||
{
|
|
||||||
updated = it;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((nodePanel->flags & NK_WINDOW_HIDDEN)) /* Node close button has been clicked */
|
|
||||||
{
|
|
||||||
/* Delete node */
|
|
||||||
struct node_link *link_remove;
|
|
||||||
node_editor_pop(editor, it);
|
|
||||||
for (int n = 0; n < it->input_count; n++) {
|
|
||||||
if ((link_remove = node_editor_find_link_by_output(editor, it, n)))
|
|
||||||
{
|
|
||||||
node_editor_delete_link(link_remove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int n = 0; n < it -> output_count; n++) {
|
|
||||||
while((link_remove = node_editor_find_link_by_input(editor, it, n)))
|
|
||||||
{
|
|
||||||
node_editor_delete_link(link_remove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NK_ASSERT(editor->node_buf[it->ID] == it);
|
|
||||||
editor->node_buf[it->ID] = NULL;
|
|
||||||
FREE(it->inputs);
|
|
||||||
FREE(it->outputs);
|
|
||||||
FREE(it);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
/* ================= NODE CONTENT ===================== */
|
|
||||||
|
|
||||||
it->display_func(ui_ctx, it);
|
|
||||||
|
|
||||||
/* ==================================================== */
|
|
||||||
|
|
||||||
}
|
|
||||||
nk_group_end(ui_ctx);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ui_ctx->style.window.fixed_background.data.color = bak;
|
|
||||||
|
|
||||||
if (!(nodePanel->flags & NK_WINDOW_HIDDEN))
|
|
||||||
{
|
|
||||||
/* node pin and linking */
|
|
||||||
struct nk_rect bounds;
|
|
||||||
bounds = nk_layout_space_rect_to_local(ui_ctx, nodePanel->bounds);
|
|
||||||
bounds.x += editor->scrolling.x;
|
|
||||||
bounds.y += editor->scrolling.y;
|
|
||||||
it->bounds = bounds;
|
|
||||||
|
|
||||||
/* output pins */
|
|
||||||
for (int n = 0; n < it->output_count; ++n) {
|
|
||||||
struct nk_rect circle;
|
|
||||||
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
|
|
||||||
pt.x += nodePanel->bounds.w - PIN_RADIUS / 2 + it->pin_spacing.out_padding_x;
|
|
||||||
pt.y += it->pin_spacing.out_padding_y + it->pin_spacing.out_spacing_y * (n);
|
|
||||||
PIN_DRAW(it->outputs[n],pt,PIN_RADIUS);
|
|
||||||
|
|
||||||
/* start linking process */
|
|
||||||
/* set linking active */
|
|
||||||
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true)) {
|
|
||||||
editor->linking.active = nk_true;
|
|
||||||
editor->linking.node = it;
|
|
||||||
editor->linking.input_id = it->ID;
|
|
||||||
editor->linking.input_pin = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* draw link being dragged (from linked pin to mouse position) */
|
|
||||||
if (editor->linking.active && editor->linking.node == it &&
|
|
||||||
editor->linking.input_pin == n) {
|
|
||||||
LINK_DRAW(vec2(circle.x+3,circle.y+3),ptr2(&in->mouse.pos.x),COLOR_FLOW_HI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* input pins */
|
|
||||||
for (int n = 0; n < it->input_count; ++n) {
|
|
||||||
struct nk_rect circle;
|
|
||||||
struct nk_vec2 pt = {nodePanel->bounds.x, nodePanel->bounds.y};
|
|
||||||
pt.x += it->pin_spacing.in_padding_x;
|
|
||||||
pt.y += it->pin_spacing.in_padding_y + it->pin_spacing.in_spacing_y * (n);
|
|
||||||
PIN_DRAW(it->inputs[n],pt,PIN_RADIUS);
|
|
||||||
|
|
||||||
/* Detach link */
|
|
||||||
if (nk_input_has_mouse_click_down_in_rect(in, NK_BUTTON_LEFT, circle, nk_true) &&
|
|
||||||
editor->linking.active == nk_false &&
|
|
||||||
it->inputs[n].is_connected == nk_true) {
|
|
||||||
struct node_link *node_relink = node_editor_find_link_by_output(editor, it, n);
|
|
||||||
editor->linking.active = nk_true;
|
|
||||||
editor->linking.node = node_relink->input_node;
|
|
||||||
editor->linking.input_id = node_relink->input_node->ID;
|
|
||||||
editor->linking.input_pin = node_relink->input_pin;
|
|
||||||
node_editor_delete_link(node_relink);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create link */
|
|
||||||
if (nk_input_is_mouse_released(in, NK_BUTTON_LEFT) &&
|
|
||||||
nk_input_is_mouse_hovering_rect(in, circle) &&
|
|
||||||
editor->linking.active &&
|
|
||||||
editor->linking.node != it &&
|
|
||||||
it->inputs[n].pin_type == editor->linking.node->outputs[editor->linking.input_pin].pin_type &&
|
|
||||||
it->inputs[n].is_connected != nk_true) {
|
|
||||||
editor->linking.active = nk_false;
|
|
||||||
|
|
||||||
node_editor_link(editor, editor->linking.node,
|
|
||||||
editor->linking.input_pin, it, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset (output) linking connection */
|
|
||||||
if (editor->linking.active && (!!input(KEY_LCTRL) || !!input(KEY_RCTRL) || nk_input_is_mouse_released(in, NK_BUTTON_LEFT))) {
|
|
||||||
editor->linking.active = nk_false;
|
|
||||||
editor->linking.node = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* draw each static link */
|
|
||||||
for (int n = 0; n < editor->link_count; ++n) {
|
|
||||||
struct node_link *link = &editor->links[n];
|
|
||||||
if (link->is_active == nk_true){
|
|
||||||
struct node *ni = link->input_node;
|
|
||||||
struct node *no = link->output_node;
|
|
||||||
struct nk_vec2 l0 = nk_layout_space_to_screen(ui_ctx, nk_vec2(ni->bounds.x + ni->bounds.w + ni->pin_spacing.out_padding_x, 3.0f + ni->bounds.y + ni->pin_spacing.out_padding_y + ni->pin_spacing.out_spacing_y * (link->input_pin)));
|
|
||||||
struct nk_vec2 l1 = nk_layout_space_to_screen(ui_ctx, nk_vec2(no->bounds.x + no->pin_spacing.in_padding_x, 3.0f + no->bounds.y + no->pin_spacing.in_padding_y + no->pin_spacing.in_spacing_y * (link->output_pin)));
|
|
||||||
|
|
||||||
l0.x -= editor->scrolling.x;
|
|
||||||
l0.y -= editor->scrolling.y;
|
|
||||||
l1.x -= editor->scrolling.x;
|
|
||||||
l1.y -= editor->scrolling.y;
|
|
||||||
|
|
||||||
struct nk_color color = node_get_type_color(no->inputs[link->output_pin].pin_type);
|
|
||||||
LINK_DRAW(ptr2(&l0.x), ptr2(&l1.x), color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
/* reshuffle nodes to have least recently selected node on top */
|
|
||||||
node_editor_pop(editor, updated);
|
|
||||||
node_editor_push(editor, updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* node selection */
|
|
||||||
if (nk_input_mouse_clicked(in, NK_BUTTON_LEFT, nk_layout_space_bounds(ui_ctx))) {
|
|
||||||
it = editor->begin;
|
|
||||||
editor->selected = NULL;
|
|
||||||
editor->bounds = nk_rect(in->mouse.pos.x, in->mouse.pos.y, 100, 200);
|
|
||||||
while (it) {
|
|
||||||
struct nk_rect b = nk_layout_space_rect_to_screen(ui_ctx, it->bounds);
|
|
||||||
b.x -= editor->scrolling.x;
|
|
||||||
b.y -= editor->scrolling.y;
|
|
||||||
if (nk_input_is_mouse_hovering_rect(in, b))
|
|
||||||
editor->selected = it;
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* contextual menu */
|
|
||||||
if (nk_contextual_begin(ui_ctx, 0, nk_vec2(150, 220), nk_window_get_bounds(ui_ctx))) {
|
|
||||||
struct nk_vec2 wincoords = { in->mouse.pos.x-total_space.x-50, in->mouse.pos.y-total_space.y-32 };
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
static char *filter = 0;
|
|
||||||
static int do_filter = 0;
|
|
||||||
if( input_down(KEY_F) ) if( input(KEY_LCTRL) || input(KEY_RCTRL) ) do_filter ^= 1;
|
|
||||||
int choice = ui_toolbar(ICON_MD_SEARCH ";");
|
|
||||||
if( choice == 1 ) do_filter = 1;
|
|
||||||
if( do_filter ) {
|
|
||||||
ui_string(ICON_MD_CLOSE " 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
|
|
||||||
|
|
||||||
#define ui_label_filtered(lbl) (strmatchi(lbl,filter_mask) && ui_label(lbl))
|
|
||||||
|
|
||||||
int close = 0;
|
|
||||||
if (ui_label_filtered("=Add Color node")) close=1,node_color_create(editor, wincoords);
|
|
||||||
if (ui_label_filtered("=Add Float node")) close=1,node_float_create(editor, wincoords);
|
|
||||||
if (ui_label_filtered("=Add Blend Node")) close=1,node_blend_create(editor, wincoords);
|
|
||||||
if (ui_label_filtered(editor->show_grid ? "=Hide Grid" : "=Show Grid"))
|
|
||||||
close=1,editor->show_grid = !editor->show_grid;
|
|
||||||
if(close) do_filter = 0, (filter ? filter[0] = '\0' : '\0'), nk_contextual_close(ui_ctx);
|
|
||||||
nk_contextual_end(ui_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nk_layout_space_end(ui_ctx);
|
|
||||||
|
|
||||||
/* window content scrolling */
|
|
||||||
if (nk_input_is_mouse_hovering_rect(in, nk_window_get_bounds(ui_ctx)) &&
|
|
||||||
nk_input_is_mouse_down(in, NK_BUTTON_MIDDLE)) {
|
|
||||||
editor->scrolling.x += in->mouse.delta.x;
|
|
||||||
editor->scrolling.y += in->mouse.delta.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !nk_window_is_closed(ui_ctx, "NodeEdit");
|
|
||||||
}
|
|
||||||
|
|
||||||
int editor_nodes(int window_mode) {
|
|
||||||
window_mode = EDITOR_WINDOW; // force window
|
|
||||||
|
|
||||||
if( editor_begin(NODES_TITLE, window_mode) ) {
|
|
||||||
|
|
||||||
static struct node_editor nodeEditor = {0};
|
|
||||||
node_editor(&nodeEditor);
|
|
||||||
|
|
||||||
editor_end(window_mode);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AUTORUN {
|
|
||||||
array_push(editor.subeditors, editor_nodes);
|
|
||||||
}
|
|
|
@ -25,7 +25,7 @@ pushd tools\editor
|
||||||
set "filename=%%~nf"
|
set "filename=%%~nf"
|
||||||
set "newname=v4k!filename:fwk=!%%~xf"
|
set "newname=v4k!filename:fwk=!%%~xf"
|
||||||
echo Renaming "%%f" to "!newname!"
|
echo Renaming "%%f" to "!newname!"
|
||||||
copy "%%f" "!newname!"
|
move "%%f" "!newname!"
|
||||||
)
|
)
|
||||||
echo All done.
|
echo All done.
|
||||||
endlocal
|
endlocal
|
||||||
|
|
Loading…
Reference in New Issue