From c25e30c69aceb70df4e709aacca25c593eea7046 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Mon, 8 Jun 2026 21:53:31 +0200 Subject: [PATCH] paint: add stages --- paint/sources/functions.h | 5 +- paint/sources/globals.h | 1 + paint/sources/io/import_legacy.c | 14 +++++ paint/sources/io/import_mesh.c | 2 + paint/sources/main.c | 2 +- paint/sources/types.h | 13 ++++ paint/sources/ui/tab_layers.c | 6 ++ paint/sources/ui/tab_meshes.c | 14 ++++- paint/sources/ui/tab_stages.c | 76 ----------------------- paint/sources/ui/tab_timeline.c | 102 ++++++++++++++++++++++++++----- paint/sources/ui/ui_base.c | 3 +- paint/sources/util/util_encode.c | 19 +++++- paint/sources/util/util_layer.c | 6 ++ paint/sources/util/util_stage.c | 76 +++++++++++++++++++++++ 14 files changed, 243 insertions(+), 96 deletions(-) delete mode 100644 paint/sources/ui/tab_stages.c create mode 100644 paint/sources/util/util_stage.c diff --git a/paint/sources/functions.h b/paint/sources/functions.h index e23dc7b2..0593a6d7 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -405,7 +405,10 @@ char *strings_failed_to_read_mesh_data(); char *strings_check_internet_connection(); char *strings_asset_already_imported(); char *strings_graphics_api(); -void tab_stages_draw(ui_handle_t *htab); +stage_t *tab_stages_create_stage(char *name); +stage_t *tab_stages_get_stage(); +void tab_stages_apply(stage_t *stage); +void tab_stages_prune(); void project_open(); void project_save(bool save_and_quit); void project_save_as(bool save_and_quit); diff --git a/paint/sources/globals.h b/paint/sources/globals.h index 52215cc0..2233052f 100644 --- a/paint/sources/globals.h +++ b/paint/sources/globals.h @@ -158,6 +158,7 @@ i32 history_redos = 0; // Redos available bool history_push_undo = false; // Store undo on next paint slot_layer_t_array_t *history_undo_layers = NULL; ui_handle_t *tab_scripts_hscript; +extern int tab_stages_selected; any_map_t *import_mesh_importers; i32 ui_menubar_default_w = 406; ui_handle_t *ui_menubar_hwnd; diff --git a/paint/sources/io/import_legacy.c b/paint/sources/io/import_legacy.c index 5ab86221..2d2a9acf 100644 --- a/paint/sources/io/import_legacy.c +++ b/paint/sources/io/import_legacy.c @@ -271,11 +271,25 @@ project_t *import_arm_from_map_to_arm(any_map_t *old) { } } + any_array_t *stages = any_map_get(old, "stages"); + if (stages != NULL) { + project->stages = any_array_create_from_raw((void *[]){}, 0); + for (i32 i = 0; i < stages->length; ++i) { + any_map_t *old = stages->buffer[i]; + stage_t *d = GC_ALLOC_INIT(stage_t, {0}); + d->name = any_map_get(old, "name"); + d->objects = any_map_get(old, "objects"); + d->layers = any_map_get(old, "layers"); + any_array_push(project->stages, d); + } + } + return project; } project_t *import_arm_from_version_8(any_map_t *old) { any_map_set(old, "script_names", NULL); + any_map_set(old, "stages", NULL); return import_arm_from_map_to_arm(old); } diff --git a/paint/sources/io/import_mesh.c b/paint/sources/io/import_mesh.c index 11d3fca7..2ef23c62 100644 --- a/paint/sources/io/import_mesh.c +++ b/paint/sources/io/import_mesh.c @@ -213,6 +213,8 @@ void import_mesh_make_mesh(raw_mesh_t *mesh) { history_reset(); } + g_project->stages = NULL; + // Wait for add_mesh calls to finish sys_notify_on_next_frame(&import_mesh_finish_import, NULL); } diff --git a/paint/sources/main.c b/paint/sources/main.c index 5ad4011b..c6b67d6e 100644 --- a/paint/sources/main.c +++ b/paint/sources/main.c @@ -162,7 +162,6 @@ #include "ui/tab_meshes.c" #include "ui/tab_plugins.c" #include "ui/tab_scripts.c" -#include "ui/tab_stages.c" #include "ui/tab_swatches.c" #include "ui/tab_textures.c" #include "ui/tab_timeline.c" @@ -194,6 +193,7 @@ #include "util/util_resize.c" #include "util/util_select.c" #include "util/util_shortcut.c" +#include "util/util_stage.c" #include "util/util_stencil.c" #include "util/util_texture.c" #include "util/util_touch.c" diff --git a/paint/sources/types.h b/paint/sources/types.h index 1d008194..4bd19192 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -521,6 +521,12 @@ typedef struct node_group { struct ui_node_canvas *canvas; } node_group_t; +typedef struct { + char *name; + string_array_t *objects; + string_array_t *layers; +} stage_t; + typedef struct { struct asset_t_array *assets; struct slot_material_t_array *materials; @@ -566,6 +572,7 @@ typedef struct project { i32 timeline_max_frames; struct timeline_layer_keyframe_data_t_array *timeline_layers; struct timeline_mesh_keyframe_data_t_array *timeline_meshes; + struct stage_t_array *stages; project_runtime_t *_; } project_t; @@ -949,3 +956,9 @@ typedef struct swatch_color_t_array { int length; int capacity; } swatch_color_t_array_t; + +typedef struct stage_t_array { + stage_t **buffer; + int length; + int capacity; +} stage_t_array_t; diff --git a/paint/sources/ui/tab_layers.c b/paint/sources/ui/tab_layers.c index e2a36f8c..209d6203 100644 --- a/paint/sources/ui/tab_layers.c +++ b/paint/sources/ui/tab_layers.c @@ -226,6 +226,7 @@ void tab_layers_delete_layer(slot_layer_t *l) { slot_material_t *m = g_project->_->materials->buffer[i]; tab_layers_remap_layer_pointers(m->canvas->nodes, tab_layers_fill_layer_map(pointers)); } + tab_stages_prune(); } void tab_layers_draw_layer_slot_full_delete_layer(void *_) { @@ -902,6 +903,11 @@ void tab_layers_draw_layer_context_menu(slot_layer_t *l, bool mini) { } void tab_layers_draw_layer_slot(slot_layer_t *l, i32 i, bool mini) { + stage_t *stage = tab_stages_get_stage(); + if (stage != NULL && string_array_index_of(stage->layers, l->name) < 0) { + return; + } + if (g_context->layer_filter > 0 && slot_layer_get_object_mask(l) > 0 && slot_layer_get_object_mask(l) != g_context->layer_filter) { return; } diff --git a/paint/sources/ui/tab_meshes.c b/paint/sources/ui/tab_meshes.c index 2d714128..a42bf1c7 100644 --- a/paint/sources/ui/tab_meshes.c +++ b/paint/sources/ui/tab_meshes.c @@ -9,6 +9,7 @@ any_map_t *tab_meshes_preview_map = NULL; void tab_meshes_draw_context_menu_delete_next_frame(mesh_object_t *o) { data_delete_mesh(o->data->_->handle); mesh_object_remove(o); + tab_stages_prune(); g_context->paint_object = context_main_object(); util_mesh_merge(NULL); g_context->ddirty = 2; @@ -572,7 +573,7 @@ void tab_meshes_draw(ui_handle_t *htab) { g_ui->_x += 2; if (row > 0) { - g_ui->_y += UI_ELEMENT_OFFSET() * 10.0; + g_ui->_y += slotw * 0.9 + 8 + UI_ELEMENT_OFFSET() * 2.0; } for (i32 j = 0; j < num; ++j) { @@ -655,6 +656,17 @@ void tab_meshes_draw(ui_handle_t *htab) { o->base->visible = ui_check(h, o->base->name, ""); if (h->changed) { + stage_t *stage = tab_stages_get_stage(); + if (stage != NULL) { + i32 idx = string_array_index_of(stage->objects, o->base->name); + if (o->base->visible && idx < 0) { + string_array_push(stage->objects, o->base->name); + } + else if (!o->base->visible && idx >= 0) { + array_splice(stage->objects, idx, 1); + } + } + mesh_object_t_array_t *visibles = any_array_create_from_raw((void *[]){}, 0); for (i32 k = 0; k < g_project->_->paint_objects->length; ++k) { mesh_object_t *p = g_project->_->paint_objects->buffer[k]; diff --git a/paint/sources/ui/tab_stages.c b/paint/sources/ui/tab_stages.c deleted file mode 100644 index 44053ba4..00000000 --- a/paint/sources/ui/tab_stages.c +++ /dev/null @@ -1,76 +0,0 @@ - -#include "../global.h" - -typedef struct slot_stage { - char *name; -} slot_stage_t; - -any_array_t *project_stages = NULL; -int selected_stage = 0; -bool tab_stages_show_context_menu = false; -slot_stage_t *tab_stages_context_stage = NULL; - -void tab_stages_context_menu_delete(void *_) { - array_remove(project_stages, tab_stages_context_stage); - if (selected_stage >= project_stages->length) { - selected_stage = project_stages->length - 1; - } -} - -void tab_stages_context_menu_draw() { - g_ui->enabled = project_stages->length > 1; - if (ui_menu_button(tr("Delete"), "delete", ICON_DELETE)) { - sys_notify_on_next_frame(&tab_stages_context_menu_delete, NULL); - } - g_ui->enabled = true; -} - -void tab_stages_draw(ui_handle_t *htab) { - if (ui_tab(htab, tr("Stages"), false, -1, false)) { - - ui_begin_sticky(); - f32_array_t *row = f32_array_create_from_raw( - (f32[]){ - -70, - }, - 1); - ui_row(row); - if (ui_icon_button("New", ICON_PLUS, UI_ALIGN_CENTER)) { - slot_stage_t *s = GC_ALLOC_INIT(slot_stage_t, {.name = string("%s %s", tr("Stage"), i32_to_string(project_stages->length + 1))}); - any_array_push(project_stages, s); - selected_stage = project_stages->length - 1; - } - // if (ui_button(tr("Nodes"), UI_ALIGN_CENTER, "")) {} - ui_end_sticky(); - - if (project_stages == NULL) { - project_stages = any_array_create(0); - gc_root(project_stages); - - slot_stage_t *s = GC_ALLOC_INIT(slot_stage_t, {.name = "Stage 1"}); - any_array_push(project_stages, s); - } - - for (i32 i = 0; i < project_stages->length; ++i) { - slot_stage_t *s = project_stages->buffer[i]; - tab_stages_show_context_menu = false; - if (i == selected_stage) { - ui_fill(0, 0, g_ui->_window_w, g_theme->ELEMENT_H, g_theme->HIGHLIGHT_COL); - } - ui_text(s->name, UI_ALIGN_LEFT, 0x00000000); - if (g_ui->is_released) { - selected_stage = i; - } - if (g_ui->is_hovered && g_ui->input_released_r) { - gc_unroot(tab_stages_context_stage); - tab_stages_context_stage = s; - gc_root(tab_stages_context_stage); - tab_stages_show_context_menu = true; - } - ui_fill(0, 0, (g_ui->_window_w / (float)UI_SCALE() - 2), 1 * UI_SCALE(), g_theme->SEPARATOR_COL); - if (tab_stages_show_context_menu) { - ui_menu_draw(&tab_stages_context_menu_draw, -1, -1); - } - } - } -} diff --git a/paint/sources/ui/tab_timeline.c b/paint/sources/ui/tab_timeline.c index 6fb1b6f6..60496fce 100644 --- a/paint/sources/ui/tab_timeline.c +++ b/paint/sources/ui/tab_timeline.c @@ -963,8 +963,36 @@ void tab_timeline_draw(ui_handle_t *htab) { tab_timeline_init(); ui_begin_sticky(); - f32_array_t *row = f32_array_create_from_raw((f32[]){-100, -100, -40, -40, -40, -40, -60}, 7); + f32_array_t *row = f32_array_create_from_raw((f32[]){-70, -110, -100, -100, -40, -40, -40, -40, -60}, 9); ui_row(row); + + // Stage + if (g_project->stages == NULL) { + g_project->stages = any_array_create_from_raw((void *[]){}, 0); + any_array_push(g_project->stages, tab_stages_create_stage("Stage 1")); + } + + string_array_t *stage_names = string_array_create(0); + if (g_project->stages != NULL) { + for (i32 i = 0; i < g_project->stages->length; ++i) { + string_array_push(stage_names, g_project->stages->buffer[i]->name); + } + } + + if (ui_icon_button(tr("Stage"), ICON_PLUS, UI_ALIGN_CENTER)) { + stage_t *s = tab_stages_create_stage(string("%s %s", tr("Stage"), i32_to_string(g_project->stages->length + 1))); + any_array_push(g_project->stages, s); + tab_stages_selected = g_project->stages->length - 1; + tab_stages_apply(s); + } + + ui_handle_t *stage_handle = ui_handle(__ID__); + stage_handle->i = tab_stages_selected; + tab_stages_selected = ui_combo(stage_handle, stage_names, tr("Stage"), false, UI_ALIGN_LEFT, true); + if (stage_handle->changed && g_project->stages != NULL && tab_stages_selected < g_project->stages->length) { + tab_stages_apply(g_project->stages->buffer[tab_stages_selected]); + } + if (ui_icon_button(tr("Keyframe"), ICON_PLUS, UI_ALIGN_CENTER)) { i32 li = tab_timeline_selected_row; if (tab_timeline_selected_frame > 0 && li < g_project->_->layers->length && slot_layer_is_layer(g_project->_->layers->buffer[li])) { @@ -1017,7 +1045,7 @@ void tab_timeline_draw(ui_handle_t *htab) { g_ui->enabled = true; ui_end_sticky(); - f32 layer_name_w = 100.0f * UI_SCALE(); + f32 layer_name_w = 120.0f * UI_SCALE(); f32 frame_w = 16.0f * UI_SCALE(); f32 start_x = g_ui->_x + layer_name_w; f32 start_y = g_ui->_y; @@ -1061,19 +1089,39 @@ void tab_timeline_draw(ui_handle_t *htab) { u32 sel_col = g_theme->HIGHLIGHT_COL; i32 row_count = g_project->_->layers->length; + stage_t *stage = tab_stages_get_stage(); + gpu_texture_t *icons = resource_get("icons.k"); f32 icon_size = strip_h - 2; + f32 eye_size = 18.0f * UI_SCALE(); + i32 vis_row = 0; for (i32 ri = 0; ri < row_count; ri++) { - slot_layer_t *layer = g_project->_->layers->buffer[ri]; - f32 row_y = start_y + font_h + 2 + ri * strip_h; - f32 icon_y = row_y + (strip_h - icon_size) / 2.0f; + slot_layer_t *layer = g_project->_->layers->buffer[ri]; + if (stage != NULL && string_array_index_of(stage->layers, layer->name) < 0) { + continue; + } + f32 row_y = start_y + font_h + 2 + vis_row * strip_h; + f32 icon_y = row_y + (strip_h - icon_size) / 2.0f; + + // Eye icon + rect_t *eye = resource_tile18(icons, layer->visible ? ICON18_EYE_ON : ICON18_EYE_OFF); + f32 eye_y = row_y + (strip_h - eye_size) / 2.0f; + draw_set_color(g_theme->HOVER_COL + 0x00282828); + draw_scaled_sub_image(icons, eye->x, eye->y, eye->w, eye->h, g_ui->_x, eye_y, eye_size, eye_size); + bool eye_hover = !tab_timeline_scrolling && g_ui->input_x > g_ui->_window_x + g_ui->_x && g_ui->input_x < g_ui->_window_x + g_ui->_x + eye_size && + g_ui->input_y > g_ui->_window_y + row_y && g_ui->input_y < g_ui->_window_y + row_y + strip_h; + if (eye_hover && g_ui->input_released) { + layer->visible = !layer->visible; + make_material_parse_mesh_material(); + g_context->ddirty = 2; + } rect_t *rect = resource_tile50(icons, ICON_LAYERS); draw_set_color(g_theme->LABEL_COL); - draw_scaled_sub_image(icons, rect->x, rect->y, rect->w, rect->h, g_ui->_x, icon_y, icon_size, icon_size); + draw_scaled_sub_image(icons, rect->x, rect->y, rect->w, rect->h, g_ui->_x + eye_size + 4, icon_y, icon_size, icon_size); draw_set_color(g_theme->LABEL_COL); - draw_string(layer->name, g_ui->_x + icon_size + 2, row_y + (strip_h - font_h) / 2.0f); + draw_string(layer->name, g_ui->_x + eye_size + icon_size + 6, row_y + (strip_h - font_h) / 2.0f); for (i32 i = tab_timeline_scroll; i < tab_timeline_scroll + visible + 1 && i < tab_timeline_max_frames; i++) { f32 x = start_x + (i - tab_timeline_scroll) * frame_w; @@ -1119,20 +1167,45 @@ void tab_timeline_draw(ui_handle_t *htab) { ui_menu_draw(&tab_timeline_draw_frame_context_menu, -1, -1); } } + vis_row++; } i32 mesh_count = g_project->_->paint_objects->length; for (i32 mi = 0; mi < mesh_count; mi++) { - mesh_object_t *mesh = g_project->_->paint_objects->buffer[mi]; - i32 ri = row_count + mi; - f32 row_y = start_y + font_h + 2 + ri * strip_h; - f32 icon_y = row_y + (strip_h - icon_size) / 2.0f; + mesh_object_t *mesh = g_project->_->paint_objects->buffer[mi]; + i32 ri = row_count + mi; + if (stage != NULL && string_array_index_of(stage->objects, mesh->base->name) < 0) { + continue; + } + f32 row_y = start_y + font_h + 2 + vis_row * strip_h; + f32 icon_y = row_y + (strip_h - icon_size) / 2.0f; + + // Eye icon + rect_t *eye = resource_tile18(icons, mesh->base->visible ? ICON18_EYE_ON : ICON18_EYE_OFF); + f32 eye_y = row_y + (strip_h - eye_size) / 2.0f; + draw_set_color(g_theme->HOVER_COL + 0x00282828); + draw_scaled_sub_image(icons, eye->x, eye->y, eye->w, eye->h, g_ui->_x, eye_y, eye_size, eye_size); + bool eye_hover = !tab_timeline_scrolling && g_ui->input_x > g_ui->_window_x + g_ui->_x && g_ui->input_x < g_ui->_window_x + g_ui->_x + eye_size && + g_ui->input_y > g_ui->_window_y + row_y && g_ui->input_y < g_ui->_window_y + row_y + strip_h; + if (eye_hover && g_ui->input_released) { + mesh->base->visible = !mesh->base->visible; + + mesh_object_t_array_t *visibles = any_array_create_from_raw((void *[]){}, 0); + for (i32 k = 0; k < g_project->_->paint_objects->length; k++) { + mesh_object_t *p = g_project->_->paint_objects->buffer[k]; + if (p->base->visible) { + any_array_push(visibles, p); + } + } + util_mesh_merge(visibles); + g_context->ddirty = 2; + } rect_t *rect = resource_tile50(icons, ICON_CUBE); draw_set_color(g_theme->LABEL_COL); - draw_scaled_sub_image(icons, rect->x, rect->y, rect->w, rect->h, g_ui->_x, icon_y, icon_size, icon_size); + draw_scaled_sub_image(icons, rect->x, rect->y, rect->w, rect->h, g_ui->_x + eye_size + 4, icon_y, icon_size, icon_size); draw_set_color(g_theme->LABEL_COL); - draw_string(mesh->base->name, g_ui->_x + icon_size + 2, row_y + (strip_h - font_h) / 2.0f); + draw_string(mesh->base->name, g_ui->_x + eye_size + icon_size + 6, row_y + (strip_h - font_h) / 2.0f); for (i32 i = tab_timeline_scroll; i < tab_timeline_scroll + visible + 1 && i < tab_timeline_max_frames; i++) { f32 x = start_x + (i - tab_timeline_scroll) * frame_w; @@ -1178,11 +1251,12 @@ void tab_timeline_draw(ui_handle_t *htab) { ui_menu_draw(&tab_timeline_draw_frame_context_menu, -1, -1); } } + vis_row++; } // Scrollbar f32 scrollbar_h = 8.0f * UI_SCALE(); - f32 scrollbar_y = start_y + font_h + 2 + (row_count + mesh_count) * strip_h; + f32 scrollbar_y = start_y + font_h + 2 + vis_row * strip_h; f32 handle_w = track_w * (f32)visible / tab_timeline_max_frames; f32 handle_x = start_x + (max_scroll > 0 ? tab_timeline_scroll * (track_w - handle_w) / max_scroll : 0); diff --git a/paint/sources/ui/ui_base.c b/paint/sources/ui/ui_base.c index 15c3463d..ecc49625 100644 --- a/paint/sources/ui/ui_base.c +++ b/paint/sources/ui/ui_base.c @@ -347,7 +347,7 @@ tab_draw_array_t_array_t *ui_base_init_hwnd_tabs() { tab_draw_array_t *a0 = any_array_create_from_raw( (void *[]){ _draw_callback_create(tab_layers_draw), - _draw_callback_create(tab_stages_draw), + _draw_callback_create(tab_meshes_draw), _draw_callback_create(tab_scripts_draw), }, 3); @@ -361,7 +361,6 @@ tab_draw_array_t_array_t *ui_base_init_hwnd_tabs() { tab_draw_array_t *a2 = any_array_create_from_raw( (void *[]){ _draw_callback_create(tab_browser_draw), - _draw_callback_create(tab_meshes_draw), _draw_callback_create(tab_textures_draw), _draw_callback_create(tab_fonts_draw), _draw_callback_create(tab_swatches_draw), diff --git a/paint/sources/util/util_encode.c b/paint/sources/util/util_encode.c index dd2b55f9..8b002ad8 100644 --- a/paint/sources/util/util_encode.c +++ b/paint/sources/util/util_encode.c @@ -201,7 +201,7 @@ buffer_t *util_encode_project(project_t *raw) { buffer_t *encoded = buffer_create(size); armpack_encode_start(encoded->buffer); - armpack_encode_map(32); + armpack_encode_map(33); armpack_encode_string("version"); armpack_encode_string(raw->version); @@ -527,6 +527,23 @@ buffer_t *util_encode_project(project_t *raw) { armpack_encode_null(); } + armpack_encode_string("stages"); + if (raw->stages != NULL) { + armpack_encode_array(raw->stages->length); + for (i32 i = 0; i < raw->timeline_meshes->length; ++i) { + armpack_encode_map(3); + armpack_encode_string("name"); + armpack_encode_string(raw->stages->buffer[i]->name); + armpack_encode_string("objects"); + armpack_encode_array_string(raw->stages->buffer[i]->objects); + armpack_encode_string("layers"); + armpack_encode_array_string(raw->stages->buffer[i]->layers); + } + } + else { + armpack_encode_null(); + } + i32 ei = armpack_encode_end(); encoded->length = ei; return encoded; diff --git a/paint/sources/util/util_layer.c b/paint/sources/util/util_layer.c index c0893750..13ca5d8a 100644 --- a/paint/sources/util/util_layer.c +++ b/paint/sources/util/util_layer.c @@ -594,6 +594,12 @@ slot_layer_t *layers_new_layer(bool clear, i32 position, slot_layer_t *parent) { if (clear) { sys_notify_on_next_frame(&layers_new_layer_clear, l); } + + stage_t *stage = tab_stages_get_stage(); + if (stage != NULL && string_array_index_of(stage->layers, l->name) < 0) { + string_array_push(stage->layers, l->name); + } + g_context->layer_preview_dirty = true; return l; } diff --git a/paint/sources/util/util_stage.c b/paint/sources/util/util_stage.c new file mode 100644 index 00000000..174804d8 --- /dev/null +++ b/paint/sources/util/util_stage.c @@ -0,0 +1,76 @@ + +#include "../global.h" + +int tab_stages_selected = 0; + +stage_t *tab_stages_create_stage(char *name) { + stage_t *s = GC_ALLOC_INIT(stage_t, {0}); + s->name = name; + s->objects = string_array_create(0); + s->layers = string_array_create(0); + for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) { + string_array_push(s->objects, g_project->_->paint_objects->buffer[i]->base->name); + } + for (i32 i = 0; i < g_project->_->layers->length; ++i) { + string_array_push(s->layers, g_project->_->layers->buffer[i]->name); + } + return s; +} + +stage_t *tab_stages_get_stage() { + if (g_project->stages == NULL || g_project->stages->length == 0) { + return NULL; + } + if (tab_stages_selected < 0 || tab_stages_selected >= g_project->stages->length) { + return NULL; + } + return g_project->stages->buffer[tab_stages_selected]; +} + +void tab_stages_apply(stage_t *stage) { + mesh_object_t_array_t *visibles = any_array_create_from_raw((void *[]){}, 0); + for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) { + mesh_object_t *p = g_project->_->paint_objects->buffer[i]; + p->base->visible = string_array_index_of(stage->objects, p->base->name) >= 0; + if (p->base->visible) { + any_array_push(visibles, p); + } + } + util_mesh_merge(visibles); + g_context->ddirty = 2; +} + +void tab_stages_prune() { + if (g_project->stages == NULL) { + return; + } + for (i32 i = 0; i < g_project->stages->length; ++i) { + stage_t *s = g_project->stages->buffer[i]; + + for (i32 j = s->objects->length - 1; j >= 0; --j) { + bool found = false; + for (i32 k = 0; k < g_project->_->paint_objects->length; ++k) { + if (string_equals(g_project->_->paint_objects->buffer[k]->base->name, s->objects->buffer[j])) { + found = true; + break; + } + } + if (!found) { + array_splice(s->objects, j, 1); + } + } + + for (i32 j = s->layers->length - 1; j >= 0; --j) { + bool found = false; + for (i32 k = 0; k < g_project->_->layers->length; ++k) { + if (string_equals(g_project->_->layers->buffer[k]->name, s->layers->buffer[j])) { + found = true; + break; + } + } + if (!found) { + array_splice(s->layers, j, 1); + } + } + } +}