diff options
| -rw-r--r-- | editor/konedit.c | 365 |
1 files changed, 346 insertions, 19 deletions
diff --git a/editor/konedit.c b/editor/konedit.c index ff4020b..181c598 100644 --- a/editor/konedit.c +++ b/editor/konedit.c @@ -17,14 +17,17 @@ #include "konui.h" #include "kontoml.h" #include "konfs.h" +#include "konpack.h" +#include "stb_image.h" -#if !defined(KON_MODULE_UI) || !defined(KON_MODULE_FONT) || !defined(KON_MODULE_TOML) || !defined(KON_MODULE_FS) +#if !defined(KON_MODULE_UI) || !defined(KON_MODULE_FONT) || !defined(KON_MODULE_TOML) || !defined(KON_MODULE_FS) || !defined(KON_MODULE_PACKW) || !defined(KON_MODULE_IMAGE) #error "build with: ./build.sh -d konedit" #endif #define PATH 512 #define SIDE_WIDTH 260 -#define TABS 5 +#define TABS 6 +#define PACK_SHOWN 300 /*** colors ***/ @@ -52,9 +55,21 @@ typedef struct project { int dirty; char dir[PATH]; char name[64]; - char assets[64]; + char assets[128]; + char pack[128]; + char key[128]; } project_t; +/* what the Assets tab shows of the file picked on the left, kept so it is not read again every frame */ +typedef struct preview { + char path[PATH]; + long long size; + kon_image *image; + int width, height; + char text[1600]; + int isText; +} preview_t; + typedef enum dialog { DIALOG_NONE, DIALOG_OPEN, @@ -71,6 +86,10 @@ typedef struct state { kon_ui_t *ui; project_t project; browser_t side, pick; + preview_t preview; + kon_fsList_t packList; + char packInfo[200], packResult[200]; + int packTreeOpen; kon_uiArea_t sideArea, mainArea, statusArea, dialogTop, dialogList; dialog_t dialog; char pathInput[PATH]; @@ -81,11 +100,12 @@ typedef struct state { char status[200]; } state_t; -static const char *tabNames[TABS] = { "Project", "Assets", "Tilemap", "Script", "Level" }; +static const char *tabNames[TABS] = { "Project", "Assets", "Pack", "Tilemap", "Script", "Level" }; static const char *tabAbout[TABS] = { "", "", + "", "the tilemap editor: paint tiles on layers, pick tiles from a sheet, save the map the engine loads", "the dialogue and script editor: write konscript with a text box preview next to it", "the sector level editor: draw rooms in 2d, see them in 3d, save a kmap" @@ -146,6 +166,29 @@ static const char *kindOf(const char *name) return "file"; } +static int isTextKind(const char *kind) +{ + return strcmp(kind, "text") == 0 || strcmp(kind, "settings") == 0 || strcmp(kind, "sector map") == 0; +} + +/* a path from project.toml is from the project folder, unless it starts at a root */ +static void projectPath(const project_t *p, const char *rel, char *out, size_t size) +{ + if (kon_fsIsAbsolute(rel)) copy(out, size, rel); + else kon_fsJoin(out, size, p->dir, rel); +} + +static long long fileSize(const char *path) +{ + FILE *f = fopen(path, "rb"); + long long size = -1; + + if (!f) return -1; + if (fseek(f, 0, SEEK_END) == 0) size = (long long)ftell(f); + fclose(f); + return size; +} + /*** browser ***/ static int browserGo(browser_t *b, const char *path) @@ -213,6 +256,74 @@ static int browserDraw(state_t *state, browser_t *b) return result; } +/*** preview ***/ + +static void previewFree(preview_t *v) +{ + if (v->image) kon_freeImage(v->image); + memset(v, 0, sizeof(*v)); +} + +static void previewLoad(preview_t *v, const char *path, const char *kind, long long size) +{ + previewFree(v); + copy(v->path, sizeof(v->path), path); + v->size = size; + + if (strcmp(kind, "image") == 0) { + int w, h, n; + unsigned char *pixels = size < (64LL << 20) ? stbi_load(path, &w, &h, &n, 4) : NULL; + + if (pixels) { + v->image = kon_loadImage(pixels, w, h, konFormatRGBA8); + v->width = w; + v->height = h; + stbi_image_free(pixels); + } + } else if (isTextKind(kind)) { + FILE *f = fopen(path, "rb"); + + if (f) { + size_t got = fread(v->text, 1, sizeof(v->text) - 1, f), i, out = 0; + + fclose(f); + for (i = 0; i < got; i++) { + unsigned char c = (unsigned char)v->text[i]; + + if (c == '\r') continue; + v->text[out++] = (char)(c == '\n' || (c >= 32 && c < 127) ? c : (c == '\t' ? ' ' : '.')); + } + v->text[out] = 0; + v->isText = 1; + } + } +} + +/* the size a picture is shown at: small ones are blown up whole times, big ones shrink to fit */ +static void previewSize(int w, int h, int maxW, int maxH, int *outW, int *outH) +{ + int k; + + if (maxW < 16) maxW = 16; + if (w > maxW || h > maxH) { + if ((long long)w * maxH > (long long)h * maxW) { + *outW = maxW; + *outH = (int)((long long)h * maxW / w); + } else { + *outH = maxH; + *outW = (int)((long long)w * maxH / h); + } + if (*outW < 1) *outW = 1; + if (*outH < 1) *outH = 1; + return; + } + k = maxW / w < maxH / h ? maxW / w : maxH / h; + if (k > 8) k = 8; + if (k < 1) k = 1; + *outW = w * k; + *outH = h * k; +} + /*** project ***/ static int projectLoad(project_t *p, const char *dir, char *error, size_t errorSize) @@ -235,6 +346,8 @@ static int projectLoad(project_t *p, const char *dir, char *error, size_t errorS copy(p->dir, sizeof(p->dir), dir); copy(p->name, sizeof(p->name), kon_tomlGetString(toml, "project", "name", kon_fsBaseName(dir))); copy(p->assets, sizeof(p->assets), kon_tomlGetString(toml, "project", "assets", "assets")); + copy(p->pack, sizeof(p->pack), kon_tomlGetString(toml, "pack", "output", "data.kpk")); + copy(p->key, sizeof(p->key), kon_tomlGetString(toml, "pack", "key", "")); kon_freeToml(toml); return 1; } @@ -259,6 +372,10 @@ static int projectSave(project_t *p) writeString(f, p->name); fputs("\nassets = ", f); writeString(f, p->assets); + fputs("\n\n[pack]\noutput = ", f); + writeString(f, p->pack); + fputs("\nkey = ", f); + writeString(f, p->key); fputc('\n', f); if (fclose(f) != 0) return 0; p->dirty = 0; @@ -279,6 +396,7 @@ static int projectCreate(project_t *p, const char *dir, const char *name, char * copy(p->dir, sizeof(p->dir), dir); copy(p->name, sizeof(p->name), name[0] ? name : kon_fsBaseName(dir)); copy(p->assets, sizeof(p->assets), "assets"); + copy(p->pack, sizeof(p->pack), "data.kpk"); kon_fsJoin(assets, sizeof(assets), dir, p->assets); if (!kon_fsMakeDir(assets) || !projectSave(p)) { snprintf(error, errorSize, "cannot write in %s", tail(dir, 60)); @@ -288,6 +406,127 @@ static int projectCreate(project_t *p, const char *dir, const char *name, char * return 1; } +/*** pack ***/ + +static void scanPack(state_t *state) +{ + project_t *p = &state->project; + char assets[PATH], out[PATH]; + long long total = 0, onDisk; + size_t i; + + kon_fsFree(&state->packList); + state->packInfo[0] = 0; + if (!p->open) return; + + projectPath(p, p->assets, assets, sizeof(assets)); + if (!kon_fsWalk(assets, &state->packList)) { + snprintf(state->packInfo, sizeof(state->packInfo), "no folder %s", tail(assets, 60)); + return; + } + for (i = 0; i < state->packList.count; i++) total += state->packList.items[i].size; + + projectPath(p, p->pack, out, sizeof(out)); + onDisk = fileSize(out); + if (onDisk >= 0) { + snprintf(state->packInfo, sizeof(state->packInfo), "%d files, %lld bytes in the assets folder. the pack on disk is %lld bytes", + (int)state->packList.count, total, onDisk); + } else { + snprintf(state->packInfo, sizeof(state->packInfo), "%d files, %lld bytes in the assets folder. no pack built yet", + (int)state->packList.count, total); + } +} + +/* 1 for a good key, 0 when it is not 32 bytes, -1 when it cannot be opened */ +static int readKey(const char *path, uint8_t key[KON_PACK_KEY_SIZE]) +{ + FILE *f = fopen(path, "rb"); + size_t got; + uint8_t extra; + int ok; + + if (!f) return -1; + got = fread(key, 1, KON_PACK_KEY_SIZE, f); + ok = got == KON_PACK_KEY_SIZE && fread(&extra, 1, 1, f) == 0; + fclose(f); + return ok; +} + +static void buildPack(state_t *state) +{ + project_t *p = &state->project; + char assets[PATH], out[PATH], keyPath[PATH]; + uint8_t key[KON_PACK_KEY_SIZE]; + const uint8_t *useKey = NULL; + char **paths; + int i, n = 0, count, failed = 0; + kon_pack_t *check; + + state->packResult[0] = 0; + scanPack(state); + count = (int)state->packList.count; + if (count == 0) { + snprintf(state->packResult, sizeof(state->packResult), "nothing to pack"); + return; + } + projectPath(p, p->assets, assets, sizeof(assets)); + projectPath(p, p->pack, out, sizeof(out)); + if (p->key[0]) { + int keyState; + + projectPath(p, p->key, keyPath, sizeof(keyPath)); + keyState = readKey(keyPath, key); + if (keyState < 0) { + snprintf(state->packResult, sizeof(state->packResult), "cannot read the key file %s", tail(keyPath, 40)); + return; + } + if (keyState == 0) { + snprintf(state->packResult, sizeof(state->packResult), "the key file has to be exactly %d bytes: %s", KON_PACK_KEY_SIZE, tail(keyPath, 40)); + return; + } + useKey = key; + } + + paths = calloc((size_t)count, sizeof(char *)); + if (!paths) { + snprintf(state->packResult, sizeof(state->packResult), "out of memory"); + return; + } + for (i = 0; i < count; i++) { + char full[PATH]; + + kon_fsJoin(full, sizeof(full), assets, state->packList.items[i].name); + if (strcmp(full, out) == 0) continue; + paths[n] = malloc(strlen(full) + 1); + if (!paths[n]) { + failed = 1; + break; + } + strcpy(paths[n], full); + n++; + } + + if (failed) { + snprintf(state->packResult, sizeof(state->packResult), "out of memory"); + } else if (n == 0) { + snprintf(state->packResult, sizeof(state->packResult), "nothing to pack"); + } else if (kon_packWrite(out, useKey, assets, (const char *const *)paths, n) != 0) { + snprintf(state->packResult, sizeof(state->packResult), "%s", kon_packError()); + } else if ((check = kon_packOpen(out, useKey)) == NULL) { + snprintf(state->packResult, sizeof(state->packResult), "built it but cannot read it back: %s", kon_packError()); + } else { + snprintf(state->packResult, sizeof(state->packResult), "built %s: %d files, %lld bytes%s", kon_fsBaseName(out), kon_packCount(check), + fileSize(out), useKey ? ", encrypted" : ""); + kon_packClose(check); + } + for (i = 0; i < n; i++) free(paths[i]); + free(paths); + scanPack(state); + setStatus(state, "%s", state->packResult); +} + +/*** project actions ***/ + static void projectShow(state_t *state) { copy(state->side.root, sizeof(state->side.root), state->project.dir); @@ -296,6 +535,9 @@ static void projectShow(state_t *state) state->mainArea.scroll = 0; state->tab = 0; state->dialog = DIALOG_NONE; + state->packResult[0] = 0; + previewFree(&state->preview); + scanPack(state); } static void openProject(state_t *state, const char *dir) @@ -339,6 +581,10 @@ static void closeProject(state_t *state) setStatus(state, "closed %s", state->project.name); memset(&state->project, 0, sizeof(state->project)); browserFree(&state->side); + previewFree(&state->preview); + kon_fsFree(&state->packList); + state->packInfo[0] = 0; + state->packResult[0] = 0; state->sideArea.scroll = 0; } @@ -372,7 +618,10 @@ static void drawMenus(kon_program_t *program, state_t *state) } kon_uiEndMenu(ui); if (kon_uiBeginMenu(ui, "View")) { - if (kon_uiMenuItem(ui, "Refresh files", "F5") && state->project.open) browserGo(&state->side, state->side.path); + if (kon_uiMenuItem(ui, "Refresh files", "F5") && state->project.open) { + browserGo(&state->side, state->side.path); + scanPack(state); + } } kon_uiEndMenu(ui); kon_uiEndMenuBar(ui); @@ -393,21 +642,24 @@ static void drawSide(state_t *state) kon_uiEndArea(ui); } +/* an input for a text of the project that marks it as unsaved when it changes */ +static void projectInput(state_t *state, const char *title, const char *id, char *value, size_t size) +{ + char before[128]; + + kon_uiLabel(state->ui, title); + copy(before, sizeof(before), value); + kon_uiTextInput(state->ui, id, value, size); + if (strcmp(before, value) != 0) state->project.dirty = 1; +} + static void drawProjectTab(state_t *state) { kon_ui_t *ui = state->ui; project_t *p = &state->project; - char before[64]; - - kon_uiLabel(ui, "name"); - copy(before, sizeof(before), p->name); - kon_uiTextInput(ui, "project name##name", p->name, sizeof(p->name)); - if (strcmp(before, p->name) != 0) p->dirty = 1; - kon_uiLabel(ui, "assets folder"); - copy(before, sizeof(before), p->assets); - kon_uiTextInput(ui, "assets folder##assets", p->assets, sizeof(p->assets)); - if (strcmp(before, p->assets) != 0) p->dirty = 1; + projectInput(state, "name", "project name##name", p->name, sizeof(p->name)); + projectInput(state, "assets folder", "assets folder##assets", p->assets, sizeof(p->assets)); kon_uiSeparator(ui); kon_uiLabel(ui, tail(p->dir, 60)); @@ -423,7 +675,10 @@ static void drawAssetsTab(state_t *state) { kon_ui_t *ui = state->ui; browser_t *b = &state->side; + preview_t *v = &state->preview; const kon_fsEntry_t *found = NULL; + const char *kind; + char path[PATH]; size_t i; if (b->selected[0]) { @@ -433,13 +688,79 @@ static void drawAssetsTab(state_t *state) } if (!found) { kon_uiLabel(ui, "pick a file on the left"); - kon_uiLabel(ui, "the asset browser and the pack tool come here"); return; } + kind = kindOf(found->name); + kon_fsJoin(path, sizeof(path), b->path, found->name); + if (strcmp(v->path, path) != 0 || v->size != found->size) previewLoad(v, path, kind, found->size); + kon_uiLabel(ui, found->name); - kon_uiLabelf(ui, "kind: %s", kindOf(found->name)); - kon_uiLabelf(ui, "size: %lld bytes", found->size); + kon_uiLabelf(ui, "%s, %lld bytes", kind, found->size); kon_uiLabel(ui, tail(b->path, 60)); + kon_uiSeparator(ui); + + if (strcmp(kind, "image") == 0) { + if (v->image) { + int w, h; + + previewSize(v->width, v->height, state->mainArea.w - 60, 320, &w, &h); + kon_uiLabelf(ui, "%d x %d", v->width, v->height); + kon_uiImage(ui, v->image, w, h); + } else { + kon_uiLabel(ui, "cannot read this image"); + } + } else if (v->isText) { + const char *at = v->text; + int lines = 0; + + while (*at && lines < 24) { + char line[120]; + size_t n = strcspn(at, "\n"); + + if (n > sizeof(line) - 1) n = sizeof(line) - 1; + memcpy(line, at, n); + line[n] = 0; + kon_uiLabel(ui, line[0] ? line : " "); + at += strcspn(at, "\n"); + if (*at == '\n') at++; + lines++; + } + if (*at) kon_uiLabel(ui, "..."); + } +} + +static void drawPackTab(state_t *state) +{ + kon_ui_t *ui = state->ui; + project_t *p = &state->project; + size_t i, shown; + + projectInput(state, "pack file", "pack file##pack", p->pack, sizeof(p->pack)); + projectInput(state, "key file, 32 bytes, or empty for no encryption", "key file##key", p->key, sizeof(p->key)); + + kon_uiSeparator(ui); + kon_uiNextWidth(ui, 110); + if (kon_uiButton(ui, "Scan")) scanPack(state); + kon_uiSameLine(ui); + kon_uiNextWidth(ui, 110); + if (kon_uiButton(ui, "Build Pack")) buildPack(state); + if (p->dirty) { + kon_uiSameLine(ui); + kon_uiLabel(ui, "unsaved"); + } + + if (state->packInfo[0]) kon_uiLabel(ui, state->packInfo); + if (state->packResult[0]) kon_uiLabel(ui, state->packResult); + + kon_uiSeparator(ui); + kon_uiTreeNode(ui, "what goes in the pack", &state->packTreeOpen); + if (!state->packTreeOpen) return; + + kon_uiIndent(ui, 8); + shown = state->packList.count < PACK_SHOWN ? state->packList.count : PACK_SHOWN; + for (i = 0; i < shown; i++) kon_uiLabelf(ui, "%s (%lld)", state->packList.items[i].name, state->packList.items[i].size); + if (shown < state->packList.count) kon_uiLabelf(ui, "and %d more", (int)(state->packList.count - shown)); + kon_uiIndent(ui, -8); } static void drawMain(state_t *state) @@ -455,6 +776,7 @@ static void drawMain(state_t *state) kon_uiSeparator(ui); if (state->tab == 0) drawProjectTab(state); else if (state->tab == 1) drawAssetsTab(state); + else if (state->tab == 2) drawPackTab(state); else kon_uiLabel(ui, tabAbout[state->tab]); } kon_uiEndArea(ui); @@ -542,7 +864,10 @@ static int event(kon_program_t *program, const kon_event_t *ev, void *user) if (ev->key == KON_KEY_LEFT_CTRL || ev->key == KON_KEY_RIGHT_CTRL) state->ctrl = down; if (down && ev->key == KON_KEY_S && state->ctrl) saveProject(state); if (down && ev->key == KON_KEY_ESCAPE && !kon_uiWantsKeyboard(state->ui)) state->dialog = DIALOG_NONE; - if (down && ev->key == KON_KEY_F5 && state->project.open) browserGo(&state->side, state->side.path); + if (down && ev->key == KON_KEY_F5 && state->project.open) { + browserGo(&state->side, state->side.path); + scanPack(state); + } } return kon_uiWantsMouse(state->ui) || kon_uiWantsKeyboard(state->ui); } @@ -622,6 +947,8 @@ static void shutdown(kon_program_t *program, void *user) (void)program; browserFree(&state->side); browserFree(&state->pick); + previewFree(&state->preview); + kon_fsFree(&state->packList); kon_uiDestroy(state->ui); } |
