techkaczynski.dev / code

summaryrefslogtreecommitdiff
path: root/editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor.c')
-rw-r--r--editor.c296
1 files changed, 292 insertions, 4 deletions
diff --git a/editor.c b/editor.c
index ab70611..c31c9a5 100644
--- a/editor.c
+++ b/editor.c
@@ -1,11 +1,12 @@
#include "editor.h"
+#include "raygui.h"
#include "save.h"
#include "cam.h"
#include "physics.h"
#include "ent.h"
#include "app.h"
#include "res.h"
-#include "raygui.h"
+#include "env.h"
static bool started = false;
static Vec2 start_point = {0};
@@ -62,6 +63,18 @@ static struct {
char query[128];
} asset_picker;
+typedef enum {
+ ED_CMD_NONE = -1,
+ ED_CMD_TIME,
+ ED_CMD_SPAWN_PREFAB,
+ ED_CMD_CNT,
+} EditorCommand;
+
+static struct {
+ char query[128];
+ int selected;
+} command_menu;
+
static const cstr material_shader_keys[] = {
"model",
"model_cutoff"
@@ -240,6 +253,19 @@ action_begin(EditorActionTag tag, bool roots_only) {
case ED_ACTION_PHYSICS:
break;
+ case ED_ACTION_TIME:
+ action.item_cnt = 1;
+ action.item = calloc(1, sizeof(*action.item));
+ if (!action.item) {
+ perror("action_begin: time action alloc failed.");
+ exit(1);
+ }
+
+ /* Scene settings: this item's pid is unused. */
+ action.item[0].before.time.day_cur = env.day_cur;
+ action.item[0].before.time.day_len = env.day_len;
+ return action;
+
default:
TraceLog(LOG_FATAL, "action_begin: unsupported tag.");
}
@@ -402,6 +428,16 @@ action_apply(EditorAction *action, bool undo) {
}
break;
+ case ED_ACTION_TIME: {
+ const EditorActionData *data = undo ?
+ &action->item[0].before :
+ &action->item[0].after;
+
+ env.day_cur = data->time.day_cur;
+ env.day_len = data->time.day_len;
+ break;
+ }
+
default:
TraceLog(LOG_FATAL, "action with invalid tag.");
break;
@@ -440,6 +476,9 @@ action_free(EditorAction *action) {
}
break;
+ case ED_ACTION_TIME:
+ break;
+
default:
TraceLog(LOG_FATAL, "action_free: unhandled action tag.");
break;
@@ -544,6 +583,19 @@ action_commit(EditorAction *action) {
changed = action->item_cnt != 0;
break;
+ case ED_ACTION_TIME: {
+ const EditorActionData *before = &action->item[0].before;
+ EditorActionData *after = &action->item[0].after;
+
+ after->time.day_cur = env.day_cur;
+ after->time.day_len = env.day_len;
+
+ changed =
+ before->time.day_cur != after->time.day_cur ||
+ before->time.day_len != after->time.day_len;
+ break;
+ }
+
default:
TraceLog(LOG_FATAL, "action_commit: unhangled action tag.");
break;
@@ -576,6 +628,8 @@ editor_init(void) {
save_file_read("res/save/scene.fgs") ?
ed.history_csr : UINT32_MAX;
+ ed.time_preview = env.day_cur;
+
ed.grid_sz = 1.0f;
ed.rotation_snap = 2; // 15 degrees
@@ -765,7 +819,15 @@ editor_update(void) {
bool shift = IsKeyDown(KEY_LEFT_SHIFT);
bool alt = IsKeyDown(KEY_LEFT_ALT);
- /* sorry about the indentation */
+ if (ed.time_preview_playing) {
+ ed.time_preview = fmodf(
+ ed.time_preview + GetFrameTime() / env.day_len,
+ 1.0f);
+ }
+
+ env.cycle = ed.time_preview;
+
+ /* sorry about the indentation :^) */
switch (ed.interact) {
case ED_INTERACT_NONE:
if (!ed.focus_captured && IsKeyPressed(KEY_SPACE)) {
@@ -855,6 +917,8 @@ case ED_INTERACT_NONE:
ed.history_save_csr =
save_file_read("res/save/scene.fgs") ?
ed.history_csr : UINT32_MAX;
+
+ ed.time_preview = env.day_cur;
}
}
@@ -4060,6 +4124,197 @@ panel_physics_properties(EditorPanelContainer container, bool input) {
}
static void
+panel_time(EditorPanelContainer container, bool input) {
+ static PanelEdit edit = {0};
+
+ Rect bounds = ed.panel[container].bounds;
+ edit.next_id = 1;
+ edit.input = input;
+
+ /* Escape cancels numeric entry before closing the panel. */
+ if (input && IsKeyPressed(KEY_ESCAPE)) {
+ if (edit.focus != 0) {
+ edit.focus = 0;
+ return;
+ }
+
+ ed.panel[container].enabled = false;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ return;
+ }
+
+ if (input && edit.focus == 0 &&
+ IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_Z)) {
+ if (IsKeyDown(KEY_LEFT_SHIFT)) {
+ history_redo();
+ } else {
+ history_undo();
+ }
+ }
+
+ GuiPanel(bounds, "Time of Day");
+
+ Rect row = {
+ bounds.x + 16, bounds.y + 40,
+ bounds.width - 32, 24
+ };
+
+ int minutes = (int)(ed.time_preview * 24.0f * 60.0f);
+ char label[64];
+ snprintf(label, sizeof(label), "Preview time: %02i:%02i",
+ minutes / 60, minutes % 60);
+ GuiLabel(row, label);
+
+ row.y += 32;
+ f32 hours = ed.time_preview * 24.0f;
+ if (GuiSlider(row, NULL, NULL, &hours, 0.0f, 24.0f)) {
+ ed.time_preview = fmodf(hours / 24.0f, 1.0f);
+ }
+
+ row.y += 40;
+ if (GuiButton(row, ed.time_preview_playing ?
+ "Pause preview" : "Play preview")) {
+ edit.focus = 0;
+ ed.time_preview_playing = !ed.time_preview_playing;
+ }
+
+ row.y += 40;
+ minutes = (int)(env.day_cur * 24.0f * 60.0f);
+ snprintf(label, sizeof(label), "Scene starting time: %02i:%02i",
+ minutes / 60, minutes % 60);
+ GuiLabel(row, label);
+
+ row.y += 28;
+ if (GuiButton(row, "Use preview as scene starting time")) {
+ edit.focus = 0;
+ EditorAction action = action_begin(ED_ACTION_TIME, false);
+ env.day_cur = ed.time_preview;
+ action_commit(&action);
+ }
+
+ row.y += 40;
+ GuiLabel(row, "Day length (real seconds)");
+
+ row.y += 28;
+ f32 day_len = env.day_len;
+ if (panel_float(&edit, row, &day_len, false)) {
+ if (isfinite(day_len) && day_len > 0.0f) {
+ EditorAction action =
+ action_begin(ED_ACTION_TIME, false);
+ env.day_len = day_len;
+ action_commit(&action);
+ }
+ }
+}
+
+static EditorCommand
+panel_commands(Rect bounds, bool input) {
+ static const cstr names[ED_CMD_CNT] = {
+ [ED_CMD_TIME] = "Time of day",
+ [ED_CMD_SPAWN_PREFAB] = "Spawn prefab",
+ };
+
+ GuiPanel(bounds, "Command Menu");
+
+ Rect search = {
+ bounds.x + 8, bounds.y + 30,
+ bounds.width - 16, 24
+ };
+
+ char previous[sizeof(command_menu.query)];
+ memcpy(previous, command_menu.query, sizeof(previous));
+
+ /* Keep keyboard focus in the search field. */
+ GuiTextBox(search, command_menu.query,
+ sizeof(command_menu.query), input);
+
+ if (strcmp(previous, command_menu.query) != 0) {
+ command_menu.selected = 0;
+ }
+
+ struct CommandMatch {
+ EditorCommand command;
+ int score;
+ } matches[ED_CMD_CNT];
+
+ int count = 0;
+ for (int i = 0; i < ED_CMD_CNT; ++i) {
+ int score = fuzzy_match(command_menu.query, names[i]);
+ if (score == INT32_MIN) {
+ continue;
+ }
+
+ int index = count;
+ while (index > 0 && matches[index - 1].score < score) {
+ matches[index] = matches[index - 1];
+ index--;
+ }
+
+ matches[index] = (struct CommandMatch){i, score};
+ count++;
+ }
+
+ Rect row = {
+ search.x, search.y + 32,
+ search.width, 24
+ };
+
+ if (!count) {
+ GuiLabel(row, "No matching commands");
+ return ED_CMD_NONE;
+ }
+
+ if (input) {
+ 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;
+ }
+
+ command_menu.selected =
+ (command_menu.selected + step + count) % count;
+ }
+
+ EditorCommand chosen = ED_CMD_NONE;
+
+ for (int i = 0; i < count; ++i) {
+ bool hovered = input &&
+ CheckCollisionPointRec(GetMousePosition(), row);
+
+ if (hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ command_menu.selected = i;
+ chosen = matches[i].command;
+ }
+
+ if (i == command_menu.selected) {
+ DrawRectangleRec(row,
+ GetColor(GuiGetStyle(LISTVIEW,
+ BASE_COLOR_PRESSED)));
+ }
+
+ GuiLabel(row, names[matches[i].command]);
+ row.y += row.height;
+ }
+
+ if (input && IsKeyPressed(KEY_ENTER)) {
+ chosen = matches[command_menu.selected].command;
+ }
+
+ GuiLabel((Rect){
+ search.x, bounds.y + bounds.height - 30,
+ search.width, 22
+ }, "Up/Down/Tab: select Enter: run Esc: close");
+
+ return chosen;
+}
+
+static void
panel_draw(EditorPanelContainer container) {
if (!ed.panel[container].enabled) {
return;
@@ -4105,6 +4360,12 @@ panel_draw(EditorPanelContainer container) {
exit = true;
}
+ if (IsKeyPressed(KEY_SLASH)) {
+ memset(&command_menu, 0, sizeof(command_menu));
+ ed.panel[container].type = ED_PNL_TYPE_CMD;
+ break;
+ }
+
if (exit) {
ed.panel[container].enabled = false;
ed.focus = ED_PNL_NULL;
@@ -4113,8 +4374,35 @@ panel_draw(EditorPanelContainer container) {
panel_space(container);
break;
- case ED_PNL_TYPE_CMD:
- GuiPanel(bounds, "Command Menu");
+ case ED_PNL_TYPE_CMD: {
+ if (input && IsKeyPressed(KEY_ESCAPE)) {
+ ed.panel[container].enabled = false;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ break;
+ }
+
+ EditorCommand command = panel_commands(bounds, input);
+
+ switch (command) {
+ case ED_CMD_TIME:
+ ed.panel[container].type = ED_PNL_TYPE_TIME;
+ break;
+
+ case ED_CMD_SPAWN_PREFAB:
+ ed.panel[container].enabled = false;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ case ED_PNL_TYPE_TIME:
+ panel_time(container, input);
break;
case ED_PNL_TYPE_ASSET_PICKER: {