diff options
| -rw-r--r-- | app.c | 2 | ||||
| -rw-r--r-- | editor.c | 600 |
2 files changed, 381 insertions, 221 deletions
@@ -11,8 +11,6 @@ #include "raygui.h" App app = {0}; -u64 dude_id = ENTITY_INVALID_IDX; -u64 car_id = ENTITY_INVALID_IDX; static void app_init(void) { @@ -155,15 +155,15 @@ action_collect(EditorAction *action, u64 id, bool recursive) { } static EditorAction -action_begin(EditorActionTag tag) { +action_begin(EditorActionTag tag, bool roots_only) { EditorAction action = { .tag = tag }; - bool roots_only = false; + // bool roots_only = false; bool recursive = false; switch (tag) { case ED_ACTION_TRANSFORM: - roots_only = true; + // roots_only = true; break; case ED_ACTION_CREATE: @@ -182,7 +182,7 @@ action_begin(EditorActionTag tag) { return action; case ED_ACTION_DELETE: - roots_only = true; + // roots_only = true; recursive = true; break; @@ -227,12 +227,14 @@ action_begin(EditorActionTag tag) { entity_id_from_pid(action.item[i].pid)); assert(ent); EditorActionData *before = &action.item[i].before; + switch (tag) { case ED_ACTION_TRANSFORM: before->transform.pos = ent->tr.pos; before->transform.rot = ent->tr.rot; before->transform.scale = ent->tr.scale; break; + case ED_ACTION_DELETE: { Entity *parent = entity_get(ent->tr.parent); Entity *prev = entity_get(ent->tr.prev); @@ -245,9 +247,11 @@ action_begin(EditorActionTag tag) { before->stored.next = next ? next->pid : 0; break; } + case ED_ACTION_RENAME: // Name ownership will transfer when the rename is submitted. break; + default: TraceLog(LOG_FATAL, "action_begin: unhandled action tag."); @@ -520,7 +524,7 @@ case APP_MODE_EDIT_NONE: if (IsKeyPressed(KEY_X)) { if (!ctrl && ed.sel_cnt) { - EditorAction action = action_begin(ED_ACTION_DELETE); + EditorAction action = action_begin(ED_ACTION_DELETE, true); if (action.item_cnt) { action_apply(&action, false); action_commit(&action); @@ -783,7 +787,7 @@ editor_move(void) { static Vec3 mv_start = {0}; if (!tr_action.item) { - tr_action = action_begin(ED_ACTION_TRANSFORM); + tr_action = action_begin(ED_ACTION_TRANSFORM, true); started = false; } @@ -853,7 +857,7 @@ editor_move(void) { void editor_rotate(void) { if (!tr_action.item) { - tr_action = action_begin(ED_ACTION_TRANSFORM); + tr_action = action_begin(ED_ACTION_TRANSFORM, true); started = false; } @@ -896,10 +900,12 @@ editor_rotate(void) { } /* draw */ -static void -panel_vec3(Rect bounds, Vec3 *val, cstr label) { - -} +// static bool +// panel_vec3(Rect bounds, Vec3 *val, cstr label) { + // for (int i = 0; i < 3; ++i) { + // } + // return true; +// } static void statusline(void) { @@ -1102,7 +1108,7 @@ panel_scene(EditorPanelContainer container, bool input) { if (GuiButton( (Rect){ bounds.x + 8, bounds.y + 30, btn_w, 26 }, "Create Entity")) { - EditorAction action = action_begin(ED_ACTION_CREATE); + EditorAction action = action_begin(ED_ACTION_CREATE, false); action_commit(&action); } GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); @@ -1151,6 +1157,278 @@ panel_scene(EditorPanelContainer container, bool input) { } } +typedef struct { + int focus; + int next_id; + bool input; + char text[64]; +} PanelEdit; + +// mixed means “show a blank field and start editing with an empty buffer”, +// currently used for when there's multiple entities selected. +static bool +panel_field(PanelEdit *edit, Rect box, cstr text, bool mixed, bool numeric) { + int id = edit->next_id++; + bool editing = edit->focus == id; + + char display[sizeof(edit->text)]; + snprintf(display, sizeof(display), "%s", text); + + bool toggle; + if (numeric) { + f32 draft = 0; + toggle = GuiValueBoxFloat(box, NULL, + editing ? edit->text : display, &draft, + edit->input && editing); + } else { + toggle = GuiTextBox(box, editing ? edit->text : display, + sizeof(edit->text), edit->input && editing); + } + + if (!edit->input) { + return false; + } + + if (editing && IsKeyPressed(KEY_ESCAPE)) { + edit->focus = 0; + return false; + } + + if (!toggle) { + return false; + } + + if (!editing) { + memset(edit->text, 0, sizeof(edit->text)); + snprintf(edit->text, sizeof(edit->text), "%s", + mixed ? "" : display); + edit->focus = id; + return false; + } + + edit->focus = 0; + return IsKeyPressed(KEY_ENTER); +} + +static bool +panel_float(PanelEdit *edit, Rect box, f32 *value, bool mixed) { + char display[64] = ""; + if (!mixed) { + snprintf(display, sizeof(display), "%.3f", *value); + } + + if (!panel_field(edit, box, display, mixed, true)) { + return false; + } + + char *end; + f32 parsed = strtof(edit->text, &end); + if (end == edit->text || *end != '\0') { + return false; + } + + *value = parsed; + return true; +} + +/* Returns the submitted axis, or -1 when nothing was submitted. */ +static int +panel_vec3(PanelEdit *edit, Rect row, cstr label, Vec3 *value, bool mixed) { + GuiLabel((Rect){ row.x, row.y, 45, row.height }, label); + + f32 *axis[3] = { &value->x, &value->y, &value->z }; + f32 width = (row.width - 45) / 3; + int submitted = -1; + + for (int i = 0; i < 3; ++i) { + Rect box = { + row.x + 45 + i * width, + row.y, + width - 4, + row.height + }; + + if (panel_float(edit, box, axis[i], mixed)) { + submitted = i; + } + } + + return submitted; +} + +static void +panel_properties(EditorPanelContainer container, bool input) { + static PanelEdit edit; + + Rect bounds = ed.panel[container].bounds; + int state = GuiGetState(); + f32 pad = 8; + + input = input && app.mode == APP_MODE_EDIT_NONE + && state != STATE_DISABLED; + + edit.next_id = 1; + edit.input = input; + + if (!input) { + edit.focus = 0; + GuiSetState(STATE_DISABLED); + } + + GuiPanel(bounds, "Entity Properties"); + + if (!ed.sel_cnt) { + edit.focus = 0; + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(bounds, "no entities selected"); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + goto reset; + } + + Entity *ent = NULL; + bool mixed = ed.sel_cnt > 1; + + if (!mixed) { + for (u32 i = 0; i < entity_count(); ++i) { + Entity *candidate = entity_get(entity_id_at(i)); + if (candidate && candidate->pid && + candidate->ed.selected) { + ent = candidate; + break; + } + } + } + + /* Name */ + cstr name = mixed ? "<multiple entities selected>" + : (ent && ent->name ? ent->name : ""); + + f32 label_w = editor_label_w("Name"); + Rect name_box = { + bounds.x + pad, + bounds.y + 30, + label_w, + 20 + }; + + GuiLabel(name_box, "Name"); + name_box.x += label_w + pad; + name_box.width = bounds.x + bounds.width - name_box.x - pad; + + if (panel_field(&edit, name_box, name, mixed, false)) { + editor_rename_selected(edit.text); + } + + /* Transform */ + Rect group = { + bounds.x + pad, + bounds.y + 60, + bounds.width - 2 * pad, + 90 + 2 * pad + }; + + GuiGroupBox(group, "Transform"); + + Rect row = { + group.x + pad, + group.y + 10, + group.width - 2 * pad, + 20 + }; + + Vec3 pos = ent ? ent->tr.pos : (Vec3){0}; + int axis = panel_vec3(&edit, row, "pos", &pos, mixed); + + if (axis >= 0) { + EditorAction action = + action_begin(ED_ACTION_TRANSFORM, false); + + f32 value[3] = { pos.x, pos.y, pos.z }; + for (u32 j = 0; j < action.item_cnt; ++j) { + Entity *target = entity_get( + entity_id_from_pid(action.item[j].pid) + ); + + f32 *component[3] = { + &target->tr.pos.x, + &target->tr.pos.y, + &target->tr.pos.z, + }; + *component[axis] = value[axis]; + } + + action_commit(&action); + } + + row.y += 30; + Vec3 rot = ent ? QuaternionToEuler(ent->tr.rot) : (Vec3) {0}; + rot.x *= RAD2DEG; + rot.y *= RAD2DEG; + rot.z *= RAD2DEG; + axis = panel_vec3(&edit, row, "rot", &rot, mixed); + + if (axis >= 0) { + EditorAction action = + action_begin(ED_ACTION_TRANSFORM, false); + + f32 value[3] = { + rot.x * DEG2RAD, + rot.y * DEG2RAD, + rot.z * DEG2RAD + }; + + for (u32 j = 0; j < action.item_cnt; ++j) { + Entity *target = entity_get( + entity_id_from_pid(action.item[j].pid) + ); + + Vec3 rt = QuaternionToEuler(target->tr.rot); + // rt.x *= RAD2DEG; + // rt.y *= RAD2DEG; + // rt.z *= RAD2DEG; + + f32 *component[3] = { &rt.x, &rt.y, &rt.z }; + *component[axis] = value[axis]; + + target->tr.rot = QuaternionFromEuler(rt.x, rt.y, rt.z); + } + + action_commit(&action); + } + + row.y += 30; + Vec3 scale = ent ? ent->tr.scale : (Vec3){0}; + axis = panel_vec3(&edit, row, "scale", &scale, mixed); + + if (axis >= 0) { + EditorAction action = + action_begin(ED_ACTION_TRANSFORM, false); + + f32 value[3] = { scale.x, scale.y, scale.z }; + for (u32 j = 0; j < action.item_cnt; ++j) { + Entity *target = entity_get( + entity_id_from_pid(action.item[j].pid) + ); + + f32 *component[3] = { + &target->tr.scale.x, + &target->tr.scale.y, + &target->tr.scale.z, + }; + + *component[axis] = value[axis]; + } + + action_commit(&action); + } + + reset: + GuiSetState(state); + if (input && container != ED_PNL_CENTER) { + ed.focus_captured = edit.focus != 0; + } +} + // static void // panel_properties(EditorPanelContainer container, bool input) { // Rect view = {0}; @@ -1172,6 +1450,19 @@ panel_scene(EditorPanelContainer container, bool input) { // GuiPanel(bounds, "Entity Properties"); +// /* +// NOTE (tech kaczynski - 21/9/6): +// I had a choice here to either create a tagged union of components and +// iterate over them, or to write a giant function like this. I chose the +// latter because the former would require a big refactor that I don't +// really care for at this stage. The former still requires a loop and a +// switch or branches for each type, which requires extra indentation or +// abstraction into special helper functions etc. For now, the code +// duplication and verbosity I can live with and might not be much worse +// than the alternative anyway, plus I get to avoid refactoring every +// system that touches entity component data. +// */ + // /* needed for input field focus */ // enum { // TEXT_FIELD_NONE, @@ -1182,9 +1473,11 @@ panel_scene(EditorPanelContainer container, bool input) { // TEXT_FIELD_TR_POS_X, // TEXT_FIELD_TR_POS_Y, // TEXT_FIELD_TR_POS_Z, + // TEXT_FIELD_TR_ROT_X, // TEXT_FIELD_TR_ROT_Y, // TEXT_FIELD_TR_ROT_Z, + // TEXT_FIELD_TR_SCALE_X, // TEXT_FIELD_TR_SCALE_Y, // TEXT_FIELD_TR_SCALE_Z, @@ -1208,11 +1501,12 @@ panel_scene(EditorPanelContainer container, bool input) { // } // /* ed.sel_cnt can't be 0 because we've already checked and returned */ +// Entity *ent = NULL; // only set if sel_cnt == 1 // if (ed.sel_cnt > 1) { // substr = "<multiple entities selected>"; // } else { // for (u32 i = 0; i < entity_count(); ++i) { -// Entity *ent = entity_get(entity_id_at(i)); +// ent = entity_get(entity_id_at(i)); // if (ent && ent->pid && ent->ed.selected) { // substr = ent->name ? ent->name : ""; // break; @@ -1266,233 +1560,101 @@ panel_scene(EditorPanelContainer container, bool input) { // } // } -// /* -// NOTE (tech kaczynski - 21/9/6): -// I had a choice here to either create a tagged union of components and -// iterate over them, or to write a giant function like this. I chose the -// latter because the former would require a big refactor that I don't -// really care for at this stage. The former still requires a loop and a -// switch or branches for each type, which requires extra indentation or -// abstraction into special helper functions etc. For now, the code -// duplication and verbosity I can live with and might not be much worse -// than the alternative anyway, plus I get to avoid refactoring every -// system that touches entity component data. -// */ - // /* transform comp */ -// { // prec.x = bounds.x + pad; // prec.y += 30; // prec.width = bounds.width - pad * 2; // prec.height = 90 + pad * 2; // GuiGroupBox(prec, "Transform"); -// /* pos */ -// prec.height = 20; -// prec.y += 15; -// prec.x += pad; -// prec.width -= pad; -// GuiLabel(prec, "Pos"); -// // panel_vec3(prec, ent->pos, cstr label) - -// /* rot */ -// static Vec3 rot; // euler angle representation of entity rot -// prec.y += 30; -// GuiLabel(prec, "Rot"); -// { - +// Vec3 pos = ent ? ent->tr.pos : (Vec3){0}; +// f32 val[3] = { pos.x, pos.y, pos.z }; +// Rect row = { prec.x + pad, prec.y + 10, prec.width - 2 * pad, 20 }; + +// /* position */ +// GuiLabel((Rect){ row.x, row.y, 45, row.height }, "pos"); + +// for (int i = 0; i < 3; ++i) { +// int field = TEXT_FIELD_TR_POS_X + i; +// Rect box = { +// row.x + 45 + i * ((row.width - 45) / 3), +// row.y, +// (row.width - 45) / 3 - 4, +// row.height +// }; + +// char display[64] = ""; +// if (ent) { +// snprintf(display, sizeof(display), "%.3f", val[i]); // } -// /* scale */ -// prec.y += 30; -// GuiLabel(prec, "Scale"); - -// // GuiTextBox(prec, char *text, int textSize, false); -// } - -// /* reset */ -// reset: -// GuiSetState(state); -// if (input && container != ED_PNL_CENTER) { -// ed.focus_captured = text_focus; -// } -// } - -static void -panel_properties(EditorPanelContainer container, bool input) { - Rect view = {0}; - Rect bounds = ed.panel[container].bounds; - Vec2 *scroll = &ed.panel[container].scroll; - - int state = GuiGetState(); +// bool editing = text_focus == field; +// f32 draft = 0; +// bool toggle = GuiValueBoxFloat(box, NULL, +// editing ? text_input : display, +// &draft, input && editing); - input = input && app.mode == - APP_MODE_EDIT_NONE && state != STATE_DISABLED; - - if (!input) { - GuiSetState(STATE_DISABLED); - } - - if (input && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - ed.focus_captured = true; - } - - GuiPanel(bounds, "Entity Properties"); - - /* needed for input field focus */ - enum { - TEXT_FIELD_NONE, - - TEXT_FIELD_NAME, - - /* transform */ - TEXT_FIELD_TR_POS_X, - TEXT_FIELD_TR_POS_Y, - TEXT_FIELD_TR_POS_Z, - TEXT_FIELD_TR_ROT_X, - TEXT_FIELD_TR_ROT_Y, - TEXT_FIELD_TR_ROT_Z, - TEXT_FIELD_TR_SCALE_X, - TEXT_FIELD_TR_SCALE_Y, - TEXT_FIELD_TR_SCALE_Z, - }; - - static int text_focus; - static char text_input[64]; - char display_text[sizeof(text_input)]; +// if (!input) { +// continue; +// } - Rect prec = bounds; - f32 pad = 8; - cstr substr = ""; +// if (editing && IsKeyPressed(KEY_ESCAPE)) { +// text_focus = TEXT_FIELD_NONE; +// continue; +// } - if (!ed.sel_cnt) { - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - cstr str = "no entities selected"; - GuiLabel(bounds, str); - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - text_focus = 0; - goto reset; - } +// if (!toggle) { +// continue; +// } - /* ed.sel_cnt can't be 0 because we've already checked and returned */ - Entity *ent = NULL; // only set if sel_cnt == 1 - if (ed.sel_cnt > 1) { - substr = "<multiple entities selected>"; - } else { - for (u32 i = 0; i < entity_count(); ++i) { - ent = entity_get(entity_id_at(i)); - if (ent && ent->pid && ent->ed.selected) { - substr = ent->name ? ent->name : ""; - break; - } - } - } +// /* clicked an inactive field, begin editing */ +// if (!editing) { +// memset(text_input, 0, sizeof(text_input)); +// snprintf(text_input, sizeof(text_input),"%s", display); +// text_focus = field; +// continue; +// } - if (text_focus != TEXT_FIELD_NAME) { - snprintf(display_text, sizeof(display_text), "%s", substr); - } +// /* editing ended. clicking away cancels, enter submits */ +// text_focus = TEXT_FIELD_NONE; +// if (!IsKeyPressed(KEY_ENTER)) { +// continue; +// } - cstr name_lbl = "Name"; - u32 name_lbl_w = editor_label_w(name_lbl); - prec.x += pad; - prec.y += 30; - prec.width = name_lbl_w; - prec.height = 20; - GuiLabel(prec, name_lbl); +// char *end; +// f32 value = strtof(text_input, &end); +// if (end == text_input || *end != '\0') { +// continue; +// } - prec.x += name_lbl_w + pad; - prec.width = bounds.x + bounds.width - prec.x - pad; +// EditorAction action = action_begin(ED_ACTION_TRANSFORM, false); - bool name_edit = text_focus == TEXT_FIELD_NAME; - bool name_toggle = - GuiTextBox( - prec, - name_edit ? text_input : display_text, - sizeof(text_input), - input && name_edit - ); +// for (u32 j = 0; j < action.item_cnt; ++j) { +// u64 id = entity_id_from_pid(action.item[j].pid); +// Entity *target = entity_get(id); - if (input) { - if (IsKeyPressed(KEY_ESCAPE)) { - if (name_edit) { - text_focus = 0; - } - } else if (name_toggle) { - if (name_edit) { - if (IsKeyPressed(KEY_ENTER)) { - editor_rename_selected(text_input); - } - text_focus = 0; - } else { - snprintf(text_input, - sizeof(text_input), - "%s", - ed.sel_cnt > 1 ? - "" : display_text); - text_focus = TEXT_FIELD_NAME; - } - } - } +// // f32 *tval = (f32*)&target->tr.pos; +// // *(tval + i) = value; - /* - NOTE (tech kaczynski - 21/9/6): - I had a choice here to either create a tagged union of components and - iterate over them, or to write a giant function like this. I chose the - latter because the former would require a big refactor that I don't - really care for at this stage. The former still requires a loop and a - switch or branches for each type, which requires extra indentation or - abstraction into special helper functions etc. For now, the code - duplication and verbosity I can live with and might not be much worse - than the alternative anyway, plus I get to avoid refactoring every - system that touches entity component data. - */ - - /* transform comp */ - { - prec.x = bounds.x + pad; - prec.y += 30; - prec.width = bounds.width - pad * 2; - prec.height = 90 + pad * 2; - GuiGroupBox(prec, "Transform"); - - static Vec3 pos, rot, scale; - // pos = panel_vec3(prec, pos, "pos"); - // rot = panel_vec3(prec, rot, "rot"); - // scale = panel_vec3(prec, scale, "scale"); - - // /* pos */ - // prec.height = 20; - // prec.y += 15; - // prec.x += pad; - // prec.width -= pad; - // GuiLabel(prec, "Pos"); - // // panel_vec3(prec, ent->pos, cstr label) - - // /* rot */ - // static Vec3 rot; // euler angle representation of entity rot - // prec.y += 30; - // GuiLabel(prec, "Rot"); - // if (ent) { - // rot = QuaternionToEuler(ent->tr.rot); - // } - // for (int i = 0; i < 3; ++i) { +// f32 *axis[3] = { +// &target->tr.pos.x, +// &target->tr.pos.y, +// &target->tr.pos.z, +// }; - // } - - // /* scale */ - // prec.y += 30; - // GuiLabel(prec, "Scale"); +// *axis[i] = value; +// } - // GuiTextBox(prec, char *text, int textSize, false); - } +// action_commit(&action); +// } - /* reset */ - reset: - GuiSetState(state); - if (input && container != ED_PNL_CENTER) { - ed.focus_captured = text_focus; - } -} +// /* reset */ +// reset: +// GuiSetState(state); +// if (input && container != ED_PNL_CENTER) { +// ed.focus_captured = text_focus; +// } +// } static void panel_space(EditorPanelContainer container) { @@ -1723,7 +1885,7 @@ editor_label_w(cstr text) { void editor_rename_selected(cstr name) { - EditorAction action = action_begin(ED_ACTION_RENAME); + EditorAction action = action_begin(ED_ACTION_RENAME, false); for (u32 i = 0; i < action.item_cnt; ++i) { u64 id = entity_id_from_pid(action.item[i].pid); |
