From 2790624094ab0b81dfdc3150eaf0da08accf610e Mon Sep 17 00:00:00 2001 From: tech kaczynski Date: Thu, 1 Oct 2026 05:30:26 +1000 Subject: added model picker panel. work on property panel components. --- editor.c | 790 +++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 547 insertions(+), 243 deletions(-) (limited to 'editor.c') diff --git a/editor.c b/editor.c index 90ab378..cf74bae 100644 --- a/editor.c +++ b/editor.c @@ -29,10 +29,18 @@ EditorInfo ed = { }, }; +typedef enum { + MDL_PICK_COMP_ADD, + MDL_PICK_COMP_UPDATE, + MDL_PICK_ENT_CREATE, +} ModelPickerMode; + /* forward decl */ static bool selection_root(Entity *ent); static void action_apply(EditorAction*, bool); static void action_free(EditorAction*); +static void model_picker_open(ModelPickerMode mode); +static void model_picker_close(void); static void history_push(EditorAction action) { @@ -182,11 +190,11 @@ action_begin(EditorActionTag tag, bool roots_only) { return action; case ED_ACTION_DELETE: - // roots_only = true; recursive = true; break; case ED_ACTION_RENAME: + case ED_ACTION_MODEL: break; default: @@ -252,6 +260,12 @@ action_begin(EditorActionTag tag, bool roots_only) { // Name ownership will transfer when the rename is submitted. break; + case ED_ACTION_MODEL: + before->model = ent->mdl; + before->model.key = ent->mdl.key ? + strdup(ent->mdl.key) : NULL; + break; + default: TraceLog(LOG_FATAL, "action_begin: unhandled action tag."); @@ -309,6 +323,17 @@ action_apply(EditorAction *action, bool undo) { } break; + case ED_ACTION_MODEL: + for (u32 i = 0; i < action->item_cnt; ++i) { + u64 id = entity_id_from_pid(action->item[i].pid); + Entity *ent = entity_get(id); + + ModelCmp model = ent->mdl; + ent->mdl = action->item[i].before.model; + action->item[i].before.model = model; + } + break; + default: TraceLog(LOG_FATAL, "action with invalid tag."); break; @@ -334,6 +359,12 @@ action_free(EditorAction *action) { } break; + case ED_ACTION_MODEL: + for (u32 i = 0; i < action->item_cnt; ++i) { + free(action->item[i].before.model.key); + } + break; + default: TraceLog(LOG_FATAL, "action_free: unhandled action tag."); break; @@ -393,6 +424,23 @@ action_commit(EditorAction *action) { } break; + case ED_ACTION_MODEL: + for (u32 i = 0; i < action->item_cnt; ++i) { + u64 id = entity_id_from_pid(action->item[i].pid); + Entity *ent = entity_get(id); + assert(ent); + + ModelCmp *before = &action->item[i].before.model; + ModelCmp *after = &ent->mdl; + + changed |= before->has != after->has || + before->draw != after->draw || + strcmp(before->key ? before->key : "", + after->key ? after->key : "") != 0; + + } + break; + default: TraceLog(LOG_FATAL, "action_commit: unhangled action tag."); break; @@ -426,6 +474,7 @@ editor_init(void) { void editor_exit(void) { + model_picker_close(); action_free(&tr_action); tr_action = (EditorAction){0}; history_clear(); @@ -900,13 +949,6 @@ editor_rotate(void) { } /* draw */ -// static bool -// panel_vec3(Rect bounds, Vec3 *val, cstr label) { - // for (int i = 0; i < 3; ++i) { - // } - // return true; -// } - static void statusline(void) { char mode_str[16] = {0}; @@ -1200,8 +1242,8 @@ panel_field(PanelEdit *edit, Rect box, cstr text, bool mixed, bool numeric) { if (!editing) { memset(edit->text, 0, sizeof(edit->text)); - snprintf(edit->text, sizeof(edit->text), "%s", - mixed ? "" : display); + // snprintf(edit->text, sizeof(edit->text), "%s", + // mixed ? "" : display); edit->focus = id; return false; } @@ -1210,6 +1252,24 @@ panel_field(PanelEdit *edit, Rect box, cstr text, bool mixed, bool numeric) { return IsKeyPressed(KEY_ENTER); } +static bool +panel_bool(Rect row, cstr label, bool *value, bool mixed) { + GuiLabel((Rect){row.x, row.y, 40, row.height}, label); + + if (mixed) { + *value = false; + GuiLabel((Rect){ + row.x + 68, row.y, row.width - 68, row.height, + }, "(mixed)"); + } + + return GuiCheckBox((Rect){ + row.x + 40, + row.y + (row.height - 20) * 0.5f, + 20, 20, + }, NULL, value); +} + static bool panel_float(PanelEdit *edit, Rect box, f32 *value, bool mixed) { char display[64] = ""; @@ -1256,9 +1316,12 @@ panel_vec3(PanelEdit *edit, Rect row, cstr label, Vec3 *value, bool mixed) { return submitted; } + static void panel_properties(EditorPanelContainer container, bool input) { static PanelEdit edit; + static bool add_open; + static int add_choice; Rect bounds = ed.panel[container].bounds; int state = GuiGetState(); @@ -1271,10 +1334,15 @@ panel_properties(EditorPanelContainer container, bool input) { edit.input = input; if (!input) { + add_open = false; edit.focus = 0; GuiSetState(STATE_DISABLED); } + if (add_open) { + GuiLock(); + } + GuiPanel(bounds, "Entity Properties"); if (!ed.sel_cnt) { @@ -1299,6 +1367,32 @@ panel_properties(EditorPanelContainer container, bool input) { } } + ModelCmp *model = NULL; + int model_cnt = 0; + bool key_mixed = false; + bool draw_mixed = false; + + for (u32 i = 0; i < entity_count(); ++i) { + Entity *candidate = entity_get(entity_id_at(i)); + + if (!candidate->pid || !candidate->ed.selected || + !candidate->mdl.has) { + continue; + } + + model_cnt++; + + if (!model) { + model = &candidate->mdl; + } else { + key_mixed |= strcmp(model->key, + candidate->mdl.key) != 0; + draw_mixed |= model->draw != candidate->mdl.draw; + } + } + + bool all_have_model = model_cnt == ed.sel_cnt; + /* Name */ cstr name = mixed ? "" : (ent && ent->name ? ent->name : ""); @@ -1331,7 +1425,7 @@ panel_properties(EditorPanelContainer container, bool input) { Rect row = { group.x + pad, - group.y + 10, + group.y + 15, group.width - 2 * pad, 20 }; @@ -1383,10 +1477,6 @@ panel_properties(EditorPanelContainer container, bool input) { ); 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]; @@ -1422,240 +1512,88 @@ panel_properties(EditorPanelContainer container, bool input) { action_commit(&action); } + model_comp: + if (!all_have_model) { + goto comp_add; + } + + group.y += group.height + 16; + group.height = 80; + GuiGroupBox(group, "Model"); + + row = (Rect){ + group.x + pad, + group.y + 15, + group.width - 2 * pad, + 20, + }; + + GuiLabel((Rect){row.x, row.y, 32, row.height}, "Key"); + + if (GuiButton( + (Rect){row.x + 40, row.y, row.width - 40, row.height}, + key_mixed ? "" : GetFileName(model->key))) { + edit.focus = 0; + model_picker_open(MDL_PICK_COMP_UPDATE); + } + + row.y += 30; + bool draw = draw_mixed ? false : model->draw; + + if (panel_bool(row, "Draw", &draw, draw_mixed)) { + edit.focus = 0; + EditorAction action = action_begin(ED_ACTION_MODEL, false); + + for (u32 i = 0; i < action.item_cnt; ++i) { + Entity *target = entity_get( + entity_id_from_pid(action.item[i].pid) + ); + + target->mdl.draw = draw; + } + + action_commit(&action); + } + + row.y = group.y + group.height - 20; + + comp_add: + GuiUnlock(); + row.y += 40; + + int add_state = GuiGetState(); + + if (all_have_model) { + add_open = false; + GuiSetState(STATE_DISABLED); + } + + cstr choices = model_cnt + ? "Add Component;Add Model to Missing" + : "Add Component;Model"; + + if (GuiDropdownBox(row, choices, &add_choice, add_open)) { + add_open = !add_open; + edit.focus = 0; + + if (!add_open && add_choice == 1) { + model_picker_open(MDL_PICK_COMP_ADD); + add_choice = 0; + } + } + + GuiSetState(add_state); + reset: + GuiUnlock(); GuiSetState(state); if (input && container != ED_PNL_CENTER) { - ed.focus_captured = edit.focus != 0; + ed.focus_captured = edit.focus != 0 || + add_open || + ed.panel[ED_PNL_CENTER].enabled; } } -// 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(); - -// 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"); - -// /* -// 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, - -// 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)]; - -// Rect prec = bounds; -// f32 pad = 8; -// cstr substr = ""; - -// 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; -// } - -// /* 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 = ""; -// } 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; -// } -// } -// } - -// if (text_focus != TEXT_FIELD_NAME) { -// snprintf(display_text, sizeof(display_text), "%s", substr); -// } - -// 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); - -// prec.x += name_lbl_w + pad; -// prec.width = bounds.x + bounds.width - prec.x - pad; - -// 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 -// ); - -// 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; -// } -// } -// } - -// /* transform comp */ -// prec.x = bounds.x + pad; -// prec.y += 30; -// prec.width = bounds.width - pad * 2; -// prec.height = 90 + pad * 2; -// GuiGroupBox(prec, "Transform"); - -// 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]); -// } - -// bool editing = text_focus == field; -// f32 draft = 0; -// bool toggle = GuiValueBoxFloat(box, NULL, -// editing ? text_input : display, -// &draft, input && editing); - -// if (!input) { -// continue; -// } - -// if (editing && IsKeyPressed(KEY_ESCAPE)) { -// text_focus = TEXT_FIELD_NONE; -// continue; -// } - -// if (!toggle) { -// continue; -// } - -// /* 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; -// } - -// /* editing ended. clicking away cancels, enter submits */ -// text_focus = TEXT_FIELD_NONE; -// if (!IsKeyPressed(KEY_ENTER)) { -// continue; -// } - -// char *end; -// f32 value = strtof(text_input, &end); -// if (end == text_input || *end != '\0') { -// continue; -// } - -// EditorAction action = action_begin(ED_ACTION_TRANSFORM, false); - -// for (u32 j = 0; j < action.item_cnt; ++j) { -// u64 id = entity_id_from_pid(action.item[j].pid); -// Entity *target = entity_get(id); - -// // f32 *tval = (f32*)&target->tr.pos; -// // *(tval + i) = value; - -// f32 *axis[3] = { -// &target->tr.pos.x, -// &target->tr.pos.y, -// &target->tr.pos.z, -// }; - -// *axis[i] = value; -// } - -// action_commit(&action); -// } - -// /* reset */ -// reset: -// GuiSetState(state); -// if (input && container != ED_PNL_CENTER) { -// ed.focus_captured = text_focus; -// } -// } - static void panel_space(EditorPanelContainer container) { static cstr strs[][2] = { @@ -1696,6 +1634,350 @@ panel_space(EditorPanelContainer container) { } } +/* model picker */ +typedef struct { + cstr key; + int score; + Asset *asset; + bool tried_loading; +} ModelChoice; + +static struct { + RenderTexture preview; + ModelChoice *items; + int item_cnt, match_cnt, sel_idx, scroll; + ModelPickerMode mode; + char query[128]; +} model_picker; + +static bool +editor_set_model(cstr key, ModelPickerMode mode) { + Asset *asset = asset_get(key); + if (!asset || asset->tag != ASSET_TAG_MODEL) { + return false; + } + + EditorAction action = action_begin(ED_ACTION_MODEL, false); + + for (u32 i = 0; i < action.item_cnt; ++i) { + u64 id = entity_id_from_pid(action.item[i].pid); + Entity *ent = entity_get(id); + + if ((mode == MDL_PICK_COMP_ADD && ent->mdl.has) || + (mode == MDL_PICK_COMP_UPDATE && !ent->mdl.has)) { + continue; + } + + free(ent->mdl.key); + ent->mdl = (ModelCmp){ + .key = strdup(key), + .mdl = &asset->mdl, + .has = true, + .draw = ent->mdl.has ? ent->mdl.draw : true, + }; + } + + action_commit(&action); + return true; +} + +/* qsort compare func */ +static int +model_choice_compare(const void *a, const void *b) { + const ModelChoice *left = a; + const ModelChoice *right = b; + + if (left->score > right->score) { + return -1; + } + + if (left->score < right->score) { + return 1; + } + + int order = strcmp(GetFileName(left->key), GetFileName(right->key)); + if (order != 0) { + return order; + } + + return strcmp(left->key, right->key); +} + +static void +model_picker_close(void) { + if (model_picker.preview.id) { + UnloadRenderTexture(model_picker.preview); + } + free(model_picker.items); + memset(&model_picker, 0, sizeof(model_picker)); + + ed.panel[ED_PNL_CENTER].enabled = false; + ed.focus = ED_PNL_NULL; + ed.focus_captured = false; +} + +static void +model_picker_open(ModelPickerMode mode) { + model_picker.mode = mode; + char **keys = asset_mdl_keys(&model_picker.item_cnt); + + if (model_picker.item_cnt) { + model_picker.items = calloc(model_picker.item_cnt, + sizeof(*model_picker.items)); + if (!model_picker.items) { + perror("model_picker_open: alloc failed."); + exit(1); + } + } + + for (int i = 0; i < model_picker.item_cnt; ++i) { + model_picker.items[i].key = keys[i]; + } + + free(keys); + model_picker.match_cnt = model_picker.item_cnt; + if (model_picker.item_cnt > 1) { + qsort(model_picker.items, model_picker.item_cnt, + sizeof(*model_picker.items), model_choice_compare); + } + + ed.panel[ED_PNL_CENTER].type = ED_PNL_TYPE_MODEL_PICKER; + ed.panel[ED_PNL_CENTER].enabled = true; + ed.focus = ED_PNL_CENTER; + ed.focus_captured = true; +} + +static void +model_picker_preview(Rect bounds, Model mdl) { + if (model_picker.preview.texture.width != bounds.width || + model_picker.preview.texture.height != bounds.height) { + if (model_picker.preview.texture.id) { + UnloadRenderTexture(model_picker.preview); + } + + model_picker.preview = + LoadRenderTexture(bounds.width, bounds.height); + } + + Matrix transform = mdl.transform; + mdl.transform = MatrixIdentity(); + BoundingBox bb = GetModelBoundingBox(mdl); + + Vec3 center = Vector3Scale(Vector3Add(bb.min, bb.max), 0.5); + center = Vector3Transform(center, transform); + + f32 radius = 0.001f; + for (int i = 0; i < 8; ++i) { + Vec3 corner = { + (i & 1) ? bb.max.x : bb.min.x, + (i & 2) ? bb.max.y : bb.min.y, + (i & 4) ? bb.max.z : bb.min.z + }; + corner = Vector3Transform(corner, transform); + radius = fmaxf(radius, Vector3Distance(corner, center)); + } + + transform = MatrixMultiply(transform, + MatrixTranslate(-center.x, -center.y, -center.z)); + transform = MatrixMultiply(transform, + MatrixScale(1.0/radius, 1.0/radius, 1.0/radius)); + + f32 dist = 3.0f * fmaxf(1.0f, (f32)bounds.height / (f32)bounds.width); + + Camera3D cam = { + .position = + Vector3Scale(Vector3Normalize((Vec3){1,0.7,1}), dist), + .target = {0}, + .up = {0,1,0}, + .fovy = 45, + .projection = CAMERA_PERSPECTIVE, + }; + + BeginTextureMode(model_picker.preview); + ClearBackground((Color){ 35, 35, 40, 255 }); + BeginMode3D(cam); + + /* we do this bc our normal draw calls override default shader */ + for (int i = 0; i < mdl.meshCount; ++i) { + Material mat = mdl.materials[mdl.meshMaterial[i]]; + mat.shader = (Shader) { + .id = rlGetShaderIdDefault(), + .locs = rlGetShaderLocsDefault(), + }; + DrawMesh(mdl.meshes[i], mat, transform); + } + + EndMode3D(); + EndTextureMode(); + + DrawTexturePro(model_picker.preview.texture, (Rect) { + 0, 0, bounds.width, -bounds.height, + }, bounds, (Vec2){0}, 0, WHITE); +} + + +static cstr +panel_model_picker(Rect bounds, bool input) { + if (input && IsKeyPressed(KEY_ESCAPE)) { + model_picker_close(); + return NULL; + } + + GuiPanel(bounds, "Choose Model"); + + f32 left_width = floorf((bounds.width - 24) * 0.45f); + + Rect search = { + bounds.x + 8, bounds.y + 30, + left_width, 24, + }; + + Rect list = { + search.x, search.y + 32, + left_width, fmaxf(24, bounds.height - 100), + }; + + Rect preview = { + search.x + left_width + 8, search.y, + bounds.width - left_width - 24, + bounds.height - 38, + }; + + char previous[sizeof(model_picker.query)]; + memcpy(previous, model_picker.query, sizeof(previous)); + + /* Ignore the toggle result: the search always keeps text focus. */ + GuiTextBox(search, model_picker.query, + sizeof(model_picker.query), input); + + if (strcmp(previous, model_picker.query) != 0) { + model_picker.match_cnt = 0; + + for (int i = 0; i < model_picker.item_cnt; ++i) { + ModelChoice *item = &model_picker.items[i]; + + item->score = + fuzzy_match(model_picker.query, + GetFileName(item->key)); + + if (item->score != INT32_MIN) { + model_picker.match_cnt++; + } + } + + if (model_picker.item_cnt > 1) { + qsort(model_picker.items, + model_picker.item_cnt, + sizeof(*model_picker.items), + model_choice_compare); + } + + model_picker.sel_idx = 0; + model_picker.scroll = 0; + } + + if (input && model_picker.match_cnt) { + int step = 0; + + if (IsKeyPressed(KEY_UP)) { + step = -1; + } else if (IsKeyPressed(KEY_DOWN)) { + step = 1; + } else if (IsKeyPressed(KEY_TAB)) { + bool backwards = IsKeyDown(KEY_LEFT_SHIFT) || + IsKeyDown(KEY_RIGHT_SHIFT); + step = backwards ? -1 : 1; + } + + model_picker.sel_idx = + (model_picker.sel_idx + step + + model_picker.match_cnt) % + model_picker.match_cnt; + } + + int visible = MAX(1, (int)(list.height / 24)); + + if (model_picker.sel_idx < model_picker.scroll) { + model_picker.scroll = model_picker.sel_idx; + } + + if (model_picker.sel_idx >= model_picker.scroll + visible) { + model_picker.scroll = model_picker.sel_idx - visible + 1; + } + + BeginScissorMode((int)list.x, (int)list.y, + (int)list.width, (int)list.height); + + for (int row = 0; row < visible; ++row) { + int index = model_picker.scroll + row; + + if (index >= model_picker.match_cnt) { + break; + } + + Rect item_box = { + list.x, list.y + row * 24, + list.width, 24, + }; + + if (index == model_picker.sel_idx) { + DrawRectangleRec(item_box, + GetColor(GuiGetStyle(LISTVIEW, + BASE_COLOR_PRESSED))); + } + + GuiLabel(item_box, GetFileName(model_picker.items[index].key)); + } + + if (!model_picker.match_cnt) { + GuiLabel((Rect) { + list.x, list.y, list.width, 24 + }, "No matching models"); + } + + EndScissorMode(); + + GuiPanel(preview, NULL); + + if (model_picker.match_cnt) { + ModelChoice *item = &model_picker.items[model_picker.sel_idx]; + + if (!item->tried_loading) { + item->asset = asset_get(item->key); + item->tried_loading = true; + } + + /* model preview */ + if (item->asset) { + Rect image = { + preview.x + 1, + preview.y + 34, + preview.width - 2, + preview.height - 35, + }; + model_picker_preview(image, item->asset->mdl); + } + + GuiLabel((Rect){ + preview.x + 8, preview.y + 8, + preview.width - 16, 24, + }, item->asset ? + GetFileName(item->key) : + "Could not load model"); + + if (input && item->asset && IsKeyPressed(KEY_ENTER)) { + return item->key; + } + } + + GuiLabel((Rect){ + list.x, bounds.y + bounds.height - 30, + list.width, 22, + }, "Tab: cycle Enter: add Esc: cancel"); + + return NULL; +} + static void panel_draw(EditorPanelContainer container) { if (!ed.panel[container].enabled) { @@ -1754,6 +2036,28 @@ panel_draw(EditorPanelContainer container) { case ED_PNL_TYPE_CMD: GuiPanel(bounds, "Command Menu"); break; + + case ED_PNL_TYPE_MODEL_PICKER: + {} + cstr key = panel_model_picker(bounds, input); + if (!key) { + break; + } + + switch (model_picker.mode) { + case MDL_PICK_COMP_ADD: + case MDL_PICK_COMP_UPDATE: + if (editor_set_model(key, model_picker.mode)) { + model_picker_close(); + } + break; + + case MDL_PICK_ENT_CREATE: + /* TODO: */ + break; + } + + break; } GuiSetState(state); -- cgit v1.2.3