luboslenco 1 year ago
parent
commit
53ba0e62b1
56 changed files with 1119 additions and 835 deletions
  1. 1 1
      armorlab/Sources/make_material.ts
  2. 3 3
      armorlab/Sources/nodes/inpaint_node.ts
  3. 9 10
      armorlab/Sources/nodes/photo_to_pbr_node.ts
  4. 2 2
      armorlab/Sources/nodes/rgb_node.ts
  5. 95 110
      armorlab/Sources/ui_nodes_ext.ts
  6. 4 4
      armorpaint/Sources/make_material.ts
  7. 29 23
      armorpaint/Sources/slot_layer.ts
  8. 2 3
      armorpaint/Sources/slot_material.ts
  9. 56 53
      armorpaint/Sources/tab_layers.ts
  10. 1 1
      armorsculpt/Sources/import_mesh.ts
  11. 3 3
      armorsculpt/Sources/make_material.ts
  12. 63 54
      base/Sources/base.ts
  13. 32 37
      base/Sources/box_export.ts
  14. 10 4
      base/Sources/box_preferences.ts
  15. 14 15
      base/Sources/box_projects.ts
  16. 7 7
      base/Sources/console.ts
  17. 2 7
      base/Sources/context.ts
  18. 3 3
      base/Sources/export_arm.ts
  19. 58 19
      base/Sources/file.ts
  20. 34 35
      base/Sources/history.ts
  21. 21 18
      base/Sources/import_arm.ts
  22. 21 13
      base/Sources/import_asset.ts
  23. 2 3
      base/Sources/import_blend_material.ts
  24. 4 4
      base/Sources/import_envmap.ts
  25. 2 3
      base/Sources/import_font.ts
  26. 2 2
      base/Sources/import_mesh.ts
  27. 13 6
      base/Sources/import_texture.ts
  28. 4 4
      base/Sources/node_shader.ts
  29. 11 0
      base/Sources/nodes_material.ts
  30. 44 34
      base/Sources/parser_exr.ts
  31. 1 3
      base/Sources/parser_logic.ts
  32. 1 1
      base/Sources/physics_world.ts
  33. 120 76
      base/Sources/project.ts
  34. 1 4
      base/Sources/render_path_raytrace_bake.ts
  35. 87 67
      base/Sources/tab_browser.ts
  36. 16 4
      base/Sources/tab_brushes.ts
  37. 1 1
      base/Sources/tab_console.ts
  38. 20 7
      base/Sources/tab_fonts.ts
  39. 9 7
      base/Sources/tab_materials.ts
  40. 8 0
      base/Sources/tab_meshes.ts
  41. 1 1
      base/Sources/tab_script.ts
  42. 13 0
      base/Sources/tab_swatches.ts
  43. 22 9
      base/Sources/tab_textures.ts
  44. 18 0
      base/Sources/translator.ts
  45. 14 9
      base/Sources/ui_base.ts
  46. 24 10
      base/Sources/ui_files.ts
  47. 7 5
      base/Sources/ui_header.ts
  48. 7 3
      base/Sources/ui_menu.ts
  49. 1 1
      base/Sources/ui_menubar.ts
  50. 95 54
      base/Sources/ui_nodes.ts
  51. 62 53
      base/Sources/ui_toolbar.ts
  52. 14 4
      base/Sources/ui_view2d.ts
  53. 3 12
      base/Sources/uniforms_ext.ts
  54. 2 3
      base/Sources/util_render.ts
  55. 19 19
      base/Sources/util_uv.ts
  56. 1 1
      base/project.js

+ 1 - 1
armorlab/Sources/make_material.ts

@@ -123,7 +123,7 @@ function make_material_voxelgi_half_extents(): string {
 }
 
 function make_material_delete_context(c: shader_context_t) {
-	base_notify_on_next_frame(function (c: shader_context_t) { // Ensure pipeline is no longer in use
+	app_notify_on_next_frame(function (c: shader_context_t) { // Ensure pipeline is no longer in use
 		shader_context_delete(c);
 	}, c);
 }

+ 3 - 3
armorlab/Sources/nodes/inpaint_node.ts

@@ -32,7 +32,7 @@ function inpaint_node_init() {
 
 	if (inpaint_node_mask == null) {
 		inpaint_node_mask = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8);
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function () {
 			g4_begin(inpaint_node_mask);
 			g4_clear(color_from_floats(1.0, 1.0, 1.0, 1.0));
 			g4_end();
@@ -73,7 +73,7 @@ function inpaint_node_get_as_image(self: inpaint_node_t, from: i32): image_t {
 }
 
 function inpaint_node_get_cached_image(self: inpaint_node_t): image_t {
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function (self: inpaint_node_t) {
 		let source: image_t = self.base.inputs[0].get_as_image();
 		if (base_pipe_copy == null) {
 			base_make_pipe();
@@ -89,7 +89,7 @@ function inpaint_node_get_cached_image(self: inpaint_node_t): image_t {
 		g4_set_index_buffer(const_data_screen_aligned_ib);
 		g4_draw();
 		g4_end();
-	});
+	}, self);
 	return inpaint_node_image;
 }
 

+ 9 - 10
armorlab/Sources/nodes/photo_to_pbr_node.ts

@@ -36,16 +36,15 @@ function photo_to_pbr_node_init() {
 }
 
 function photo_to_pbr_node_get_as_image(self: photo_to_pbr_node_t, from: i32): image_t {
-	let get_source = function (): image_t {
-		if (photo_to_pbr_node_cached_source != null) {
-			return photo_to_pbr_node_cached_source;
-		}
-		else {
-			return self.base.inputs[0].get_as_image();
-		}
+
+	let source: image_t;
+	if (photo_to_pbr_node_cached_source != null) {
+		source = photo_to_pbr_node_cached_source;
+	}
+	else {
+		source = self.base.inputs[0].get_as_image();
 	}
 
-	let source: image_t = get_source();
 	photo_to_pbr_node_cached_source = source;
 
 	console_progress(tr("Processing") + " - " + tr("Photo to PBR"));
@@ -158,9 +157,9 @@ function photo_to_pbr_node_get_as_image(self: photo_to_pbr_node_t, from: i32): i
 		g2_begin(photo_to_pbr_node_images[from]);
 		g2_draw_image(temp2, x * photo_to_pbr_node_tile_w, y * photo_to_pbr_node_tile_w);
 		g2_end();
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(temp2: image_t) {
 			image_unload(temp2);
-		});
+		}, temp2);
 	}
 
 	return photo_to_pbr_node_images[from];

+ 2 - 2
armorlab/Sources/nodes/rgb_node.ts

@@ -14,9 +14,9 @@ function rgb_node_create(arg: any): rgb_node_t {
 
 function rgb_node_get_as_image(self: rgb_node_t, from: i32): image_t {
 	if (self.image != null) {
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (self: rgb_node_t) {
 			image_unload(self.image);
-		});
+		}, self);
 	}
 
 	let f32a = f32_array_create(4);

+ 95 - 110
armorlab/Sources/ui_nodes_ext.ts

@@ -4,136 +4,121 @@ let ui_nodes_ext_last_vertices: buffer_view_t = null; // Before displacement
 function ui_nodes_ext_draw_buttons(ew: f32, start_y: f32) {
 	let ui = ui_nodes_ui;
 	if (zui_button(tr("Run"))) {
-		console_progress(tr("Processing"));
 
 		let delay_idle_sleep = function () {
 			krom_delay_idle_sleep();
 		}
 		app_notify_on_render_2d(delay_idle_sleep);
 
-		let tasks: i32 = 1;
+		ui_nodes_ext_tasks = 1;
 
-		let task_done = function () {
-			tasks--;
-			if (tasks == 0) {
-				console_progress(null);
-				context_raw.ddirty = 2;
-				app_remove_render_2d(delay_idle_sleep);
+		console_progress(tr("Processing"));
+		krom_g4_swap_buffers();
+
+		let timer = time_time();
+		parser_logic_parse(project_canvas);
+
+		photo_to_pbr_node_cached_source = null;
+		let texbase: image_t = brush_output_node_inst.get_as_image(channel_type_t.BASE_COLOR);
+		let texocc: image_t = brush_output_node_inst.get_as_image(channel_type_t.OCCLUSION);
+		let texrough: image_t = brush_output_node_inst.get_as_image(channel_type_t.ROUGHNESS);
+		let texnor: image_t = brush_output_node_inst.get_as_image(channel_type_t.NORMAL_MAP);
+		let texheight: image_t = brush_output_node_inst.get_as_image(channel_type_t.HEIGHT);
+
+		if (texbase != null) {
+			let texpaint = map_get(render_path_render_targets, "texpaint")._image;
+			g2_begin(texpaint);
+			g2_draw_scaled_image(texbase, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_end();
+		}
 
-				///if (krom_direct3d12 || krom_vulkan || krom_metal)
-				render_path_raytrace_ready = false;
-				///end
-			}
+		if (texnor != null) {
+			let texpaint_nor = map_get(render_path_render_targets, "texpaint_nor")._image;
+			g2_begin(texpaint_nor);
+			g2_draw_scaled_image(texnor, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_end();
 		}
 
-		base_notify_on_next_frame(function () {
-			let timer = time_time();
-			parser_logic_parse(project_canvas);
-
-			photo_to_pbr_node_cached_source = null;
-			brush_output_node_inst.get_as_image(channel_type_t.BASE_COLOR, function (texbase: image_t) {
-			brush_output_node_inst.get_as_image(channel_type_t.OCCLUSION, function (texocc: image_t) {
-			brush_output_node_inst.get_as_image(channel_type_t.ROUGHNESS, function (texrough: image_t) {
-			brush_output_node_inst.get_as_image(channel_type_t.NORMAL_MAP, function (texnor: image_t) {
-			brush_output_node_inst.get_as_image(channel_type_t.HEIGHT, function (texheight: image_t) {
-
-				if (texbase != null) {
-					let texpaint = map_get(render_path_render_targets, "texpaint")._image;
-					g2_begin(texpaint);
-					g2_draw_scaled_image(texbase, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
-					g2_end();
-				}
+		if (base_pipe_copy == null) {
+			base_make_pipe();
+		}
+		if (base_pipe_copy_a == null) {
+			base_make_pipe_copy_a();
+		}
+		if (const_data_screen_aligned_vb == null) {
+			const_data_create_screen_aligned_data();
+		}
 
-				if (texnor != null) {
-					let texpaint_nor = map_get(render_path_render_targets, "texpaint_nor")._image;
-					g2_begin(texpaint_nor);
-					g2_draw_scaled_image(texnor, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
-					g2_end();
-				}
+		let texpaint_pack = map_get(render_path_render_targets, "texpaint_pack")._image;
 
-				if (base_pipe_copy == null) {
-					base_make_pipe();
-				}
-				if (base_pipe_copy_a == null) {
-					base_make_pipe_copy_a();
-				}
-				if (const_data_screen_aligned_vb == null) {
-					const_data_create_screen_aligned_data();
-				}
+		if (texocc != null) {
+			g2_begin(texpaint_pack);
+			g2_set_pipeline(base_pipe_copy_r);
+			g2_draw_scaled_image(texocc, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_set_pipeline(null);
+			g2_end();
+		}
 
-				let texpaint_pack = map_get(render_path_render_targets, "texpaint_pack")._image;
+		if (texrough != null) {
+			g2_begin(texpaint_pack);
+			g2_set_pipeline(base_pipe_copy_g);
+			g2_draw_scaled_image(texrough, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_set_pipeline(null);
+			g2_end();
+		}
 
-				if (texocc != null) {
-					g2_begin(texpaint_pack);
-					g2_set_pipeline(base_pipe_copy_r);
-					g2_draw_scaled_image(texocc, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
-					g2_set_pipeline(null);
-					g2_end();
+		if (texheight != null) {
+			g4_begin(texpaint_pack);
+			g4_set_pipeline(base_pipe_copy_a);
+			g4_set_tex(base_pipe_copy_a_tex, texheight);
+			g4_set_vertex_buffer(const_data_screen_aligned_vb);
+			g4_set_index_buffer(const_data_screen_aligned_ib);
+			g4_draw();
+			g4_end();
+
+			if (ui_header_worktab.position == space_type_t.SPACE3D &&
+				BrushOutputNode.inst.inputs[channel_type_t.HEIGHT].node.constructor != FloatNode) {
+
+				// Make copy of vertices before displacement
+				let o = project_paint_objects[0];
+				let g = o.data;
+				let vertices = g4_vertex_buffer_lock(g._.vertex_buffer);
+				if (ui_nodes_ext_last_vertices == null || buffer_view_size(ui_nodes_ext_last_vertices) != buffer_view_size(vertices)) {
+					ui_nodes_ext_last_vertices = buffer_view_create(buffer_create(buffer_view_size(vertices)));
+					for (let i: i32 = 0; i < math_floor(buffer_view_size(vertices) / 2); ++i) {
+						buffer_view_set_i16(ui_nodes_ext_last_vertices, i * 2, buffer_view_get_i16(vertices, i * 2));
+					}
 				}
-
-				if (texrough != null) {
-					g2_begin(texpaint_pack);
-					g2_set_pipeline(base_pipe_copy_g);
-					g2_draw_scaled_image(texrough, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
-					g2_set_pipeline(null);
-					g2_end();
+				else {
+					for (let i: i32 = 0; i < math_floor(buffer_view_size(vertices) / 2); ++i) {
+						buffer_view_set_i16(vertices, i * 2, buffer_view_get_i16(ui_nodes_ext_last_vertices, i * 2));
+					}
 				}
+				g4_vertex_buffer_unlock(g._.vertex_buffer);
 
-				if (texheight != null) {
-					g4_begin(texpaint_pack);
-					g4_set_pipeline(base_pipe_copy_a);
-					g4_set_tex(base_pipe_copy_a_tex, texheight);
-					g4_set_vertex_buffer(const_data_screen_aligned_vb);
-					g4_set_index_buffer(const_data_screen_aligned_ib);
-					g4_draw();
-					g4_end();
-
-					if (ui_header_worktab.position == space_type_t.SPACE3D &&
-						BrushOutputNode.inst.inputs[channel_type_t.HEIGHT].node.constructor != FloatNode) {
-
-						// Make copy of vertices before displacement
-						let o = project_paint_objects[0];
-						let g = o.data;
-						let vertices = g4_vertex_buffer_lock(g._.vertex_buffer);
-						if (ui_nodes_ext_last_vertices == null || buffer_view_size(ui_nodes_ext_last_vertices) != buffer_view_size(vertices)) {
-							ui_nodes_ext_last_vertices = buffer_view_create(buffer_create(buffer_view_size(vertices)));
-							for (let i: i32 = 0; i < math_floor(buffer_view_size(vertices) / 2); ++i) {
-								buffer_view_set_i16(ui_nodes_ext_last_vertices, i * 2, buffer_view_get_i16(vertices, i * 2));
-							}
-						}
-						else {
-							for (let i: i32 = 0; i < math_floor(buffer_view_size(vertices) / 2); ++i) {
-								buffer_view_set_i16(vertices, i * 2, buffer_view_get_i16(ui_nodes_ext_last_vertices, i * 2));
-							}
-						}
-						g4_vertex_buffer_unlock(g._.vertex_buffer);
-
-						// Apply displacement
-						if (config_raw.displace_strength > 0) {
-							tasks++;
-							base_notify_on_next_frame(function () {
-								console_progress(tr("Apply Displacement"));
-								base_notify_on_next_frame(function () {
-									let uv_scale = scene_meshes[0].data.scale_tex * context_raw.brush_scale;
-									util_mesh_apply_displacement(texpaint_pack, 0.05 * config_raw.displace_strength, uv_scale);
-									util_mesh_calc_normals();
-									task_done();
-								});
-							});
-						}
-					}
+				// Apply displacement
+				if (config_raw.displace_strength > 0) {
+					ui_nodes_ext_tasks++;
+					console_progress(tr("Apply Displacement"));
+					krom_g4_swap_buffers();
+
+					let uv_scale = scene_meshes[0].data.scale_tex * context_raw.brush_scale;
+					util_mesh_apply_displacement(texpaint_pack, 0.05 * config_raw.displace_strength, uv_scale);
+					util_mesh_calc_normals();
 				}
+			}
+		}
+
+		console_log("Processing finished in " + (time_time() - timer));
+		krom_ml_unload();
 
-				console_log("Processing finished in " + (time_time() - timer));
-				krom_ml_unload();
+		console_progress(null);
+		context_raw.ddirty = 2;
+		app_remove_render_2d(delay_idle_sleep);
 
-				task_done();
-			});
-			});
-			});
-			});
-			});
-		});
+		///if (krom_direct3d12 || krom_vulkan || krom_metal)
+		render_path_raytrace_ready = false;
+		///end
 	}
 	ui._x += ew + 3;
 	ui._y = 2 + start_y;

+ 4 - 4
armorpaint/Sources/make_material.ts

@@ -267,9 +267,9 @@ function make_material_bake_node_previews() {
 		let key: string = keys[i];
 		if (array_index_of(context_raw.node_previews_used, key) == -1) {
 			let image: image_t = map_get(context_raw.node_previews, key);
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function (image: image_t) {
 				image_unload(image);
-			});
+			}, image);
 			map_delete(context_raw.node_previews, key);
 		}
 	}
@@ -530,7 +530,7 @@ function make_material_voxelgi_half_extents(): string {
 }
 
 function make_material_delete_context(c: shader_context_t) {
-	base_notify_on_next_frame(function () { // Ensure pipeline is no longer in use
+	app_notify_on_next_frame(function (c: shader_context_t) { // Ensure pipeline is no longer in use
 		shader_context_delete(c);
-	});
+	}, c);
 }

+ 29 - 23
armorpaint/Sources/slot_layer.ts

@@ -190,19 +190,23 @@ function slot_layer_unload(raw: slot_layer_t) {
 	let _texpaint_preview: image_t = raw.texpaint_preview;
 	///end
 
-	let _next = function () {
+	app_notify_on_next_frame(function (_texpaint: image_t) {
 		image_unload(_texpaint);
-		///if is_paint
-		if (_texpaint_nor != null) {
+	}, _texpaint);
+
+	///if is_paint
+	if (_texpaint_nor != null) {
+		app_notify_on_next_frame(function (_texpaint_nor: image_t) {
 			image_unload(_texpaint_nor);
-		}
-		if (_texpaint_pack != null) {
+		}, _texpaint_nor);
+	}
+	if (_texpaint_pack != null) {
+		app_notify_on_next_frame(function (_texpaint_pack: image_t) {
 			image_unload(_texpaint_pack);
-		}
-		image_unload(_texpaint_preview);
-		///end
+		}, _texpaint_pack);
 	}
-	base_notify_on_next_frame(_next);
+	image_unload(_texpaint_preview);
+	///end
 
 	map_delete(render_path_render_targets, "texpaint" + raw.ext);
 	///if is_paint
@@ -280,10 +284,9 @@ function slot_layer_invert_mask(raw: slot_layer_t) {
 	g2_set_pipeline(null);
 	g2_end();
 	let _texpaint: image_t = raw.texpaint;
-	let _next = function () {
+	app_notify_on_next_frame(function (_texpaint: image_t) {
 		image_unload(_texpaint);
-	}
-	base_notify_on_next_frame(_next);
+	}, _texpaint);
 	raw.texpaint = map_get(render_path_render_targets, "texpaint" + raw.id)._image = inverted;
 	context_raw.layer_preview_dirty = true;
 	context_raw.ddirty = 3;
@@ -419,14 +422,19 @@ function slot_layer_resize_and_set_bits(raw: slot_layer_t) {
 		g2_end();
 		///end
 
-		let _next = function () {
+		app_notify_on_next_frame(function (_texpaint: image_t) {
 			image_unload(_texpaint);
-			///if is_paint
+		}, _texpaint);
+
+		///if is_paint
+		app_notify_on_next_frame(function (_texpaint_nor: image_t) {
 			image_unload(_texpaint_nor);
+		}, _texpaint_nor);
+
+		app_notify_on_next_frame(function (_texpaint_pack: image_t) {
 			image_unload(_texpaint_pack);
-			///end
-		}
-		base_notify_on_next_frame(_next);
+		}, _texpaint_pack);
+		///end
 
 		map_get(rts, "texpaint" + raw.ext)._image = raw.texpaint;
 		///if is_paint
@@ -444,10 +452,9 @@ function slot_layer_resize_and_set_bits(raw: slot_layer_t) {
 		g2_set_pipeline(null);
 		g2_end();
 
-		let _next = function () {
+		app_notify_on_next_frame(function (_texpaint: image_t) {
 			image_unload(_texpaint);
-		}
-		base_notify_on_next_frame(_next);
+		}, _texpaint);
 
 		map_get(rts, "texpaint" + raw.ext)._image = raw.texpaint;
 	}
@@ -457,12 +464,11 @@ function slot_layer_to_fill_layer(raw: slot_layer_t) {
 	context_set_layer(raw);
 	raw.fill_layer = context_raw.material;
 	base_update_fill_layer();
-	let _next = function () {
+	app_notify_on_next_frame(function () {
 		make_material_parse_paint_material();
 		context_raw.layer_preview_dirty = true;
 		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-	}
-	base_notify_on_next_frame(_next);
+	});
 }
 
 function slot_layer_to_paint_layer(raw: slot_layer_t) {

+ 2 - 3
armorpaint/Sources/slot_material.ts

@@ -68,11 +68,10 @@ function slot_material_create(m: material_data_t = null, c: zui_node_canvas_t =
 }
 
 function slot_material_unload(raw: slot_material_t) {
-	let _next = function () {
+	app_notify_on_next_frame(function (raw: slot_material_t) {
 		image_unload(raw.image);
 		image_unload(raw.image_icon);
-	}
-	base_notify_on_next_frame(_next);
+	}, raw);
 }
 
 function slot_material_delete(raw: slot_material_t) {

+ 56 - 53
armorpaint/Sources/tab_layers.ts

@@ -102,10 +102,9 @@ function tab_layers_button_new(text: string) {
 				// let l: slot_layer_t = raw.layer;
 
 				let m: slot_layer_t = base_new_mask(false, l);
-				let _next = function () {
+				app_notify_on_next_frame(function (m: slot_layer_t) {
 					slot_layer_clear(m, 0x00000000);
-				}
-				base_notify_on_next_frame(_next);
+				}, m);
 				context_raw.layer_preview_dirty = true;
 				history_new_black_mask();
 				base_update_fill_layers();
@@ -117,10 +116,9 @@ function tab_layers_button_new(text: string) {
 				// let l: slot_layer_t = raw.layer;
 
 				let m: slot_layer_t = base_new_mask(false, l);
-				let _next = function () {
+				app_notify_on_next_frame(function (m: slot_layer_t) {
 					slot_layer_clear(m, 0xffffffff);
-				}
-				base_notify_on_next_frame(_next);
+				}, m);
 				context_raw.layer_preview_dirty = true;
 				history_new_white_mask();
 				base_update_fill_layers();
@@ -132,10 +130,9 @@ function tab_layers_button_new(text: string) {
 				// let l: slot_layer_t = raw.layer;
 
 				let m: slot_layer_t = base_new_mask(false, l);
-				let _init = function () {
+				app_notify_on_init(function (m: slot_layer_t) {
 					slot_layer_to_fill_layer(m);
-				}
-				app_notify_on_init(_init);
+				}, m);
 				context_raw.layer_preview_dirty = true;
 				history_new_fill_mask();
 				base_update_fill_layers();
@@ -425,10 +422,9 @@ function tab_layers_draw_layer_slot_full(l: slot_layer_t, i: i32) {
 							 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
 		if (in_focus && ui.is_delete_down && tab_layers_can_delete(context_raw.layer)) {
 			ui.is_delete_down = false;
-			let _init = function () {
+			app_notify_on_init(function () {
 				tab_layers_delete_layer(context_raw.layer);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 	}
 	ui._y -= center;
@@ -484,7 +480,7 @@ function tab_layers_draw_layer_slot_full(l: slot_layer_t, i: i32) {
 	ui._y -= zui_ELEMENT_OFFSET(ui);
 }
 
-function tab_layers_combo_object(ui: zui_t, l: slot_layer_t, label = false): zui_handle_t {
+function tab_layers_combo_object(ui: zui_t, l: slot_layer_t, label: bool = false): zui_handle_t {
 	let ar: string[] = [tr("Shared")];
 	for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
 		let p: mesh_object_t = project_paint_objects[i];
@@ -504,12 +500,11 @@ function tab_layers_combo_object(ui: zui_t, l: slot_layer_t, label = false): zui
 		context_set_layer(l);
 		make_material_parse_mesh_material();
 		if (l.fill_layer != null) { // Fill layer
-			let _init = function () {
+			app_notify_on_init(function (l: slot_layer_t) {
 				context_raw.material = l.fill_layer;
 				slot_layer_clear(l);
 				base_update_fill_layers();
-			}
-			app_notify_on_init(_init);
+			}, l);
 		}
 		else {
 			base_set_object_mask();
@@ -518,7 +513,7 @@ function tab_layers_combo_object(ui: zui_t, l: slot_layer_t, label = false): zui
 	return object_handle;
 }
 
-function tab_layers_combo_blending(ui: zui_t, l: slot_layer_t, label = false): zui_handle_t {
+function tab_layers_combo_blending(ui: zui_t, l: slot_layer_t, label: bool = false): zui_handle_t {
 	let blending_handle: zui_handle_t = zui_nest(zui_handle(__ID__), l.id);
 	blending_handle.position = l.blending;
 	zui_combo(blending_handle, [
@@ -724,6 +719,9 @@ function tab_layers_can_merge_down(l: slot_layer_t) : bool {
 	return true;
 }
 
+let tab_layers_l: slot_layer_t;
+let tab_layers_mini: bool;
+
 function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 	let add: i32 = 0;
 
@@ -748,10 +746,16 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 			add += 1;
 		}
 	}
+
 	let menu_elements: i32 = slot_layer_is_group(l) ? 7 : (19 + add);
+	tab_layers_l = l;
+	tab_layers_mini = mini;
 
 	ui_menu_draw(function (ui: zui_t) {
 
+		let l: slot_layer_t = tab_layers_l;
+		let mini: bool = tab_layers_mini;
+
 		if (mini) {
 			let visible_handle: zui_handle_t = zui_handle(__ID__);
 			visible_handle.selected = l.visible;
@@ -779,6 +783,7 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 		if (ui_menu_button(ui, tr("Export"))) {
 			if (slot_layer_is_mask(l)) {
 				ui_files_show("png", true, false, function (path: string) {
+					let l: slot_layer_t = tab_layers_l;
 					let f: string = ui_files_filename;
 					if (f == "") {
 						f = tr("untitled");
@@ -802,33 +807,33 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 			let to_paint_string: string = slot_layer_is_layer(l) ? tr("To Paint Layer") : tr("To Paint Mask");
 
 			if (l.fill_layer == null && ui_menu_button(ui, to_fill_string)) {
-				let _init = function () {
+				app_notify_on_init(function () {
+					let l: slot_layer_t = tab_layers_l;
 					slot_layer_is_layer(l) ? history_to_fill_layer() : history_to_fill_mask();
 					slot_layer_to_fill_layer(l);
-				}
-				app_notify_on_init(_init);
+				});
 			}
 			if (l.fill_layer != null && ui_menu_button(ui, to_paint_string)) {
-				let _init = function () {
+				app_notify_on_init(function () {
+					let l: slot_layer_t = tab_layers_l;
 					slot_layer_is_layer(l) ? history_to_paint_layer() : history_to_paint_mask();
 					slot_layer_to_paint_layer(l);
-				}
-				app_notify_on_init(_init);
+				});
 			}
 		}
 
 		ui.enabled = tab_layers_can_delete(l);
 		if (ui_menu_button(ui, tr("Delete"), "delete")) {
-			let _init = function () {
+			app_notify_on_init(function () {
 				tab_layers_delete_layer(context_raw.layer);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		ui.enabled = true;
 
 		if (l.fill_layer == null && ui_menu_button(ui, tr("Clear"))) {
 			context_set_layer(l);
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				if (!slot_layer_is_group(l)) {
 					history_clear_layer();
 					slot_layer_clear(l);
@@ -843,52 +848,51 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 					context_raw.layers_preview_dirty = true;
 					context_raw.layer = l;
 				}
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		if (slot_layer_is_mask(l) && l.fill_layer == null && ui_menu_button(ui, tr("Invert"))) {
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				context_set_layer(l);
 				history_invert_mask();
 				slot_layer_invert_mask(l);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		if (slot_layer_is_mask(l) && ui_menu_button(ui, tr("Apply"))) {
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				context_raw.layer = l;
 				history_apply_mask();
 				slot_layer_apply_mask(l);
 				context_set_layer(l.parent);
 				make_material_parse_mesh_material();
 				context_raw.layers_preview_dirty = true;
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		if (slot_layer_is_group(l) && ui_menu_button(ui, tr("Merge Group"))) {
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				base_merge_group(l);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		ui.enabled = tab_layers_can_merge_down(l);
 		if (ui_menu_button(ui, tr("Merge Down"))) {
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				context_set_layer(l);
 				history_merge_layers();
 				base_merge_down();
 				if (context_raw.layer.fill_layer != null) slot_layer_to_paint_layer(context_raw.layer);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 		ui.enabled = true;
 		if (ui_menu_button(ui, tr("Duplicate"))) {
-			let _init = function () {
+			app_notify_on_init(function () {
+				let l: slot_layer_t = tab_layers_l;
 				context_set_layer(l);
 				history_duplicate_layer();
 				base_duplicate_layer(l);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 
 		ui_menu_fill(ui);
@@ -949,10 +953,9 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 			if (scale_handle.changed) {
 				context_set_material(l.fill_layer);
 				context_set_layer(l);
-				let _init = function () {
+				app_notify_on_init(function () {
 					base_update_fill_layers();
-				}
-				app_notify_on_init(_init);
+				});
 				ui_menu_keep_open = true;
 			}
 
@@ -965,10 +968,9 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 				context_set_material(l.fill_layer);
 				context_set_layer(l);
 				make_material_parse_paint_material();
-				let _init = function () {
+				app_notify_on_init(function () {
 					base_update_fill_layers();
-				}
-				app_notify_on_init(_init);
+				});
 				ui_menu_keep_open = true;
 			}
 
@@ -981,10 +983,9 @@ function tab_layers_draw_layer_context_menu(l: slot_layer_t, mini: bool) {
 				context_set_material(l.fill_layer);
 				context_set_layer(l);
 				make_material_parse_paint_material();
-				let _init = function () {
+				app_notify_on_init(function () {
 					base_update_fill_layers();
-				}
-				app_notify_on_init(_init);
+				});
 				ui_menu_keep_open = true;
 			}
 		}
@@ -1060,7 +1061,9 @@ function tab_layers_make_mask_preview_rgba32(l: slot_layer_t) {
 	// Convert from R8 to RGBA32 for tooltip display
 	if (context_raw.mask_preview_last != l) {
 		context_raw.mask_preview_last = l;
+		tab_layers_l = l;
 		app_notify_on_init(function () {
+			let l: slot_layer_t = tab_layers_l;
 			g2_begin(context_raw.mask_preview_rgba32);
 			g2_set_pipeline(ui_view2d_pipe);
 			g4_set_int(ui_view2d_channel_loc, 1);

+ 1 - 1
armorsculpt/Sources/import_mesh.ts

@@ -128,7 +128,7 @@ function _import_mesh_make_mesh(mesh: any) {
 	// Wait for add_mesh calls to finish
 	app_notify_on_init(import_mesh_finish_import);
 
-	base_notify_on_next_frame(function (mesh: any) {
+	app_notify_on_next_frame(function (mesh: any) {
 		let f32 = f32_array_create(config_get_texture_res_x() * config_get_texture_res_y() * 4);
 		for (let i: i32 = 0; i < math_floor(mesh.inda.length); ++i) {
 			let index = mesh.inda[i];

+ 3 - 3
armorsculpt/Sources/make_material.ts

@@ -267,8 +267,8 @@ function make_material_bake_node_previews() {
 	for (let i: i32 = 0; i < keys.length; ++i) {
 		let key: string = keys[i];
 		if (array_index_of(context_raw.node_previews_used, key) == -1) {
-			let image = map_get(context_raw.node_previews, key);
-			base_notify_on_next_frame(function (image: image_t) {
+			let image: image_t = map_get(context_raw.node_previews, key);
+			app_notify_on_next_frame(function (image: image_t) {
 				image_unload(image);
 			}, image);
 			map_delete(context_raw.node_previews, key);
@@ -370,7 +370,7 @@ function make_material_voxelgi_half_extents(): string {
 }
 
 function make_material_delete_context(c: shader_context_t) {
-	base_notify_on_next_frame(function (c: shader_context_t) { // Ensure pipeline is no longer in use
+	app_notify_on_next_frame(function (c: shader_context_t) { // Ensure pipeline is no longer in use
 		shader_context_delete(c);
 	}, c);
 }

+ 63 - 54
base/Sources/base.ts

@@ -254,9 +254,7 @@ function base_init() {
 
 	sys_notify_on_drop_files(function (drop_path: string) {
 		///if krom_linux
-		krom_log(drop_path);
 		drop_path = uri_decode(drop_path);
-		krom_log(drop_path);
 		///end
 		drop_path = trim_end(drop_path);
 		array_push(base_drop_paths, drop_path);
@@ -563,11 +561,9 @@ function base_resize() {
 	}
 
 	if (ui_nodes_grid != null) {
-		let _grid: image_t = ui_nodes_grid;
-		let _next = function () {
-			image_unload(_grid);
-		}
-		base_notify_on_next_frame(_next);
+		app_notify_on_next_frame(function (grid: image_t) {
+			image_unload(grid);
+		}, ui_nodes_grid);
 		ui_nodes_grid = null;
 	}
 
@@ -875,16 +871,16 @@ function base_render() {
 		context_raw.camera_controls = config_raw.camera_controls;
 
 		///if is_lab
-		base_notify_on_next_frame(function () {
-			base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function () {
 				tab_meshes_set_default_mesh(".Sphere");
 			});
 		});
 		///end
 
 		///if is_sculpt
-		base_notify_on_next_frame(function () {
-			base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function () {
 				context_raw.project_type = project_model_t.SPHERE;
 				project_new();
 			});
@@ -987,20 +983,6 @@ function base_get_asset_index(file_name: string): i32 {
 	return i >= 0 ? i : 0;
 }
 
-function base_notify_on_next_frame(f: (data?: any)=>void, data: any = null) {
-	let _render = function (data: any) {
-		app_notify_on_init(function (data: any) {
-			let _update = function (data: any) {
-				app_notify_on_init(f, data);
-				app_remove_update(_update);
-			}
-			app_notify_on_update(_update, data);
-		}, data);
-		app_remove_render(_render);
-	}
-	app_notify_on_render(_render, data);
-}
-
 function base_toggle_fullscreen() {
 	if (sys_mode() == window_mode_t.WINDOWED) {
 		///if (krom_windows || krom_linux || krom_darwin)
@@ -1220,9 +1202,9 @@ function base_resize_layers() {
 		}
 		while (history_undo_layers.length > conf.undo_steps) {
 			let l: slot_layer_t = history_undo_layers.pop();
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function (l: slot_layer_t) {
 				slot_layer_unload(l);
-			});
+			}, l);
 		}
 	}
 	for (let i: i32 = 0; i < project_layers.length; ++i) {
@@ -1235,25 +1217,25 @@ function base_resize_layers() {
 	}
 	let rts: map_t<string, render_target_t> = render_path_render_targets;
 	let _texpaint_blend0: image_t = map_get(rts, "texpaint_blend0")._image;
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function (_texpaint_blend0: image_t) {
 		image_unload(_texpaint_blend0);
-	});
+	}, _texpaint_blend0);
 	map_get(rts, "texpaint_blend0").width = config_get_texture_res_x();
 	map_get(rts, "texpaint_blend0").height = config_get_texture_res_y();
 	map_get(rts, "texpaint_blend0")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8);
 	let _texpaint_blend1: image_t = map_get(rts, "texpaint_blend1")._image;
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function (_texpaint_blend1: image_t) {
 		image_unload(_texpaint_blend1);
-	});
+	}, _texpaint_blend1);
 	map_get(rts, "texpaint_blend1").width = config_get_texture_res_x();
 	map_get(rts, "texpaint_blend1").height = config_get_texture_res_y();
 	map_get(rts, "texpaint_blend1")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8);
 	context_raw.brush_blend_dirty = true;
 	if (map_get(rts, "texpaint_blur") != null) {
 		let _texpaint_blur: image_t = map_get(rts, "texpaint_blur")._image;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_texpaint_blur: image_t) {
 			image_unload(_texpaint_blur);
-		});
+		}, _texpaint_blur);
 		let size_x: f32 = math_floor(config_get_texture_res_x() * 0.95);
 		let size_y: f32 = math_floor(config_get_texture_res_y() * 0.95);
 		map_get(rts, "texpaint_blur").width = size_x;
@@ -1548,9 +1530,9 @@ function base_make_temp_img() {
 
 	if (base_temp_image != null && (base_temp_image.width != l.texpaint.width || base_temp_image.height != l.texpaint.height || base_temp_image.format != l.texpaint.format)) {
 		let _temptex0: render_target_t = map_get(render_path_render_targets, "temptex0");
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_temptex0: render_target_t) {
 			render_target_unload(_temptex0);
-		});
+		}, _temptex0);
 		map_delete(render_path_render_targets, "temptex0");
 		base_temp_image = null;
 	}
@@ -1578,9 +1560,9 @@ function base_make_temp_img() {
 function base_make_temp_mask_img() {
 	if (base_temp_mask_image != null && (base_temp_mask_image.width != config_get_texture_res_x() || base_temp_mask_image.height != config_get_texture_res_y())) {
 		let _temp_mask_image: image_t = base_temp_mask_image;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_temp_mask_image: image_t) {
 			image_unload(_temp_mask_image);
-		});
+		}, _temp_mask_image);
 		base_temp_mask_image = null;
 	}
 	if (base_temp_mask_image == null) {
@@ -1601,11 +1583,15 @@ function base_make_export_img() {
 		let _expa: image_t = base_expa;
 		let _expb: image_t = base_expb;
 		let _expc: image_t = base_expc;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_expa: image_t) {
 			image_unload(_expa);
+		}, _expa);
+		app_notify_on_next_frame(function (_expb: image_t) {
 			image_unload(_expb);
+		}, _expb);
+		app_notify_on_next_frame(function (_expc: image_t) {
 			image_unload(_expc);
-		});
+		}, _expc);
 		base_expa = null;
 		base_expb = null;
 		base_expc = null;
@@ -1616,7 +1602,7 @@ function base_make_export_img() {
 	if (base_expa == null) {
 		///if (is_paint || is_sculpt)
 		let format: string = base_bits_handle.position == texture_bits_t.BITS8  ? "RGBA32" :
-						base_bits_handle.position == texture_bits_t.BITS16 		? "RGBA64" :
+							 base_bits_handle.position == texture_bits_t.BITS16 ? "RGBA64" :
 																				  "RGBA128";
 		///end
 		///if is_lab
@@ -2308,7 +2294,9 @@ function base_new_layer(clear: bool = true, position: i32 = -1): slot_layer_t {
 		}
 	}
 	if (clear) {
-		app_notify_on_init(function () { slot_layer_clear(l); });
+		app_notify_on_init(function (l: slot_layer_t) {
+			slot_layer_clear(l);
+		}, l);
 	}
 	context_raw.layer_preview_dirty = true;
 	return l;
@@ -2325,7 +2313,9 @@ function base_new_mask(clear: bool = true, parent: slot_layer_t, position: i32 =
 	array_insert(project_layers, position, l);
 	context_set_layer(l);
 	if (clear) {
-		app_notify_on_init(function () { slot_layer_clear(l); });
+		app_notify_on_init(function (l: slot_layer_t) {
+			slot_layer_clear(l);
+		}, l);
 	}
 	context_raw.layer_preview_dirty = true;
 	return l;
@@ -2341,17 +2331,25 @@ function base_new_group(): slot_layer_t {
 	return l;
 }
 
+let _base_uv_type: uv_type_t;
+let _base_decal_mat: mat4_t;
+let _base_position: i32;
+
 function base_create_fill_layer(uv_type: uv_type_t = uv_type_t.UVMAP, decal_mat: mat4_t = null, position: i32 = -1) {
-	let _init = function () {
-		let l: slot_layer_t = base_new_layer(false, position);
+	_base_uv_type = uv_type;
+	_base_decal_mat = decal_mat;
+	_base_position = position;
+	app_notify_on_init(function () {
+		let l: slot_layer_t = base_new_layer(false, _base_position);
 		history_new_layer();
-		l.uv_type = uv_type;
-		if (decal_mat != null) l.decal_mat = decal_mat;
+		l.uv_type = _base_uv_type;
+		if (_base_decal_mat != null) {
+			l.decal_mat = _base_decal_mat;
+		}
 		l.object_mask = context_raw.layer_filter;
 		history_to_fill_layer();
 		slot_layer_to_fill_layer(l);
-	}
-	app_notify_on_init(_init);
+	});
 }
 
 function base_create_image_mask(asset: asset_t) {
@@ -2366,15 +2364,26 @@ function base_create_image_mask(asset: asset_t) {
 	context_raw.layer_preview_dirty = true;
 }
 
+let _base_base_color: i32;
+let _base_occlusion: f32;
+let _base_roughness: f32;
+let _base_metallic: f32;
+
 function base_create_color_layer(base_color: i32, occlusion: f32 = 1.0, roughness: f32 = base_default_rough, metallic: f32 = 0.0, position: i32 = -1) {
-	let _init = function () {
-		let l: slot_layer_t = base_new_layer(false, position);
+
+	_base_base_color = base_color;
+	_base_occlusion = occlusion;
+	_base_roughness = roughness;
+	_base_metallic = metallic;
+	_base_position = position;
+
+	app_notify_on_init(function () {
+		let l: slot_layer_t = base_new_layer(false, _base_position);
 		history_new_layer();
 		l.uv_type = uv_type_t.UVMAP;
 		l.object_mask = context_raw.layer_filter;
-		slot_layer_clear(l, base_color, null, occlusion, roughness, metallic);
-	}
-	app_notify_on_init(_init);
+		slot_layer_clear(l, _base_base_color, null, _base_occlusion, _base_roughness, _base_metallic);
+	});
 }
 
 function base_on_layers_resized() {
@@ -2469,7 +2478,7 @@ function base_on_layers_resized() {
 		map_delete(render_path_render_targets, "texpaint_node_target");
 	}
 
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function () {
 		base_init_layers();
 	});
 

+ 32 - 37
base/Sources/box_export.ts

@@ -57,6 +57,8 @@ function box_export_show_bake_material() {
 }
 ///end
 
+let _box_export_bake_material: bool;
+
 ///if (is_paint || is_lab)
 function box_export_tab_export_textures(ui: zui_t, title: string, bake_material: bool = false) {
 	let tab_vertical: bool = config_raw.touch_ui;
@@ -133,40 +135,34 @@ function box_export_tab_export_textures(ui: zui_t, title: string, bake_material:
 		if (zui_button(tr("Export"))) {
 			ui_box_hide();
 			if (context_raw.layers_destination == export_destination_t.PACKED) {
+				_box_export_bake_material = bake_material;
 				context_raw.texture_export_path = "/";
-				let _init = function () {
+				app_notify_on_init(function () {
 					///if is_paint
-					export_texture_run(context_raw.texture_export_path, bake_material);
+					export_texture_run(context_raw.texture_export_path, _box_export_bake_material);
 					///end
 					///if is_lab
 					export_texture_run(context_raw.texture_export_path);
 					///end
-				}
-				app_notify_on_init(_init);
+				});
 			}
 			else {
 				let filters = base_bits_handle.position != texture_bits_t.BITS8 ? "exr" : context_raw.format_type == texture_ldr_format_t.PNG ? "png" : "jpg";
 				ui_files_show(filters, true, false, function (path: string) {
 					context_raw.texture_export_path = path;
-					let do_export = function () {
-						let _init = function () {
-							///if is_paint
-							export_texture_run(context_raw.texture_export_path, bake_material);
-							///end
-							///if is_lab
-							export_texture_run(context_raw.texture_export_path);
-							///end
-						}
-						app_notify_on_init(_init);
-					}
 					///if (krom_android || krom_ios)
-					base_notify_on_next_frame(function () {
-						console_toast(tr("Exporting textures"));
-						base_notify_on_next_frame(do_export);
-					});
-					///else
-					do_export();
+					console_toast(tr("Exporting textures"));
+					krom_g4_swap_buffers();
 					///end
+					_box_export_bake_material = bake_material;
+					app_notify_on_init(function () {
+						///if is_paint
+						export_texture_run(context_raw.texture_export_path, _box_export_bake_material);
+						///end
+						///if is_lab
+						export_texture_run(context_raw.texture_export_path);
+						///end
+					});
 				});
 			}
 		}
@@ -176,6 +172,8 @@ function box_export_tab_export_textures(ui: zui_t, title: string, bake_material:
 	}
 }
 
+let _box_export_t: export_preset_texture_t;
+
 function box_export_tab_presets(ui: zui_t) {
 	let tab_vertical: bool = config_raw.touch_ui;
 	if (zui_tab(box_export_htab, tr("Presets"), tab_vertical)) {
@@ -246,9 +244,10 @@ function box_export_tab_presets(ui: zui_t) {
 			t.name = zui_text_input(htex);
 
 			if (ui.is_hovered && ui.input_released_r) {
+				_box_export_t = t;
 				ui_menu_draw(function (ui: zui_t) {
 					if (ui_menu_button(ui, tr("Delete"))) {
-						array_remove(box_export_preset.textures, t);
+						array_remove(box_export_preset.textures, _box_export_t);
 						box_export_save_preset();
 					}
 				}, 1);
@@ -374,18 +373,14 @@ function box_export_tab_export_mesh(ui: zui_t, htab: zui_handle_t) {
 				///else
 				let f: string = ui_files_filename;
 				///end
-				if (f == "") f = tr("untitled");
-				let do_export = function () {
-					export_mesh_run(path + path_sep + f, box_export_mesh_handle.position == 0 ? null : [project_paint_objects[box_export_mesh_handle.position - 1]], apply_displacement);
+				if (f == "") {
+					f = tr("untitled");
 				}
 				///if (krom_android || krom_ios)
-				base_notify_on_next_frame(function () {
-					console_toast(tr("Exporting mesh"));
-					base_notify_on_next_frame(do_export);
-				});
-				///else
-				do_export();
+				console_toast(tr("Exporting mesh"));
+				krom_g4_swap_buffers();
 				///end
+				export_mesh_run(path + path_sep + f, box_export_mesh_handle.position == 0 ? null : [project_paint_objects[box_export_mesh_handle.position - 1]], apply_displacement);
 			});
 		}
 	}
@@ -414,9 +409,9 @@ function box_export_show_material() {
 					if (f == "") {
 						f = tr("untitled");
 					}
-					app_notify_on_init(function () {
-						export_arm_run_material(path + path_sep + f);
-					});
+					app_notify_on_init(function (path: string) {
+						export_arm_run_material(path);
+					}, path + path_sep + f);
 				});
 			}
 		}
@@ -443,9 +438,9 @@ function box_export_show_brush() {
 				ui_files_show("arm", true, false, function (path: string) {
 					let f: string = ui_files_filename;
 					if (f == "") f = tr("untitled");
-					app_notify_on_init(function () {
-						export_arm_run_brush(path + path_sep + f);
-					});
+					app_notify_on_init(function (path: string) {
+						export_arm_run_brush(path);
+					}, path + path_sep + f);
 				});
 			}
 		}

+ 10 - 4
base/Sources/box_preferences.ts

@@ -110,13 +110,13 @@ function box_preferences_show() {
 						ui_files_show("json", false, false, function (path: string) {
 							let b: buffer_t = data_get_blob(path);
 							let raw: config_t = json_parse(sys_buffer_to_string(b));
-							app_notify_on_init(function () {
+							app_notify_on_init(function (raw: config_t) {
 								ui.t.ELEMENT_H = base_default_element_h;
 								config_import_from(raw);
 								box_preferences_set_scale();
 								make_material_parse_mesh_material();
 								make_material_parse_paint_material();
-							});
+							}, raw);
 						});
 					}
 				}, 2);
@@ -247,6 +247,7 @@ function box_preferences_show() {
 						ui_menu_draw(function (ui: zui_t) {
 							ui.changed = false;
 							let color: i32 = zui_color_wheel(h, false, null, 11 * ui.t.ELEMENT_H * zui_SCALE(ui), true);
+							let theme: any = base_theme;
 							theme[key] = color;
 							if (ui.changed) {
 								ui_menu_keep_open = true;
@@ -697,7 +698,10 @@ plugin.draw_ui = function (ui) {\
 						}
 						if (ui_menu_button(ui, tr("Export"))) {
 							ui_files_show("js", true, false, function (dest: string) {
-								if (!ends_with(ui_files_filename, ".js")) ui_files_filename += ".js";
+								if (!ends_with(ui_files_filename, ".js")) {
+									ui_files_filename += ".js";
+								}
+								let path: string = path_data() + path_sep + "plugins" + path_sep + f;
 								file_copy(path, dest + path_sep + ui_files_filename);
 							});
 						}
@@ -714,7 +718,9 @@ plugin.draw_ui = function (ui) {\
 			}
 		}
 
-	}, 620, config_raw.touch_ui ? 480 : 420, function () { config_save(); });
+	}, 620, config_raw.touch_ui ? 480 : 420, function () {
+		config_save();
+	});
 }
 
 function box_preferences_fetch_themes() {

+ 14 - 15
base/Sources/box_projects.ts

@@ -35,6 +35,9 @@ function box_projects_show() {
 	}, 600, 400, null, draggable);
 }
 
+let _box_projects_path: string;
+let _box_projects_icon_path: string;
+
 function box_projects_tab(ui: zui_t) {
 	if (zui_tab(box_projects_htab, tr("Projects"), true)) {
 		zui_begin_sticky();
@@ -123,33 +126,29 @@ function box_projects_tab(ui: zui_t) {
 						ui._x = uix;
 						zui_fill(0, 0, 128, 128, 0x66000000);
 						ui._x = _uix;
-						let do_import = function () {
-							app_notify_on_init(function () {
-								ui_box_hide();
-								import_arm_run_project(path);
-							});
-						}
-
 						///if (krom_android || krom_ios)
-						base_notify_on_next_frame(function () {
-							console_toast(tr("Opening project"));
-							base_notify_on_next_frame(do_import);
-						});
-						///else
-						do_import();
+						console_toast(tr("Opening project"));
+						krom_g4_swap_buffers();
 						///end
+						app_notify_on_init(function (path: string) {
+							ui_box_hide();
+							import_arm_run_project(path);
+						}, path);
 					}
 
 					let name: string = substring(path, string_last_index_of(path, path_sep) + 1, string_last_index_of(path, "."));
 					if (ui.is_hovered && ui.input_released_r) {
+						_box_projects_path = path;
+						_box_projects_icon_path = icon_path;
 						ui_menu_draw(function (ui: zui_t) {
 							// if (menuButton(ui, tr("Duplicate"))) {}
 							if (ui_menu_button(ui, tr("Delete"))) {
 								app_notify_on_init(function () {
-									file_delete(path);
-									file_delete(icon_path);
+									file_delete(_box_projects_path);
+									file_delete(_box_projects_icon_path);
 									let data_path: string = substring(path, 0, path.length - 4);
 									file_delete(data_path);
+									let recent_projects: string[] = config_raw.recent_projects;
 									array_splice(recent_projects, i, 1);
 								});
 							}

+ 7 - 7
base/Sources/console.ts

@@ -18,15 +18,15 @@ function console_draw_toast(s: string) {
 	g2_draw_string(s, x - g2_font_width(_g2_font, _g2_font_size, s) / 2, y + 40 * scale - g2_font_height(_g2_font, _g2_font_size) / 2);
 }
 
+function _console_toast_render(s: any) {
+	console_draw_toast(s);
+	krom_g4_swap_buffers();
+	app_remove_render_2d(_console_toast_render);
+}
+
 function console_toast(s: string) {
 	// Show a popup message
-	let _render = function () {
-		console_draw_toast(s);
-		base_notify_on_next_frame(function () {
-			app_remove_render_2d(_render);
-		});
-	}
-	app_notify_on_render_2d(_render);
+	app_notify_on_render_2d(_console_toast_render, s);
 	console_trace(s);
 }
 

+ 2 - 7
base/Sources/context.ts

@@ -560,10 +560,7 @@ function context_set_material(m: slot_material_t) {
 
 	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
 	if (decal) {
-		let _next = function () {
-			util_render_make_decal_preview();
-		}
-		base_notify_on_next_frame(_next);
+		app_notify_on_next_frame(util_render_make_decal_preview);
 	}
 }
 
@@ -871,9 +868,7 @@ function context_set_render_path() {
 	else {
 		render_path_commands = render_path_deferred_commands;
 	}
-	app_notify_on_init(function () {
-		make_material_parse_mesh_material();
-	});
+	app_notify_on_init(make_material_parse_mesh_material);
 }
 
 function context_enable_import_plugin(file: string): bool {

+ 3 - 3
base/Sources/export_arm.ts

@@ -182,7 +182,7 @@ function export_arm_run_project() {
 	///if (krom_metal || krom_vulkan)
 	export_arm_bgra_swap(mesh_icon_pixels);
 	///end
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function () {
 		image_unload(mesh_icon);
 	});
 	// raw.mesh_icons =
@@ -494,12 +494,12 @@ function export_arm_pack_assets(raw: project_format_t, assets: asset_t[]) {
 			});
 		}
 	}
-	base_notify_on_next_frame(function () {
+	app_notify_on_next_frame(function (temp_images: image_t[]) {
 		for (let i: i32 = 0; i < temp_images.length; ++i) {
 			let image: image_t = temp_images[i];
 			image_unload(image);
 		}
-	});
+	}, temp_images);
 }
 
 function export_arm_run_swatches(path: string) {

+ 58 - 19
base/Sources/file.ts

@@ -65,31 +65,60 @@ function file_exists(path: string): bool {
 	return krom_file_exists(path);
 }
 
-function file_download(url: string, dstPath: string, done: ()=>void, size: i32 = 0) {
+type file_download_data_t = {
+	dst_path: string;
+	done: (url: string)=>void;
+};
+
+let _file_download_map: map_t<string, file_download_data_t> = map_create();
+
+function file_download(url: string, dst_path: string, done: (url: string)=>void, size: i32 = 0) {
 	///if (krom_windows || krom_darwin || krom_ios || krom_android)
-	krom_http_request(url, size, function (ab: buffer_t) {
+	let fdd: file_download_data_t = { dst_path: dst_path, done: done };
+	map_set(_file_download_map, url, fdd);
+	krom_http_request(url, size, function (url: string, ab: buffer_t) {
+		let fdd: file_download_data_t = map_get(_file_download_map, url);
 		if (ab != null) {
-			krom_file_save_bytes(dstPath, ab);
+			krom_file_save_bytes(fdd.dst_path, ab);
 		}
-		done();
+		fdd.done(url);
 	});
 	///elseif krom_linux
-	krom_sys_command("wget -O \"" + dstPath + "\" \"" + url + "\"");
-	done();
+	krom_sys_command("wget -O \"" + dst_path + "\" \"" + url + "\"");
+	done(url);
 	///else
-	krom_sys_command("curl -L " + url + " -o \"" + dstPath + "\"");
-	done();
+	krom_sys_command("curl -L " + url + " -o \"" + dst_path + "\"");
+	done(url);
 	///end
 }
 
-function file_download_bytes(url: string, done: (ab: buffer_t)=>void) {
+type file_download_bytes_data_t = {
+	url: string;
+	save: string;
+	done: (url: string, ab: buffer_t)=>void;
+};
+
+let _file_download_bytes_map: map_t<string, file_download_bytes_data_t> = map_create();
+
+function file_download_bytes(url: string, done: (url: string, ab: buffer_t)=>void) {
 	let save: string = (path_is_protected() ? krom_save_path() : path_data() + path_sep) + "download.bin";
-	file_download(url, save, function () {
-		let buffer: buffer_t = krom_load_blob(save);
-		done(buffer);
+	let fdbd: file_download_bytes_data_t = { url: url, save: save, done: done };
+	map_set(_file_download_bytes_map, url, fdbd);
+	file_download(url, save, function (url: string) {
+		let fdbd: file_download_bytes_data_t = map_get(_file_download_bytes_map, url);
+		let buffer: buffer_t = krom_load_blob(fdbd.save);
+		fdbd.done(fdbd.url, buffer);
 	});
 }
 
+type file_cache_cloud_data_t = {
+	dest: string;
+	path: string;
+	done: (dest: string)=>void;
+};
+
+let _file_cache_cloud_map: map_t<string, file_cache_cloud_data_t> = map_create();
+
 function file_cache_cloud(path: string, done: (s: string)=>void) {
 	///if krom_ios
 	let path2: string = string_replace_all(path, "/", "_"); // Cache everything into root folder
@@ -114,22 +143,30 @@ function file_cache_cloud(path: string, done: (s: string)=>void) {
 	path = string_replace_all(path, "\\", "/");
 	///end
 	let url: string = config_raw.server + "/" + path;
-	file_download(url, dest, function () {
+
+	let fccd: file_cache_cloud_data_t = { dest: dest, path: path, done: done };
+	map_set(_file_cache_cloud_map, url, fccd);
+
+	file_download(url, dest, function (url: string) {
+		let fccd: file_cache_cloud_data_t = map_get(_file_cache_cloud_map, url);
 		if (!file_exists(dest)) {
 			console_error(strings_error5());
-			done(null);
+			fccd.done(null);
 			return;
 		}
 		///if (krom_darwin || krom_ios)
-		done(dest);
+		fccd.done(fccd.dest);
 		///else
-		done((path_is_protected() ? krom_save_path() : path_working_dir() + path_sep) + path);
+		fccd.done((path_is_protected() ? krom_save_path() : path_working_dir() + path_sep) + fccd.path);
 		///end
 	}, map_get(file_cloud_sizes, path));
 }
 
+let _file_init_cloud_bytes_done: ()=>void;
+
 function file_init_cloud_bytes(done: ()=>void, append: string = "") {
-	file_download_bytes(config_raw.server + "/?list-type=2" + append, function (buffer: buffer_t) {
+	_file_init_cloud_bytes_done = done;
+	file_download_bytes(config_raw.server + "/?list-type=2" + append, function (url: string, buffer: buffer_t) {
 		if (buffer == null) {
 			map_set(file_cloud, "cloud", []);
 			console_error(strings_error5());
@@ -184,9 +221,11 @@ function file_init_cloud_bytes(done: ()=>void, append: string = "") {
 			let pos_start: i32 = string_index_of(str, "<NextContinuationToken>");
 			pos_start += 23;
 			let pos_end: i32 = string_index_of_pos(str, "</NextContinuationToken>", pos_start);
-			file_init_cloud_bytes(done, "&start-after=" + substring(str, pos_start, pos_end));
+			file_init_cloud_bytes(_file_init_cloud_bytes_done, "&start-after=" + substring(str, pos_start, pos_end));
+		}
+		else {
+			_file_init_cloud_bytes_done();
 		}
-		else done();
 	});
 }
 

+ 34 - 35
base/Sources/history.ts

@@ -48,7 +48,8 @@ function history_undo() {
 
 			// Undo at least second time in order to avoid empty groups
 			if (step.layer_type == layer_slot_type_t.GROUP) {
-				base_notify_on_next_frame(function () {
+				app_notify_on_next_frame(function () {
+					let active: i32 = history_steps.length - 1 - history_redos;
 					// 1. Undo deleting group masks
 					let n: i32 = 1;
 					while (history_steps[active - n].layer_type == layer_slot_type_t.MASK) {
@@ -141,11 +142,10 @@ function history_undo() {
 			context_set_layer(context_raw.layer);
 		}
 		else if (step.name == tr("Invert Mask")) {
-			let _next = function () {
+			app_notify_on_init(function (step: step_t) {
 				context_raw.layer = project_layers[step.layer];
 				slot_layer_invert_mask(context_raw.layer);
-			}
-			app_notify_on_init(_next);
+			}, step);
 		}
 		else if (step.name == "Apply Filter") {
 			history_undo_i = history_undo_i - 1 < 0 ? config_raw.undo_steps - 1 : history_undo_i - 1;
@@ -247,20 +247,20 @@ function history_redo() {
 			let l: slot_layer_t = slot_layer_create("", step.layer_type, parent);
 			array_insert(project_layers, step.layer, l);
 			if (step.name == tr("New Black Mask")) {
-				base_notify_on_next_frame(function () {
+				app_notify_on_next_frame(function (l: slot_layer_t) {
 					slot_layer_clear(l, 0x00000000);
-				});
+				}, l);
 			}
 			else if (step.name == tr("New White Mask")) {
-				base_notify_on_next_frame(function () {
+				app_notify_on_next_frame(function (l: slot_layer_t) {
 					slot_layer_clear(l, 0xffffffff);
-				});
+				}, l);
 			}
 			else if (step.name == tr("New Fill Mask")) {
-				base_notify_on_next_frame(function () {
-					context_raw.material = project_materials[step.material];
+				context_raw.material = project_materials[step.material];
+				app_notify_on_next_frame(function (l: slot_layer_t) {
 					slot_layer_to_fill_layer(l);
-				});
+				}, l);
 			}
 			context_raw.layer_preview_dirty = true;
 			context_set_layer(l);
@@ -281,11 +281,13 @@ function history_redo() {
 			// Redoing the last delete would result in an empty group
 			// Redo deleting all group masks + the group itself
 			if (step.layer_type == layer_slot_type_t.LAYER && history_steps.length >= active + 2 && (history_steps[active + 1].layer_type == layer_slot_type_t.GROUP || history_steps[active + 1].layer_type == layer_slot_type_t.MASK)) {
-				let n: i32 = 1;
-				while (history_steps[active + n].layer_type == layer_slot_type_t.MASK) {
-					++n;
-				}
-				base_notify_on_next_frame(function () {
+				app_notify_on_next_frame(function () {
+					let active: i32 = history_steps.length - history_redos;
+					let n: i32 = 1;
+					while (history_steps[active + n].layer_type == layer_slot_type_t.MASK) {
+						++n;
+					}
+
 					for (let i: i32 = 0; i < n; ++i) {
 						history_redo();
 					}
@@ -300,10 +302,9 @@ function history_redo() {
 		}
 		else if (step.name == tr("Duplicate Layer")) {
 			context_raw.layer = project_layers[step.layer];
-			let _next = function () {
+			app_notify_on_next_frame(function () {
 				base_duplicate_layer(context_raw.layer);
-			}
-			base_notify_on_next_frame(_next);
+			});
 		}
 		else if (step.name == tr("Order Layers")) {
 			let target: slot_layer_t = project_layers[step.prev_order];
@@ -317,29 +318,27 @@ function history_redo() {
 		}
 		else if (step.name == tr("Apply Mask")) {
 			context_raw.layer = project_layers[step.layer];
-				if (slot_layer_is_group_mask(context_raw.layer)) {
-					let group: slot_layer_t = context_raw.layer.parent;
-					let layers: slot_layer_t[] = slot_layer_get_children(group);
-					array_insert(layers, 0, context_raw.layer);
-					history_copy_merging_layers2(layers);
-				}
-				else {
-					history_copy_merging_layers2([context_raw.layer, context_raw.layer.parent]);
-				}
-
-			let _next = function () {
+			if (slot_layer_is_group_mask(context_raw.layer)) {
+				let group: slot_layer_t = context_raw.layer.parent;
+				let layers: slot_layer_t[] = slot_layer_get_children(group);
+				array_insert(layers, 0, context_raw.layer);
+				history_copy_merging_layers2(layers);
+			}
+			else {
+				history_copy_merging_layers2([context_raw.layer, context_raw.layer.parent]);
+			}
+
+			app_notify_on_next_frame(function () {
 				slot_layer_apply_mask(context_raw.layer);
 				context_set_layer(context_raw.layer);
 				context_raw.layers_preview_dirty = true;
-			}
-			base_notify_on_next_frame(_next);
+			});
 		}
 		else if (step.name == tr("Invert Mask")) {
-			let _next = function () {
+			app_notify_on_init(function (step: step_t) {
 				context_raw.layer = project_layers[step.layer];
 				slot_layer_invert_mask(context_raw.layer);
-			}
-			app_notify_on_init(_next);
+			}, step);
 		}
 		else if (step.name == tr("Apply Filter")) {
 			let lay: slot_layer_t = history_undo_layers[history_undo_i];

+ 21 - 18
base/Sources/import_arm.ts

@@ -185,16 +185,16 @@ function import_arm_run_project(path: string) {
 		}
 		let rts: map_t<string, render_target_t> = render_path_render_targets;
 		let _texpaint_blend0: image_t = map_get(rts, "texpaint_blend0")._image;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_texpaint_blend0: image_t) {
 			image_unload(_texpaint_blend0);
-		});
+		}, _texpaint_blend0);
 		map_get(rts, "texpaint_blend0").width = config_get_texture_res_x();
 		map_get(rts, "texpaint_blend0").height = config_get_texture_res_y();
 		map_get(rts, "texpaint_blend0")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8, depth_format_t.NO_DEPTH);
 		let _texpaint_blend1: image_t = map_get(rts, "texpaint_blend1")._image;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_texpaint_blend1: image_t) {
 			image_unload(_texpaint_blend1);
-		});
+		}, _texpaint_blend1);
 		map_get(rts, "texpaint_blend1").width = config_get_texture_res_x();
 		map_get(rts, "texpaint_blend1").height = config_get_texture_res_y();
 		map_get(rts, "texpaint_blend1")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8, depth_format_t.NO_DEPTH);
@@ -295,17 +295,22 @@ function import_arm_run_project(path: string) {
 			l.paint_subs = ld.paint_subs;
 			///end
 
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function (_texpaint: image_t) {
 				image_unload(_texpaint);
-				///if is_paint
-				if (_texpaint_nor != null) {
+			}, _texpaint);
+
+			///if is_paint
+			if (_texpaint_nor != null) {
+				app_notify_on_next_frame(function (_texpaint_nor: image_t) {
 					image_unload(_texpaint_nor);
-				}
-				if (_texpaint_pack != null) {
+				}, _texpaint_nor);
+			}
+			if (_texpaint_pack != null) {
+				app_notify_on_next_frame(function (_texpaint_pack: image_t) {
 					image_unload(_texpaint_pack);
-				}
-				///end
-			});
+				}, _texpaint_pack);
+			}
+			///end
 		}
 	}
 
@@ -466,15 +471,14 @@ function import_arm_run_material_from_project(project: project_format_t, path: s
 		}
 	}
 
-	let _init = function () {
+	app_notify_on_init(function (imported: slot_material_t[]) {
 		for (let i: i32 = 0; i < imported.length; ++i) {
 			let m: slot_material_t = imported[i];
 			context_set_material(m);
 			make_material_parse_paint_material();
 			util_render_make_material_preview();
 		}
-	}
-	app_notify_on_init(_init);
+	}, imported);
 
 	ui_nodes_group_stack = [];
 	ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
@@ -556,14 +560,13 @@ function import_arm_run_brush_from_project(project: project_format_t, path: stri
 		array_push(imported, context_raw.brush);
 	}
 
-	let _init = function () {
+	app_notify_on_init(function (imported: slot_brush_t[]) {
 		for (let i: i32 = 0; i < imported.length; ++i) {
 			let b: slot_brush_t = imported[i];
 			context_set_brush(b);
 			util_render_make_brush_preview();
 		}
-	}
-	app_notify_on_init(_init);
+	}, imported);
 
 	ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
 	data_delete_blob(path);

+ 21 - 13
base/Sources/import_asset.ts

@@ -1,23 +1,31 @@
 
+let _import_asset_drop_x: f32;
+let _import_asset_drop_y: f32;
+let _import_asset_show_box: bool;
+let _import_asset_hdr_as_envmap: bool;
+let _import_asset_done: ()=>void;
+
 function import_asset_run(path: string, drop_x: f32 = -1.0, drop_y: f32 = -1.0, show_box: bool = true, hdr_as_envmap: bool = true, done: ()=>void = null) {
 
 	if (starts_with(path, "cloud")) {
-		let do_cache_cloud = function () {
-			file_cache_cloud(path, function (abs: string) {
-				if (abs == null) return;
-				import_asset_run(abs, drop_x, drop_y, show_box, hdr_as_envmap, done);
-			});
-		}
-
 		///if (krom_android || krom_ios)
-		base_notify_on_next_frame(function () {
-			console_toast(tr("Downloading"));
-			base_notify_on_next_frame(do_cache_cloud);
-		});
-		///else
-		do_cache_cloud();
+		console_toast(tr("Downloading"));
+		krom_g4_swap_buffers();
 		///end
 
+		_import_asset_drop_x = drop_x;
+		_import_asset_drop_y = drop_y;
+		_import_asset_show_box = show_box;
+		_import_asset_hdr_as_envmap = hdr_as_envmap;
+		_import_asset_done = done;
+
+		file_cache_cloud(path, function (abs: string) {
+			if (abs == null) {
+				return;
+			}
+			import_asset_run(abs, _import_asset_drop_x, _import_asset_drop_y, _import_asset_show_box, _import_asset_hdr_as_envmap, _import_asset_done);
+		});
+
 		return;
 	}
 

+ 2 - 3
base/Sources/import_blend_material.ts

@@ -291,15 +291,14 @@ function import_blend_material_run(path: string) {
 		history_new_material();
 	}
 
-	let _init = function () {
+	app_notify_on_init(function (imported: slot_material_t[]) {
 		for (let i: i32 = 0; i < imported.length; ++i) {
 			let m: slot_material_t = imported[i];
 			context_set_material(m);
 			make_material_parse_paint_material();
 			util_render_make_material_preview();
 		}
-	}
-	app_notify_on_init(_init);
+	}, imported);
 
 	ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
 	data_delete_blob(path);

+ 4 - 4
base/Sources/import_envmap.ts

@@ -48,9 +48,9 @@ function import_envmap_run(path: string, image: image_t) {
 	let radiance_pixels: buffer_t = image_get_pixels(import_envmap_radiance);
 	if (import_envmap_radiance_cpu != null) {
 		let _radiance_cpu: image_t = import_envmap_radiance_cpu;
-		base_notify_on_next_frame(function () {
+		app_notify_on_next_frame(function (_radiance_cpu: image_t) {
 			image_unload(_radiance_cpu);
-		});
+		}, _radiance_cpu);
 	}
 	import_envmap_radiance_cpu = image_from_bytes(radiance_pixels, import_envmap_radiance.width, import_envmap_radiance.height, tex_format_t.RGBA128);
 
@@ -58,11 +58,11 @@ function import_envmap_run(path: string, image: image_t) {
 	if (import_envmap_mips_cpu != null) {
 		for (let i: i32 = 0; i < import_envmap_mips_cpu.length; ++i) {
 			let mip: image_t = import_envmap_mips_cpu[i];
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function (mip: image_t) {
 				///if (!krom_direct3d12) // TODO: crashes after 50+ imports
 				image_unload(mip);
 				///end
-			});
+			}, mip);
 		}
 	}
 	import_envmap_mips_cpu = [];

+ 2 - 3
base/Sources/import_font.ts

@@ -22,15 +22,14 @@ function import_font_run(path: string) {
 		array_push(font_slots, font_slot);
 	}
 
-	let _init = function () {
+	app_notify_on_init(function (font_slots: slot_font_t[]) {
 		for (let i: i32 = 0; i < font_slots.length; ++i) {
 			let f: slot_font_t = font_slots[i];
 			context_raw.font = f;
 			array_push(project_fonts, f);
 			util_render_make_font_preview();
 		}
-	}
-	app_notify_on_init(_init);
+	}, font_slots);
 
 	ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
 }

+ 2 - 2
base/Sources/import_mesh.ts

@@ -70,7 +70,7 @@ function import_mesh_finish_import() {
 
 	if (project_paint_objects.length > 1) {
 		// Sort by name
-		array_sort(project_paint_objects, function (a, b): i32 {
+		array_sort(project_paint_objects, function (a: mesh_object_t, b: mesh_object_t): i32 {
 			if (a.base.name < b.base.name) {
 				return -1;
 			}
@@ -167,7 +167,7 @@ function _import_mesh_make_mesh(mesh: any) {
 
 	// Wait for addMesh calls to finish
 	if (import_mesh_meshes_to_unwrap != null) {
-		base_notify_on_next_frame(import_mesh_finish_import);
+		app_notify_on_next_frame(import_mesh_finish_import);
 	}
 	else {
 		app_notify_on_init(import_mesh_finish_import);

+ 13 - 6
base/Sources/import_texture.ts

@@ -1,4 +1,9 @@
 
+type import_texture_data = {
+	path: string;
+	image: image_t
+}
+
 function import_texture_run(path: string, hdr_as_envmap: bool = true) {
 	if (!path_is_texture(path)) {
 		if (!context_enable_import_plugin(path)) {
@@ -14,9 +19,10 @@ function import_texture_run(path: string, hdr_as_envmap: bool = true) {
 			// Set as envmap
 			if (hdr_as_envmap && ends_with(to_lower_case(path), ".hdr")) {
 				let image: image_t = data_get_image(path);
-				base_notify_on_next_frame(function () { // Make sure file browser process did finish
-					import_envmap_run(path, image);
-				});
+				let itd: import_texture_data = { path: path, image: image };
+				app_notify_on_next_frame(function (itd: import_texture_data) { // Make sure file browser process did finish
+					import_envmap_run(itd.path, itd.image);
+				}, itd);
 			}
 			console_info(strings_info0());
 			return;
@@ -46,9 +52,10 @@ function import_texture_run(path: string, hdr_as_envmap: bool = true) {
 
 	// Set as envmap
 	if (hdr_as_envmap && ends_with(to_lower_case(path), ".hdr")) {
-		base_notify_on_next_frame(function () { // Make sure file browser process did finish
-			import_envmap_run(path, image);
-		});
+		let itd: import_texture_data = { path: path, image: image };
+		app_notify_on_next_frame(function (itd: import_texture_data) { // Make sure file browser process did finish
+			import_envmap_run(itd.path, itd.image);
+		}, itd);
 	}
 }
 

+ 4 - 4
base/Sources/node_shader.ts

@@ -241,7 +241,7 @@ function node_shader_get_hlsl(raw: node_shader_t, shared_sampler: string): strin
 	if (raw.ins.length > 0) {
 		s += "struct SPIRV_Cross_Input {\n";
 		index = 0;
-		array_sort(raw.ins, function (a, b): i32 {
+		array_sort(raw.ins, function (a: string, b: string): i32 {
 			// Sort inputs by name
 			return substring(a, 4, a.length) >= substring(b, 4, b.length) ? 1 : -1;
 		});
@@ -266,7 +266,7 @@ function node_shader_get_hlsl(raw: node_shader_t, shared_sampler: string): strin
 	let num: i32 = 0;
 	if (raw.outs.length > 0 || raw.shader_type == "vert") {
 		s += "struct SPIRV_Cross_Output {\n";
-		array_sort(raw.outs, function (a, b): i32 {
+		array_sort(raw.outs, function (a: string, b: string): i32 {
 			// Sort outputs by name
 			return substring(a, 4, a.length) >= substring(b, 4, b.length) ? 1 : -1;
 		});
@@ -440,7 +440,7 @@ function node_shader_get_msl(raw: node_shader_t, shared_sampler: string): string
 	//if (ins.length > 0) {
 		s += "struct main_in {\n";
 		index = 0;
-		array_sort(raw.ins, function (a, b): i32 {
+		array_sort(raw.ins, function (a: string, b: string): i32 {
 			// Sort inputs by name
 			return substring(a, 4, a.length) >= substring(b, 4, b.length) ? 1 : -1;
 		});
@@ -465,7 +465,7 @@ function node_shader_get_msl(raw: node_shader_t, shared_sampler: string): string
 	let num: i32 = 0;
 	if (raw.outs.length > 0 || raw.shader_type == "vert") {
 		s += "struct main_out {\n";
-		array_sort(raw.outs, function (a, b): i32 {
+		array_sort(raw.outs, function (a: string, b: string): i32 {
 			// Sort outputs by name
 			return substring(a, 4, a.length) >= substring(b, 4, b.length) ? 1 : -1;
 		});

+ 11 - 0
base/Sources/nodes_material.ts

@@ -2970,9 +2970,20 @@ function nodes_material_group_output_button(ui: zui_t, nodes: zui_nodes_t, node:
 	nodes_material_add_socket_button(ui, nodes, node, node.inputs);
 }
 
+let _nodes_material_nodes: zui_nodes_t;
+let _nodes_material_node: zui_node_t;
+let _nodes_material_sockets: zui_node_socket_t[];
+
 function nodes_material_add_socket_button(ui: zui_t, nodes: zui_nodes_t, node: zui_node_t, sockets: zui_node_socket_t[]) {
 	if (zui_button(tr("Add"))) {
+		_nodes_material_nodes = nodes;
+		_nodes_material_node = node;
+		_nodes_material_sockets = sockets;
 		ui_menu_draw(function (ui: zui_t) {
+			let nodes: zui_nodes_t = _nodes_material_nodes;
+			let node: zui_node_t = _nodes_material_node;
+			let sockets: zui_node_socket_t[] = _nodes_material_sockets;
+
 			let group_stack: node_group_t[] = ui_nodes_group_stack;
 			let c: zui_node_canvas_t = group_stack[group_stack.length - 1].canvas;
 			if (ui_menu_button(ui, tr("RGBA"))) {

+ 44 - 34
base/Sources/parser_exr.ts

@@ -8,6 +8,42 @@ function parser_exr_write_string(out: i32[], str: string) {
 	}
 }
 
+let _parser_exr_width: i32;
+let _parser_exr_stride: i32;
+let _parser_exr_out: u8[];
+let _parser_exr_src_view: buffer_view_t;
+let _parser_exr_write_line: (byte_pos: i32)=> void;
+
+function parser_exr_write_line16(byte_pos: i32) {
+	for (let x: i32 = 0; x < _parser_exr_width; ++x) {
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos    ));
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos + 1));
+		byte_pos += _parser_exr_stride;
+	}
+}
+
+function parser_exr_write_line32(byte_pos: i32) {
+	for (let x: i32 = 0; x < _parser_exr_width; ++x) {
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos    ));
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos + 1));
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos + 2));
+		array_push(_parser_exr_out, buffer_view_get_u8(_parser_exr_src_view, byte_pos + 3));
+		byte_pos += _parser_exr_stride;
+	}
+}
+
+function parser_exr_write_bgr(off: i32, pos: i32, byte_size: i32) {
+	_parser_exr_write_line(pos + byte_size * 2);
+	_parser_exr_write_line(pos + byte_size);
+	_parser_exr_write_line(pos);
+}
+
+function parser_exr_write_single(off: i32, pos: i32, byte_size: i32) {
+	_parser_exr_write_line(pos + off * byte_size);
+	_parser_exr_write_line(pos + off * byte_size);
+	_parser_exr_write_line(pos + off * byte_size);
+}
+
 function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16, type: i32 = 1, off: i32 = 0): buffer_t {
 	let out: u8[] = [];
 	array_push(out, 0x76); // magic
@@ -266,39 +302,13 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	let pos: i32 = 0;
 	let src_view: buffer_view_t = buffer_view_create(src);
 
-	let write_line16 = function (byte_pos: i32) {
-		for (let x: i32 = 0; x < width; ++x) {
-			array_push(out, buffer_view_get_u8(src_view, byte_pos    ));
-			array_push(out, buffer_view_get_u8(src_view, byte_pos + 1));
-			byte_pos += stride;
-		}
-	}
-
-	let write_line32 = function (byte_pos: i32) {
-		for (let x: i32 = 0; x < width; ++x) {
-			array_push(out, buffer_view_get_u8(src_view, byte_pos    ));
-			array_push(out, buffer_view_get_u8(src_view, byte_pos + 1));
-			array_push(out, buffer_view_get_u8(src_view, byte_pos + 2));
-			array_push(out, buffer_view_get_u8(src_view, byte_pos + 3));
-			byte_pos += stride;
-		}
-	}
-
-	let write_line = bits == 16 ? write_line16 : write_line32;
-
-	let write_bgr = function (off: i32) {
-		write_line(pos + byte_size * 2);
-		write_line(pos + byte_size);
-		write_line(pos);
-	}
-
-	let write_single = function (off: i32) {
-		write_line(pos + off * byte_size);
-		write_line(pos + off * byte_size);
-		write_line(pos + off * byte_size);
-	}
+	_parser_exr_width = width;
+	_parser_exr_stride = stride;
+	_parser_exr_out = out;
+	_parser_exr_src_view = src_view;
 
-	let write_data = type == 1 ? write_bgr : write_single;
+	_parser_exr_write_line = bits == 16 ? parser_exr_write_line16 : parser_exr_write_line32;
+	let write_data: (off: i32, pos: i32, byte_size: i32)=>void = type == 1 ? parser_exr_write_bgr : parser_exr_write_single;
 
 	for (let y: i32 = 0; y < height; ++y) {
 		// coordinate
@@ -312,9 +322,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 		array_push(out, (pixel_row_size >> 16) & 0xff);
 		array_push(out, (pixel_row_size >> 24) & 0xff);
 		// data
-		write_data(off);
+		write_data(off, pos, byte_size);
 		pos += width * stride;
 	}
 
-	return u8_array_t.from(out).buffer;
+	return u8_array_create_from_array(out).buffer;
 }

+ 1 - 3
base/Sources/parser_logic.ts

@@ -230,9 +230,7 @@ function parser_logic_build_default_node(inp: zui_node_socket_t): logic_node_t {
 function parser_logic_create_node_instance(node_type: string, args: any): any {
 	if (map_get(parser_logic_custom_nodes, node_type) != null) {
 		let node: logic_node_t = logic_node_create();
-		node.get = function (from: i32) {
-			return map_get(parser_logic_custom_nodes, node_type)(node, from);
-		}
+		node.get = map_get(parser_logic_custom_nodes, node_type);
 		return node;
 	}
 

+ 1 - 1
base/Sources/physics_world.ts

@@ -20,7 +20,7 @@ let physics_world_v2: vec4_t = vec4_create();
 function physics_world_load(done: ()=>void) {
 	let b: buffer_t = krom_load_blob("data/plugins/ammo.js");
 	js_eval(sys_buffer_to_string(b));
-	let print = function (s: string) {
+	let print: (s: string)=>void = function (s: string) {
 		krom_log(s);
 	};
 	Ammo({print: print}).then(done);

+ 120 - 76
base/Sources/project.ts

@@ -42,6 +42,8 @@ function project_open() {
 	});
 }
 
+let _project_save_and_quit: bool;
+
 function project_save(save_and_quit: bool = false) {
 	if (project_filepath == "") {
 		///if krom_ios
@@ -61,14 +63,18 @@ function project_save(save_and_quit: bool = false) {
 	sys_title_set(filename + " - " + manifest_title);
 	///end
 
-	let _init = function () {
+	_project_save_and_quit = save_and_quit;
+
+	app_notify_on_init(function () {
 		export_arm_run_project();
-		if (save_and_quit) sys_stop();
-	}
-	app_notify_on_init(_init);
+		if (_project_save_and_quit) {
+			sys_stop();
+		}
+	});
 }
 
 function project_save_as(save_and_quit: bool = false) {
+	_project_save_and_quit = save_and_quit;
 	ui_files_show("arm", true, false, function (path: string) {
 		let f: string = ui_files_filename;
 		if (f == "") {
@@ -78,7 +84,7 @@ function project_save_as(save_and_quit: bool = false) {
 		if (!ends_with(project_filepath, ".arm")) {
 			project_filepath += ".arm";
 		}
-		project_save(save_and_quit);
+		project_save(_project_save_and_quit);
 	});
 }
 
@@ -175,7 +181,7 @@ function project_new(reset_layers: bool = true) {
 			raw = import_mesh_raw_mesh(mesh);
 
 			///if is_sculpt
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function (mesh: any) {
 				let f32a: f32_array_t = f32_array_create(config_get_texture_res_x() * config_get_texture_res_y() * 4);
 				for (let i: i32 = 0; i < math_floor(mesh.inda.length); ++i) {
 					let index: i32 = mesh.inda[i];
@@ -192,7 +198,7 @@ function project_new(reset_layers: bool = true) {
 				g2_draw_scaled_image(imgmesh, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
 				g2_set_pipeline(null);
 				g2_end();
-			});
+			}, mesh);
 			///end
 		}
 		else {
@@ -365,10 +371,7 @@ function project_import_brush() {
 			// Parse brush
 			make_material_parse_brush();
 			ui_nodes_hwnd.redraws = 2;
-			let _init = function () {
-				util_render_make_brush_preview();
-			}
-			app_notify_on_init(_init);
+			app_notify_on_init(util_render_make_brush_preview);
 		}
 		// Import from project file
 		else {
@@ -378,20 +381,41 @@ function project_import_brush() {
 }
 ///end
 
+let _project_import_mesh_replace_existing: bool;
+let _project_import_mesh_done: ()=>void;
+
 function project_import_mesh(replace_existing: bool = true, done: ()=>void = null) {
+	_project_import_mesh_replace_existing = replace_existing;
+	_project_import_mesh_done = done;
 	ui_files_show(path_mesh_formats.join(","), false, false, function (path: string) {
-		project_import_mesh_box(path, replace_existing, true, done);
+		project_import_mesh_box(path, _project_import_mesh_replace_existing, true, _project_import_mesh_done);
 	});
 }
 
+let _project_import_mesh_box_path: string;
+let _project_import_mesh_box_replace_existing: bool;
+let _project_import_mesh_box_clear_layers: bool;
+let _project_import_mesh_box_done: ()=>void;
+
 function project_import_mesh_box(path: string, replace_existing: bool = true, clear_layers: bool = true, done: ()=>void = null) {
 
+	_project_import_mesh_box_path = path;
+	_project_import_mesh_box_replace_existing = replace_existing;
+	_project_import_mesh_box_clear_layers = clear_layers;
+	_project_import_mesh_box_done = done;
+
 	///if krom_ios
 	// Import immediately while access to resource is unlocked
 	// data_get_blob(path);
 	///end
 
 	ui_box_show_custom(function (ui: zui_t) {
+
+		let path: string = _project_import_mesh_box_path;
+		let replace_existing: bool = _project_import_mesh_box_replace_existing;
+		let clear_layers: bool = _project_import_mesh_box_clear_layers;
+		let done: ()=>void = _project_import_mesh_box_done;
+
 		let tab_vertical: bool = config_raw.touch_ui;
 		if (zui_tab(zui_handle(__ID__), tr("Import Mesh"), tab_vertical)) {
 
@@ -428,31 +452,28 @@ function project_import_mesh_box(path: string, replace_existing: bool = true, cl
 			}
 			if (zui_button(tr("Import")) || ui.is_return_down) {
 				ui_box_hide();
-				let do_import = function () {
-					///if (is_paint || is_sculpt)
-					import_mesh_run(path, clear_layers, replace_existing);
-					///end
-					///if is_lab
-					import_mesh_run(path, replace_existing);
-					///end
-					if (done != null) {
-						done();
-					}
-				}
+
 				///if (krom_android || krom_ios)
-				base_notify_on_next_frame(function () {
-					console_toast(tr("Importing mesh"));
-					base_notify_on_next_frame(do_import);
-				});
-				///else
-				do_import();
+				console_toast(tr("Importing mesh"));
+				krom_g4_swap_buffers();
+				///end
+
+				///if (is_paint || is_sculpt)
+				import_mesh_run(path, clear_layers, replace_existing);
+				///end
+				///if is_lab
+				import_mesh_run(path, replace_existing);
 				///end
+				if (done != null) {
+					done();
+				}
 			}
 			if (zui_button(tr("?"))) {
 				file_load_url("https://github.com/armory3d/armorpaint_docs/blob/master/faq.md");
 			}
 		}
 	});
+
 	ui_box_click_to_hide = false; // Prevent closing when going back to window from file browser
 }
 
@@ -465,8 +486,22 @@ function project_reimport_mesh() {
 	}
 }
 
+let _project_unwrap_mesh_box_mesh: any;
+let _project_unwrap_mesh_box_done: (a: any)=>void;
+let _project_unwrap_mesh_box_skip_ui: bool;
+
 function project_unwrap_mesh_box(mesh: any, done: (a: any)=>void, skip_ui: bool = false) {
+
+	_project_unwrap_mesh_box_mesh = mesh;
+	_project_unwrap_mesh_box_done = done;
+	_project_unwrap_mesh_box_skip_ui = skip_ui;
+
 	ui_box_show_custom(function (ui: zui_t) {
+
+		let mesh: any = _project_unwrap_mesh_box_mesh;
+		let done: (a: any)=>void = _project_unwrap_mesh_box_done;
+		let skip_ui: bool = _project_unwrap_mesh_box_skip_ui;
+
 		let tab_vertical: bool = config_raw.touch_ui;
 		if (zui_tab(zui_handle(__ID__), tr("Unwrap Mesh"), tab_vertical)) {
 
@@ -490,49 +525,53 @@ function project_unwrap_mesh_box(mesh: any, done: (a: any)=>void, skip_ui: bool
 			}
 			if (zui_button(tr("Unwrap")) || ui.is_return_down || skip_ui) {
 				ui_box_hide();
-				let do_unwrap = function () {
-					if (unwrap_by == unwrap_plugins.length - 1) {
-						util_mesh_equirect_unwrap(mesh);
-					}
-					else {
-						let f: string = unwrap_plugins[unwrap_by];
-						if (array_index_of(config_raw.plugins, f) == -1) {
-							config_enable_plugin(f);
-							console_info(f + " " + tr("plugin enabled"));
-						}
-						map_get(util_mesh_unwrappers, f)(mesh);
-					}
-					done(mesh);
-				}
+
 				///if (krom_android || krom_ios)
-				base_notify_on_next_frame(function () {
-					console_toast(tr("Unwrapping mesh"));
-					base_notify_on_next_frame(do_unwrap);
-				});
-				///else
-				do_unwrap();
+				console_toast(tr("Unwrapping mesh"));
+				krom_g4_swap_buffers();
 				///end
+
+				if (unwrap_by == unwrap_plugins.length - 1) {
+					util_mesh_equirect_unwrap(mesh);
+				}
+				else {
+					let f: string = unwrap_plugins[unwrap_by];
+					if (array_index_of(config_raw.plugins, f) == -1) {
+						config_enable_plugin(f);
+						console_info(f + " " + tr("plugin enabled"));
+					}
+					map_get(util_mesh_unwrappers, f)(mesh);
+				}
+				done(mesh);
 			}
 		}
 	});
 }
 
+let _project_import_asset_hdr_as_envmap: bool;
+
 function project_import_asset(filters: string = null, hdr_as_envmap: bool = true) {
 	if (filters == null) {
 		filters = path_texture_formats.join(",") + "," + path_mesh_formats.join(",");
 	}
+
+	_project_import_asset_hdr_as_envmap = hdr_as_envmap;
+
 	ui_files_show(filters, false, true, function (path: string) {
-		import_asset_run(path, -1.0, -1.0, true, hdr_as_envmap);
+		import_asset_run(path, -1.0, -1.0, true, _project_import_asset_hdr_as_envmap);
 	});
 }
 
+let _project_import_swatches_replace_existing: bool;
+
 function project_import_swatches(replace_existing: bool = false) {
+	_project_import_swatches_replace_existing = replace_existing;
 	ui_files_show("arm,gpl", false, false, function (path: string) {
 		if (path_is_gimp_color_palette(path)) {
-			import_gpl_run(path, replace_existing);
+			import_gpl_run(path, _project_import_swatches_replace_existing);
 		}
 		else {
-			import_arm_run_swatches(path, replace_existing);
+			import_arm_run_swatches(path, _project_import_swatches_replace_existing);
 		}
 	});
 }
@@ -544,41 +583,46 @@ function project_reimport_textures() {
 	}
 }
 
-function project_reimport_texture(asset: asset_t) {
-	let load = function (path: string) {
-		asset.file = path;
-		let i: i32 = array_index_of(project_assets, asset);
-		data_delete_image(asset.file);
-		map_delete(project_asset_map, asset.id);
-		let old_asset: asset_t = project_assets[i];
-		array_splice(project_assets, i, 1);
-		array_splice(project_asset_names, i, 1);
-		import_texture_run(asset.file);
-		array_insert(project_assets, i, project_assets.pop());
-		array_insert(project_asset_names, i, project_asset_names.pop());
+function project_reimport_texture_load(path: string, asset: asset_t) {
+	asset.file = path;
+	let i: i32 = array_index_of(project_assets, asset);
+	data_delete_image(asset.file);
+	map_delete(project_asset_map, asset.id);
+	let old_asset: asset_t = project_assets[i];
+	array_splice(project_assets, i, 1);
+	array_splice(project_asset_names, i, 1);
+	import_texture_run(asset.file);
+	array_insert(project_assets, i, project_assets.pop());
+	array_insert(project_asset_names, i, project_asset_names.pop());
+
+	///if (is_paint || is_sculpt)
+	if (context_raw.texture == old_asset) {
+		context_raw.texture = project_assets[i];
+	}
+	///end
+
+	app_notify_on_next_frame(function () {
+		make_material_parse_paint_material();
 
 		///if (is_paint || is_sculpt)
-		if (context_raw.texture == old_asset) context_raw.texture = project_assets[i];
+		util_render_make_material_preview();
+		ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
 		///end
+	});
+}
 
-		let _next = function () {
-			make_material_parse_paint_material();
+let _project_reimport_texture_asset: asset_t;
 
-			///if (is_paint || is_sculpt)
-			util_render_make_material_preview();
-			ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
-			///end
-		}
-		base_notify_on_next_frame(_next);
-	}
+function project_reimport_texture(asset: asset_t) {
 	if (!file_exists(asset.file)) {
 		let filters: string = path_texture_formats.join(",");
+		_project_reimport_texture_asset = asset;
 		ui_files_show(filters, false, false, function (path: string) {
-			load(path);
+			project_reimport_texture_load(path, _project_reimport_texture_asset);
 		});
 	}
 	else {
-		load(asset.file);
+		project_reimport_texture_load(asset.file, asset);
 	}
 }
 

+ 1 - 4
base/Sources/render_path_raytrace_bake.ts

@@ -58,10 +58,7 @@ function render_path_raytrace_bake_commands(parse_paint_material: (b?: bool)=>vo
 		render_path_set_target("baketex0", ["baketex1"]);
 		render_path_draw_meshes("paint");
 		context_raw.bake_type = _bake_type;
-		let _next = function () {
-			parse_paint_material();
-		}
-		base_notify_on_next_frame(_next);
+		app_notify_on_next_frame(parse_paint_material);
 
 		render_path_raytrace_raytrace_init(render_path_raytrace_bake_get_bake_shader_name(), rebuild);
 

+ 87 - 67
base/Sources/tab_browser.ts

@@ -10,6 +10,9 @@ function tab_browser_show_directory(directory: string) {
 	ui_base_htabs[tab_area_t.STATUS].position = 0;
 }
 
+let _tab_browser_draw_file: string;
+let _tab_browser_draw_b: string;
+
 function tab_browser_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
@@ -85,87 +88,103 @@ function tab_browser_draw(htab: zui_handle_t) {
 		let _y: f32 = ui._y;
 		ui._x = bookmarks_w;
 		ui._w -= bookmarks_w;
+
 		ui_files_file_browser(ui, tab_browser_hpath, false, true, tab_browser_hsearch.text, refresh, function (file: string) {
+
 			let file_name: string = substring(file, string_last_index_of(file, path_sep) + 1, file.length);
-			if (file_name != "..") {
-				ui_menu_draw(function (ui: zui_t) {
-					if (ui_menu_button(ui, tr("Import"))) {
-						import_asset_run(file);
-					}
-					if (path_is_texture(file)) {
-						if (ui_menu_button(ui, tr("Set as Envmap"))) {
-							import_asset_run(file, -1.0, -1.0, true, true, function () {
-								base_notify_on_next_frame(function () {
-									let asset_index: i32 = -1;
-									for (let i: i32 = 0; i < project_assets.length; ++i) {
-										if (project_assets[i].file == file) {
-											asset_index = i;
-											break;
-										}
-									}
-									if (asset_index != -1) {
-										import_envmap_run(file, project_get_image(project_assets[asset_index]));
+			if (file_name == "..") {
+				return;
+			}
+
+			_tab_browser_draw_file = file;
+
+			// Context menu
+			ui_menu_draw(function (ui: zui_t) {
+				let file: string = _tab_browser_draw_file;
+
+				if (ui_menu_button(ui, tr("Import"))) {
+					import_asset_run(file);
+				}
+				if (path_is_texture(file)) {
+					if (ui_menu_button(ui, tr("Set as Envmap"))) {
+
+						import_asset_run(file, -1.0, -1.0, true, true, function () {
+							app_notify_on_next_frame(function () {
+								let file: string = _tab_browser_draw_file;
+
+								let asset_index: i32 = -1;
+								for (let i: i32 = 0; i < project_assets.length; ++i) {
+									if (project_assets[i].file == file) {
+										asset_index = i;
+										break;
 									}
-								});
+								}
+								if (asset_index != -1) {
+									import_envmap_run(file, project_get_image(project_assets[asset_index]));
+								}
 							});
-						}
-
-						///if (is_paint || is_sculpt)
-						if (ui_menu_button(ui, tr("Set as Mask"))) {
-							import_asset_run(file, -1.0, -1.0, true, true, function () {
-								base_notify_on_next_frame(function () {
-									let asset_index: i32 = -1;
-									for (let i: i32 = 0; i < project_assets.length; ++i) {
-										if (project_assets[i].file == file) {
-											asset_index = i;
-											break;
-										}
-									}
-									if (asset_index != -1) {
-										base_create_image_mask(project_assets[asset_index]);
+						});
+					}
+
+					///if (is_paint || is_sculpt)
+					if (ui_menu_button(ui, tr("Set as Mask"))) {
+						import_asset_run(file, -1.0, -1.0, true, true, function () {
+							app_notify_on_next_frame(function () {
+								let file: string = _tab_browser_draw_file;
+
+								let asset_index: i32 = -1;
+								for (let i: i32 = 0; i < project_assets.length; ++i) {
+									if (project_assets[i].file == file) {
+										asset_index = i;
+										break;
 									}
-								});
+								}
+								if (asset_index != -1) {
+									base_create_image_mask(project_assets[asset_index]);
+								}
 							});
-						}
-						///end
-
-						///if is_paint
-						if (ui_menu_button(ui, tr("Set as Color ID Map"))) {
-							import_asset_run(file, -1.0, -1.0, true, true, function () {
-								base_notify_on_next_frame(function () {
-									let asset_index: i32 = -1;
-									for (let i: i32 = 0; i < project_assets.length; ++i) {
-										if (project_assets[i].file == file) {
-											asset_index = i;
-											break;
-										}
+						});
+					}
+					///end
+
+					///if is_paint
+					if (ui_menu_button(ui, tr("Set as Color ID Map"))) {
+						import_asset_run(file, -1.0, -1.0, true, true, function () {
+							app_notify_on_next_frame(function () {
+								let file: string = _tab_browser_draw_file;
+
+								let asset_index: i32 = -1;
+								for (let i: i32 = 0; i < project_assets.length; ++i) {
+									if (project_assets[i].file == file) {
+										asset_index = i;
+										break;
 									}
-									if (asset_index != -1) {
-										context_raw.colorid_handle.position = asset_index;
-										context_raw.colorid_picked = false;
-										ui_toolbar_handle.redraws = 1;
-										if (context_raw.tool == workspace_tool_t.COLORID) {
-											ui_header_handle.redraws = 2;
-											context_raw.ddirty = 2;
-										}
+								}
+								if (asset_index != -1) {
+									context_raw.colorid_handle.position = asset_index;
+									context_raw.colorid_picked = false;
+									ui_toolbar_handle.redraws = 1;
+									if (context_raw.tool == workspace_tool_t.COLORID) {
+										ui_header_handle.redraws = 2;
+										context_raw.ddirty = 2;
 									}
-								});
+								}
 							});
-						}
-						///end
-					}
-					if (ui_menu_button(ui, tr("Open Externally"))) {
-						file_start(file);
+						});
 					}
-				}, path_is_texture(file) ? 5 : 2);
-			}
+					///end
+				}
+				if (ui_menu_button(ui, tr("Open Externally"))) {
+					file_start(file);
+				}
+			}, path_is_texture(file) ? 5 : 2);
 		});
 
 		if (tab_browser_known) {
 			let path: string = tab_browser_hpath.text;
-			app_notify_on_init(function () {
+			app_notify_on_init(function (path: string) {
 				import_asset_run(path);
-			});
+			}, path);
 			tab_browser_hpath.text = substring(tab_browser_hpath.text, 0, string_last_index_of(tab_browser_hpath.text, path_sep));
 		}
 		tab_browser_known = string_index_of(substring(tab_browser_hpath.text, string_last_index_of(tab_browser_hpath.text, path_sep), tab_browser_hpath.text.length), ".") > 0;
@@ -214,9 +233,10 @@ function tab_browser_draw(htab: zui_handle_t) {
 			}
 
 			if (ui.is_hovered && ui.input_released_r) {
+				_tab_browser_draw_b = b;
 				ui_menu_draw(function (ui: zui_t) {
 					if (ui_menu_button(ui, tr("Delete"))) {
-						array_remove(config_raw.bookmarks, b);
+						array_remove(config_raw.bookmarks, _tab_browser_draw_b);
 						config_save();
 					}
 				}, 1);

+ 16 - 4
base/Sources/tab_brushes.ts

@@ -1,6 +1,8 @@
 
 ///if (is_paint || is_sculpt)
 
+let _tab_brushes_draw_i: i32;
+
 function tab_brushes_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	if (zui_tab(htab, tr("Brushes"))) {
@@ -81,8 +83,13 @@ function tab_brushes_draw(htab: zui_handle_t) {
 				if (ui.is_hovered && ui.input_released_r) {
 					context_select_brush(i);
 					let add: i32 = project_brushes.length > 1 ? 1 : 0;
+
+					_tab_brushes_draw_i = i;
+
 					ui_menu_draw(function (ui: zui_t) {
-						//let b: SlotBrushRaw = brushes[i];
+						let i: i32 = _tab_brushes_draw_i;
+
+						//let b: slot_brush_t = brushes[i];
 
 						if (ui_menu_button(ui, tr("Export"))) {
 							context_select_brush(i);
@@ -90,15 +97,16 @@ function tab_brushes_draw(htab: zui_handle_t) {
 						}
 
 						if (ui_menu_button(ui, tr("Duplicate"))) {
-							let _init = function () {
+							app_notify_on_init(function () {
+								let i: i32 = _tab_brushes_draw_i;
+
 								context_raw.brush = slot_brush_create();
 								array_push(project_brushes, context_raw.brush);
 								let cloned: any = json_parse(json_stringify(project_brushes[i].canvas));
 								context_raw.brush.canvas = cloned;
 								context_set_brush(context_raw.brush);
 								util_render_make_brush_preview();
-							}
-							app_notify_on_init(_init);
+							});
 						}
 
 						if (project_brushes.length > 1 && ui_menu_button(ui, tr("Delete"), "delete")) {
@@ -109,7 +117,11 @@ function tab_brushes_draw(htab: zui_handle_t) {
 
 				if (ui.is_hovered) {
 					if (img_full == null) {
+						_tab_brushes_draw_i = i;
+
 						app_notify_on_init(function () {
+							let i: i32 = _tab_brushes_draw_i;
+
 							let _brush: slot_brush_t = context_raw.brush;
 							context_raw.brush = project_brushes[i];
 							make_material_parse_brush();

+ 1 - 1
base/Sources/tab_console.ts

@@ -29,8 +29,8 @@ function tab_console_draw(htab: zui_handle_t) {
 			console_last_traces = [];
 		}
 		if (zui_button(tr("Export"))) {
-			let str: string = console_last_traces.join("\n");
 			ui_files_show("txt", true, false, function (path: string) {
+				let str: string = console_last_traces.join("\n");
 				let f: string = ui_files_filename;
 				if (f == "") {
 					f = tr("untitled");

+ 20 - 7
base/Sources/tab_fonts.ts

@@ -1,6 +1,8 @@
 
 ///if (is_paint || is_sculpt)
 
+let _tab_fonts_draw_i: i32;
+
 function tab_fonts_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
@@ -88,10 +90,13 @@ function tab_fonts_draw(htab: zui_handle_t) {
 
 				if (state == zui_state_t.STARTED) {
 					if (context_raw.font != project_fonts[i]) {
-						let _init = function () {
+						_tab_fonts_draw_i = i;
+
+						app_notify_on_init(function () {
+							let i: i32 = _tab_fonts_draw_i;
+
 							context_select_font(i);
-						}
-						app_notify_on_init(_init);
+						});
 					}
 					if (time_time() - context_raw.select_time < 0.25) {
 						ui_base_show_2d_view(view_2d_type_t.FONT);
@@ -101,7 +106,11 @@ function tab_fonts_draw(htab: zui_handle_t) {
 				if (ui.is_hovered && ui.input_released_r) {
 					context_select_font(i);
 					let add: i32 = project_fonts.length > 1 ? 1 : 0;
+					_tab_fonts_draw_i = i;
+
 					ui_menu_draw(function (ui: zui_t) {
+						let i: i32 = _tab_fonts_draw_i;
+
 						if (project_fonts.length > 1 && ui_menu_button(ui, tr("Delete"), "delete") && project_fonts[i].file != "") {
 							tab_fonts_delete_font(project_fonts[i]);
 						}
@@ -109,7 +118,11 @@ function tab_fonts_draw(htab: zui_handle_t) {
 				}
 				if (ui.is_hovered) {
 					if (img == null) {
+						_tab_fonts_draw_i = i;
+
 						app_notify_on_init(function () {
+							let i: i32 = _tab_fonts_draw_i;
+
 							let _font: slot_font_t = context_raw.font;
 							context_raw.font = project_fonts[i];
 							util_render_make_font_preview();
@@ -149,13 +162,13 @@ function tab_fonts_draw(htab: zui_handle_t) {
 }
 
 function tab_fonts_delete_font(font: slot_font_t) {
-	let i: i32 = array_index_of(project_fonts, font);
-	let _init = function () {
+	app_notify_on_init(function (font: slot_font_t) {
+		let i: i32 = array_index_of(project_fonts, font);
 		context_select_font(i == project_fonts.length - 1 ? i - 1 : i + 1);
 		data_delete_font(project_fonts[i].file);
 		array_splice(project_fonts, i, 1);
-	}
-	app_notify_on_init(_init);
+	}, font);
+
 	ui_base_hwnds[2].redraws = 2;
 }
 

+ 9 - 7
base/Sources/tab_materials.ts

@@ -47,6 +47,8 @@ function tab_materials_button_nodes() {
 	}
 }
 
+let _tab_materials_draw_slots: i32;
+
 function tab_materials_draw_slots(mini: bool) {
 	let ui: zui_t = ui_base_ui;
 	let slotw: i32 = math_floor(51 * zui_SCALE(ui));
@@ -131,10 +133,7 @@ function tab_materials_draw_slots(mini: bool) {
 					context_select_material(i);
 					///if is_paint
 					if (context_raw.tool == workspace_tool_t.MATERIAL) {
-						let _init = function () {
-							base_update_fill_layers();
-						}
-						app_notify_on_init(_init);
+						app_notify_on_init(base_update_fill_layers);
 					}
 					///end
 				}
@@ -154,8 +153,10 @@ function tab_materials_draw_slots(mini: bool) {
 			if (ui.is_hovered && ui.input_released_r) {
 				context_select_material(i);
 				let add: i32 = project_materials.length > 1 ? 1 : 0;
+				_tab_materials_draw_slots = i;
 
 				ui_menu_draw(function (ui: zui_t) {
+					let i: i32 = _tab_materials_draw_slots;
 					let m: slot_material_t = project_materials[i];
 
 					if (ui_menu_button(ui, tr("To Fill Layer"))) {
@@ -176,15 +177,16 @@ function tab_materials_draw_slots(mini: bool) {
 					///end
 
 					if (ui_menu_button(ui, tr("Duplicate"))) {
-						let _init = function () {
+						app_notify_on_init(function () {
+							let i: i32 = _tab_materials_draw_slots;
+
 							context_raw.material = slot_material_create(project_materials[0].data);
 							array_push(project_materials, context_raw.material);
 							let cloned: zui_node_canvas_t = json_parse(json_stringify(project_materials[i].canvas));
 							context_raw.material.canvas = cloned;
 							tab_materials_update_material();
 							history_duplicate_material();
-						}
-						app_notify_on_init(_init);
+						});
 					}
 
 					if (project_materials.length > 1 && ui_menu_button(ui, tr("Delete"), "delete")) {

+ 8 - 0
base/Sources/tab_meshes.ts

@@ -1,4 +1,6 @@
 
+let _tab_meshes_draw_i: i32;
+
 function tab_meshes_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
@@ -118,8 +120,14 @@ function tab_meshes_draw(htab: zui_handle_t) {
 			let h: zui_handle_t = zui_handle(__ID__);
 			h.selected = o.base.visible;
 			o.base.visible = zui_check(h, o.base.name);
+
 			if (ui.is_hovered && ui.input_released_r) {
+				_tab_meshes_draw_i = i;
+
 				ui_menu_draw(function (ui: zui_t) {
+					let i: i32 = _tab_meshes_draw_i;
+					let o: mesh_object_t = project_paint_objects[i];
+
 					if (ui_menu_button(ui, tr("Export"))) {
 						context_raw.export_mesh_index = i + 1;
 						box_export_show_mesh();

+ 1 - 1
base/Sources/tab_script.ts

@@ -28,8 +28,8 @@ function tab_script_draw(htab: zui_handle_t) {
 			});
 		}
 		if (zui_button(tr("Export"))) {
-			let str: string = tab_script_hscript.text;
 			ui_files_show("js", true, false, function (path: string) {
+				let str: string = tab_script_hscript.text;
 				let f: string = ui_files_filename;
 				if (f == "") {
 					f = tr("untitled");

+ 13 - 0
base/Sources/tab_swatches.ts

@@ -18,6 +18,8 @@ function tab_swatches_empty_get(): image_t {
 	return _tab_swatches_empty;
 }
 
+let _tab_swatches_draw_i: i32;
+
 function tab_swatches_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
@@ -130,6 +132,9 @@ function tab_swatches_draw(htab: zui_handle_t) {
 				}
 				else if (state == zui_state_t.RELEASED) {
 					if (time_time() - context_raw.select_time < 0.25) {
+
+						_tab_swatches_draw_i = i;
+
 						ui_menu_draw(function (ui: zui_t) {
 							ui.changed = false;
 							let h: zui_handle_t = zui_handle(__ID__);
@@ -138,10 +143,14 @@ function tab_swatches_draw(htab: zui_handle_t) {
 							context_raw.swatch.base = zui_color_wheel(h, false, null, 11 * ui.t.ELEMENT_H * zui_SCALE(ui), true, function () {
 								context_raw.color_picker_previous_tool = context_raw.tool;
 								context_select_tool(workspace_tool_t.PICKER);
+
 								context_raw.color_picker_callback = function (color: swatch_color_t) {
+									let i: i32 = _tab_swatches_draw_i;
+
 									project_raw.swatches[i] = project_clone_swatch(color);
 								};
 							});
+
 							let hopacity: zui_handle_t = zui_handle(__ID__);
 							hopacity.value = context_raw.swatch.opacity;
 							context_raw.swatch.opacity = zui_slider(hopacity, "Opacity", 0, 1, true);
@@ -183,7 +192,11 @@ function tab_swatches_draw(htab: zui_handle_t) {
 					add += 1;
 					///end
 
+					_tab_swatches_draw_i = i;
+
 					ui_menu_draw(function (ui: zui_t) {
+						let i: i32 = _tab_swatches_draw_i;
+
 						if (ui_menu_button(ui, tr("Duplicate"))) {
 							context_set_swatch(project_clone_swatch(context_raw.swatch));
 							array_push(project_raw.swatches, context_raw.swatch);

+ 22 - 9
base/Sources/tab_textures.ts

@@ -1,4 +1,8 @@
 
+let _tab_textures_draw_img: image_t;
+let _tab_textures_draw_path: string;
+let _tab_textures_draw_asset: asset_t;
+
 function tab_textures_draw(htab: zui_handle_t) {
 	let ui: zui_t = ui_base_ui;
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
@@ -118,10 +122,16 @@ function tab_textures_draw(htab: zui_handle_t) {
 						count = is_packed ? 6 : 6;
 						///end
 
+						_tab_textures_draw_img = img;
+
 						ui_menu_draw(function (ui: zui_t) {
 							if (ui_menu_button(ui, tr("Export"))) {
 								ui_files_show("png", true, false, function (path: string) {
-									base_notify_on_next_frame(function () {
+									_tab_textures_draw_path = path;
+
+									app_notify_on_next_frame(function () {
+										let img: image_t = _tab_textures_draw_img;
+
 										///if (is_paint || is_sculpt)
 										if (base_pipe_merge == null) base_make_pipe();
 										///end
@@ -135,7 +145,8 @@ function tab_textures_draw(htab: zui_handle_t) {
 										g2_draw_scaled_image(img, 0, 0, target.width, target.height);
 										g2_set_pipeline(null);
 										g2_end();
-										base_notify_on_next_frame(function () {
+										app_notify_on_next_frame(function () {
+											let path: string = _tab_textures_draw_path;
 											let f: string = ui_files_filename;
 											if (f == "") {
 												f = tr("untitled");
@@ -155,15 +166,18 @@ function tab_textures_draw(htab: zui_handle_t) {
 
 							///if (is_paint || is_sculpt)
 							if (ui_menu_button(ui, tr("To Mask"))) {
-								base_notify_on_next_frame(function () {
-									base_create_image_mask(asset);
+								_tab_textures_draw_asset = asset;
+								app_notify_on_next_frame(function () {
+									base_create_image_mask(_tab_textures_draw_asset);
 								});
 							}
 							///end
 
 							if (ui_menu_button(ui, tr("Set as Envmap"))) {
-								base_notify_on_next_frame(function () {
-									import_envmap_run(asset.file, img);
+								_tab_textures_draw_asset = asset;
+								_tab_textures_draw_img = img;
+								app_notify_on_next_frame(function () {
+									import_envmap_run(_tab_textures_draw_asset.file, _tab_textures_draw_img);
 								});
 							}
 
@@ -269,15 +283,14 @@ function tab_textures_delete_texture(asset: asset_t) {
 	map_delete(project_asset_map, asset.id);
 	array_splice(project_assets, i, 1);
 	array_splice(project_asset_names, i, 1);
-	let _next = function () {
+	app_notify_on_next_frame(function () {
 		make_material_parse_paint_material();
 
 		///if (is_paint || is_sculpt)
 		util_render_make_material_preview();
 		ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
 		///end
-	}
-	base_notify_on_next_frame(_next);
+	});
 
 	for (let i: i32 = 0; i < project_materials.length; ++i) {
 		let m: slot_material_t = project_materials[i];

+ 18 - 0
base/Sources/translator.ts

@@ -101,6 +101,10 @@ function translator_load_translations(new_locale: string) {
 		let cjk_font_disk_path: string = (path_is_protected() ? krom_save_path() : path_data() + path_sep) + "font_cjk.ttc";
 		if (!file_exists(cjk_font_disk_path)) {
 			file_download("https://github.com/armory3d/armorbase/raw/main/Assets/common/extra/font_cjk.ttc", cjk_font_disk_path, function () {
+
+				let cjk_font_path: string = (path_is_protected() ? krom_save_path() : "") + "font_cjk.ttc";
+				let cjk_font_disk_path: string = (path_is_protected() ? krom_save_path() : path_data() + path_sep) + "font_cjk.ttc";
+
 				if (!file_exists(cjk_font_disk_path)) {
 					// Fall back to English
 					config_raw.locale = "en";
@@ -122,12 +126,26 @@ function translator_load_translations(new_locale: string) {
 	}
 }
 
+let _translator_init_font_cjk: bool;
+let _translator_init_font_font_path: string;
+let _translator_init_font_font_scale: f32;
+
 function translator_init_font(cjk: bool, font_path: string, font_scale: f32) {
 	array_sort(_g2_font_glyphs, function (a: i32, b: i32) {
 		return a - b;
 	});
+
+	_translator_init_font_cjk = cjk;
+	_translator_init_font_font_path = font_path;
+	_translator_init_font_font_scale = font_scale;
+
 	// Load and assign font with cjk characters
 	app_notify_on_init(function () {
+
+		let cjk: bool = _translator_init_font_cjk;
+		let font_path: string = _translator_init_font_font_path;
+		let font_scale: f32 = _translator_init_font_font_scale;
+
 		let f: g2_font_t = data_get_font(font_path);
 		if (cjk) {
 			let acjk_font_indices: any = translator_cjk_font_indices as any;

+ 14 - 9
base/Sources/ui_base.ts

@@ -292,10 +292,9 @@ function ui_base_update() {
 			box_export_show_textures();
 		}
 		else {
-			let _init = function () {
+			app_notify_on_init(function () {
 				export_texture_run(context_raw.texture_export_path);
-			}
-			app_notify_on_init(_init);
+			});
 		}
 	}
 	else if (operator_shortcut(config_keymap.file_export_textures_as)) {
@@ -823,7 +822,9 @@ function ui_base_update() {
 			let ray: ray_t = raycast_get_ray(mouse_view_x(), mouse_view_y(), camera);
 			physics_body_apply_impulse(body, vec4_mult(ray.dir, 0.15));
 
-			context_raw.particle_timer = tween_timer(5, function () { mesh_object_remove(mo); });
+			context_raw.particle_timer = tween_timer(5, function () {
+				mesh_object_remove(mo);
+			});
 		}
 
 		let pairs: pair_t[] = physics_world_get_contact_pairs(world, context_raw.paint_body);
@@ -854,15 +855,19 @@ function ui_base_view_top() {
 	}
 }
 
+let _ui_base_operator_search_first: bool;
+
 function ui_base_operator_search() {
-	let search_handle: zui_handle_t = zui_handle(__ID__);
-	let first: bool = true;
+	_ui_base_operator_search_first = true;
+
 	ui_menu_draw(function (ui: zui_t) {
+		let search_handle: zui_handle_t = zui_handle(__ID__);
+
 		zui_fill(0, 0, ui._w / zui_SCALE(ui), ui.t.ELEMENT_H * 8, ui.t.SEPARATOR_COL);
 		let search: string = zui_text_input(search_handle, "", zui_align_t.LEFT, true, true);
 		ui.changed = false;
-		if (first) {
-			first = false;
+		if (_ui_base_operator_search_first) {
+			_ui_base_operator_search_first = false;
 			search_handle.text = "";
 			zui_start_text_edit(search_handle); // Focus search bar
 		}
@@ -1165,7 +1170,7 @@ function ui_base_update_ui() {
 		///if is_paint
 		// New color id picked, update fill layer
 		if (context_raw.tool == workspace_tool_t.COLORID && context_raw.layer.fill_layer != null) {
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function () {
 				base_update_fill_layer();
 				make_material_parse_paint_material(false);
 			});

+ 24 - 10
base/Sources/ui_files.ts

@@ -61,6 +61,10 @@ function ui_files_release_keys() {
 	///end
 }
 
+let _ui_files_file_browser_handle: zui_handle_t;
+let _ui_files_file_browser_f: string;
+let _ui_files_file_browser_shandle: string;
+
 function ui_files_file_browser(ui: zui_t, handle: zui_handle_t, folders_only: bool = false, drag_files: bool = false, search: string = "", refresh: bool = false, context_menu: (s: string)=>void = null): string {
 
 	let icons: image_t = resource_get("icons.k");
@@ -176,14 +180,19 @@ function ui_files_file_browser(ui: zui_t, handle: zui_handle_t, folders_only: bo
 					if (array_index_of(files_all, icon_file) >= 0) {
 						let empty: image_t = map_get(render_path_render_targets, "empty_black")._image;
 						map_set(ui_files_icon_map, handle.text + path_sep + f, empty);
+
+						_ui_files_file_browser_handle = handle;
+						_ui_files_file_browser_f = f;
+
 						file_cache_cloud(handle.text + path_sep + icon_file, function (abs: string) {
 							if (abs != null) {
 								let image: image_t = data_get_image(abs);
-								app_notify_on_init(function () {
+
+								app_notify_on_init(function (image: image_t) {
 									if (base_pipe_copy_rgb == null) {
 										base_make_pipe_copy_rgb();
 									}
-									icon = image_create_render_target(image.width, image.height);
+									let icon: image_t = image_create_render_target(image.width, image.height);
 									if (ends_with(f, ".arm")) { // Used for material sphere alpha cutout
 										g2_begin(icon);
 
@@ -199,9 +208,9 @@ function ui_files_file_browser(ui: zui_t, handle: zui_handle_t, folders_only: bo
 									g2_draw_image(image, 0, 0);
 									g2_set_pipeline(null);
 									g2_end();
-									map_set(ui_files_icon_map, handle.text + path_sep + f, icon);
+									map_set(ui_files_icon_map, _ui_files_file_browser_handle.text + path_sep + _ui_files_file_browser_f, icon);
 									ui_base_hwnds[tab_area_t.STATUS].redraws = 3;
-								});
+								}, image);
 							}
 							else {
 								ui_files_offline = true;
@@ -295,21 +304,26 @@ function ui_files_file_browser(ui: zui_t, handle: zui_handle_t, folders_only: bo
 					let empty: image_t = map_get(render_path_render_targets, "empty_black")._image;
 					map_set(ui_files_icon_map, shandle, empty);
 					let image: image_t = data_get_image(shandle);
-					app_notify_on_init(function () {
-						if (base_pipe_copy_rgb == null) base_make_pipe_copy_rgb();
+
+					_ui_files_file_browser_shandle = shandle;
+
+					app_notify_on_init(function (image: image_t) {
+						if (base_pipe_copy_rgb == null) {
+							base_make_pipe_copy_rgb();
+						}
 						let sw: i32 = image.width > image.height ? w : math_floor(1.0 * image.width / image.height * w);
 						let sh: i32 = image.width > image.height ? math_floor(1.0 * image.height / image.width * w) : w;
-						icon = image_create_render_target(sw, sh);
+						let icon: image_t = image_create_render_target(sw, sh);
 						g2_begin(icon);
 						g2_clear(0xffffffff);
 						g2_set_pipeline(base_pipe_copy_rgb);
 						g2_draw_scaled_image(image, 0, 0, sw, sh);
 						g2_set_pipeline(null);
 						g2_end();
-						map_set(ui_files_icon_map, shandle, icon);
+						map_set(ui_files_icon_map, _ui_files_file_browser_shandle, icon);
 						ui_base_hwnds[tab_area_t.STATUS].redraws = 3;
-						data_delete_image(shandle); // The big image is not needed anymore
-					});
+						data_delete_image(_ui_files_file_browser_shandle); // The big image is not needed anymore
+					}, image);
 				}
 				if (icon != null) {
 					if (i == ui_files_selected) {

+ 7 - 5
base/Sources/ui_header.ts

@@ -37,6 +37,8 @@ function ui_header_render_ui() {
 
 ///if is_paint
 
+let _ui_header_draw_tool_properties_h: zui_handle_t;
+
 function ui_header_draw_tool_properties(ui: zui_t) {
 	if (context_raw.tool == workspace_tool_t.COLORID) {
 		zui_text(tr("Picked Color"));
@@ -86,7 +88,7 @@ function ui_header_draw_tool_properties(ui: zui_t) {
 				context_set_layer(context_raw.layer.parent);
 			}
 			let m: slot_layer_t = base_new_mask(false, context_raw.layer);
-			let _next = function () {
+			app_notify_on_next_frame(function (m: slot_layer_t) {
 				if (base_pipe_merge == null) {
 					base_make_pipe();
 				}
@@ -106,8 +108,7 @@ function ui_header_draw_tool_properties(ui: zui_t) {
 				ui_header_handle.redraws = 1;
 				context_raw.layer_preview_dirty = true;
 				base_update_fill_layers();
-			}
-			base_notify_on_next_frame(_next);
+			}, m);
 			history_new_white_mask();
 		}
 		ui.enabled = true;
@@ -143,10 +144,11 @@ function ui_header_draw_tool_properties(ui: zui_t) {
 			zui_tooltip(tr("Drag and drop picked color to swatches, materials, layers or to the node editor"));
 		}
 		if (ui.is_hovered && ui.input_released) {
+			_ui_header_draw_tool_properties_h = h;
 			ui_menu_draw(function (ui: zui_t) {
 				zui_fill(0, 0, ui._w / zui_SCALE(ui), ui.t.ELEMENT_H * 9, ui.t.SEPARATOR_COL);
 				ui.changed = false;
-				zui_color_wheel(h, false, null, 10 * ui.t.ELEMENT_H * zui_SCALE(ui), false);
+				zui_color_wheel(_ui_header_draw_tool_properties_h, false, null, 10 * ui.t.ELEMENT_H * zui_SCALE(ui), false);
 				if (ui.changed) {
 					ui_menu_keep_open = true;
 				}
@@ -193,7 +195,7 @@ function ui_header_draw_tool_properties(ui: zui_t) {
 		if (!baking && zui_button(tr("Bake"))) {
 			context_raw.pdirty = rt_bake ? context_raw.bake_samples : 1;
 			context_raw.rdirty = 3;
-			base_notify_on_next_frame(function () {
+			app_notify_on_next_frame(function () {
 				context_raw.layer_preview_dirty = true;
 			});
 			ui_base_hwnds[0].redraws = 2;

+ 7 - 3
base/Sources/ui_menu.ts

@@ -11,6 +11,8 @@ let ui_menu_commands: (ui: zui_t)=>void = null;
 let ui_menu_show_first: bool = true;
 let ui_menu_hide_flag: bool = false;
 
+let _ui_menu_render_msg: string;
+
 function ui_menu_render() {
 	let ui: zui_t = base_ui_menu;
 	let menu_w: i32 = ui_menu_commands != null ? math_floor(base_default_element_w * zui_SCALE(base_ui_menu) * 2.3) : math_floor(zui_ELEMENT_W(ui) * 2.3);
@@ -501,7 +503,7 @@ function ui_menu_render() {
 				file_load_url(manifest_url_ios);
 				///else
 				// Retrieve latest version number
-				file_download_bytes("https://server.armorpaint.org/" + to_lower_case(manifest_title) + ".html", function (buffer: buffer_t) {
+				file_download_bytes("https://server.armorpaint.org/" + to_lower_case(manifest_title) + ".html", function (url: string, buffer: buffer_t) {
 					if (buffer != null)  {
 						// Compare versions
 						let update: any = json_parse(sys_buffer_to_string(buffer));
@@ -558,6 +560,8 @@ function ui_menu_render() {
 				// { lshw -C display }
 				///end
 
+				_ui_menu_render_msg = msg;
+
 				ui_box_show_custom(function (ui: zui_t) {
 					let tab_vertical: bool = config_raw.touch_ui;
 					if (zui_tab(zui_handle(__ID__), tr("About"), tab_vertical)) {
@@ -566,13 +570,13 @@ function ui_menu_render() {
 						zui_image(img);
 						zui_end_element();
 
-						zui_text_area(zui_handle(__ID__, { text: msg }), zui_align_t.LEFT, false);
+						zui_text_area(zui_handle(__ID__, { text: _ui_menu_render_msg }), zui_align_t.LEFT, false);
 
 						zui_row([1 / 3, 1 / 3, 1 / 3]);
 
 						///if (krom_windows || krom_linux || krom_darwin)
 						if (zui_button(tr("Copy"))) {
-							krom_copy_to_clipboard(msg);
+							krom_copy_to_clipboard(_ui_menu_render_msg);
 						}
 						///else
 						zui_end_element();

+ 1 - 1
base/Sources/ui_menubar.ts

@@ -42,7 +42,7 @@ function ui_menubar_render_ui() {
 				console_toast(tr("Saving project"));
 				project_save();
 				///end
-				base_notify_on_next_frame(function () {
+				app_notify_on_next_frame(function () {
 					box_projects_show();
 				});
 			}

+ 95 - 54
base/Sources/ui_nodes.ts

@@ -46,6 +46,8 @@ function ui_nodes_init() {
 	ui_nodes_ui.scroll_enabled = false;
 }
 
+let _ui_nodes_on_link_drag_link_drag: zui_node_link_t;
+
 function ui_nodes_on_link_drag(link_drag_id: i32, is_new_link: bool) {
 	if (is_new_link) {
 		let nodes: zui_nodes_t = ui_nodes_get_nodes();
@@ -61,7 +63,12 @@ function ui_nodes_on_link_drag(link_drag_id: i32, is_new_link: bool) {
 			link_y += zui_nodes_INPUT_Y(ui_nodes_get_canvas(true), node.inputs, link_drag.to_socket) + zui_nodes_OUTPUTS_H(node.outputs) + zui_nodes_BUTTONS_H(node);
 		}
 		if (math_abs(mouse_x - link_x) > 5 || math_abs(mouse_y - link_y) > 5) { // Link length
+
+			_ui_nodes_on_link_drag_link_drag = link_drag;
+
 			ui_nodes_node_search(-1, -1, function () {
+				let link_drag: zui_node_link_t = _ui_nodes_on_link_drag_link_drag;
+
 				let n: zui_node_t = zui_get_node(ui_nodes_get_canvas(true).nodes, nodes.nodes_selected_id[0]);
 				if (link_drag.to_id == -1 && n.inputs.length > 0) {
 					link_drag.to_id = n.id;
@@ -99,6 +106,18 @@ function ui_nodes_on_link_drag(link_drag_id: i32, is_new_link: bool) {
 	}
 }
 
+let _ui_nodes_on_socket_released_socket: zui_node_socket_t;
+let _ui_nodes_on_socket_released_node: zui_node_t;
+
+let _ui_nodes_htype: zui_handle_t = zui_handle_create();
+let _ui_nodes_hname: zui_handle_t = zui_handle_create();
+let _ui_nodes_hmin: zui_handle_t = zui_handle_create();
+let _ui_nodes_hmax: zui_handle_t = zui_handle_create();
+let _ui_nodes_hval0: zui_handle_t = zui_handle_create();
+let _ui_nodes_hval1: zui_handle_t = zui_handle_create();
+let _ui_nodes_hval2: zui_handle_t = zui_handle_create();
+let _ui_nodes_hval3: zui_handle_t = zui_handle_create();
+
 function ui_nodes_on_socket_released(socket_id: i32) {
 	let nodes: zui_nodes_t = ui_nodes_get_nodes();
 	let canvas: zui_node_canvas_t = ui_nodes_get_canvas(true);
@@ -106,61 +125,67 @@ function ui_nodes_on_socket_released(socket_id: i32) {
 	let node: zui_node_t = zui_get_node(canvas.nodes, socket.node_id);
 	if (ui_nodes_ui.input_released_r) {
 		if (node.type == "GROUP_INPUT" || node.type == "GROUP_OUTPUT") {
-			base_notify_on_next_frame(function () {
+
+			_ui_nodes_on_socket_released_socket = socket;
+			_ui_nodes_on_socket_released_node = node;
+
+			app_notify_on_next_frame(function () {
 				ui_menu_draw(function (ui: zui_t) {
+
+					let socket: zui_node_socket_t = _ui_nodes_on_socket_released_socket;
+					let node: zui_node_t = _ui_nodes_on_socket_released_node;
+
 					if (ui_menu_button(ui, tr("Edit"))) {
-						let htype: zui_handle_t = zui_handle(__ID__);
-						let hname: zui_handle_t = zui_handle(__ID__);
-						let hmin: zui_handle_t = zui_handle(__ID__);
-						let hmax: zui_handle_t = zui_handle(__ID__);
-						let hval0: zui_handle_t = zui_handle(__ID__);
-						let hval1: zui_handle_t = zui_handle(__ID__);
-						let hval2: zui_handle_t = zui_handle(__ID__);
-						let hval3: zui_handle_t = zui_handle(__ID__);
-						htype.position = socket.type == "RGBA" ? 0 : socket.type == "VECTOR" ? 1 : 2;
-						hname.text = socket.name;
-						hmin.value = socket.min;
-						hmax.value = socket.max;
+						_ui_nodes_htype.position = socket.type == "RGBA" ? 0 : socket.type == "VECTOR" ? 1 : 2;
+						_ui_nodes_hname.text = socket.name;
+						_ui_nodes_hmin.value = socket.min;
+						_ui_nodes_hmax.value = socket.max;
 						if (socket.type == "RGBA" || socket.type == "VECTOR") {
-							hval0.value = socket.default_value[0];
-							hval1.value = socket.default_value[1];
-							hval2.value = socket.default_value[2];
+							_ui_nodes_hval0.value = socket.default_value[0];
+							_ui_nodes_hval1.value = socket.default_value[1];
+							_ui_nodes_hval2.value = socket.default_value[2];
 							if (socket.type == "RGBA") {
-								hval3.value = socket.default_value[3];
+								_ui_nodes_hval3.value = socket.default_value[3];
 							}
 						}
 						else {
-							hval0.value = socket.default_value;
+							_ui_nodes_hval0.value = socket.default_value;
 						}
-						base_notify_on_next_frame(function () {
+
+						app_notify_on_next_frame(function () {
 							zui_end_input();
+
 							ui_box_show_custom(function (ui: zui_t) {
+
+								let socket: zui_node_socket_t = _ui_nodes_on_socket_released_socket;
+								let node: zui_node_t = _ui_nodes_on_socket_released_node;
+
 								if (zui_tab(zui_handle(__ID__), tr("Socket"))) {
-									let type: i32 = zui_combo(htype, [tr("Color"), tr("Vector"), tr("Value")], tr("Type"), true);
-									if (htype.changed) {
-										hname.text = type == 0 ? tr("Color") : type == 1 ? tr("Vector") : tr("Value");
+									let type: i32 = zui_combo(_ui_nodes_htype, [tr("Color"), tr("Vector"), tr("Value")], tr("Type"), true);
+									if (_ui_nodes_htype.changed) {
+										_ui_nodes_hname.text = type == 0 ? tr("Color") : type == 1 ? tr("Vector") : tr("Value");
 									}
-									let name: string = zui_text_input(hname, tr("Name"));
-									let min: f32 = zui_float_input(hmin, tr("Min"));
-									let max: f32 = zui_float_input(hmax, tr("Max"));
+									let name: string = zui_text_input(_ui_nodes_hname, tr("Name"));
+									let min: f32 = zui_float_input(_ui_nodes_hmin, tr("Min"));
+									let max: f32 = zui_float_input(_ui_nodes_hmax, tr("Max"));
 									let default_value: any = null;
 									if (type == 0) {
 										zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-										zui_float_input(hval0, tr("R"));
-										zui_float_input(hval1, tr("G"));
-										zui_float_input(hval2, tr("B"));
-										zui_float_input(hval3, tr("A"));
-										default_value = f32_array_create_xyzw(hval0.value, hval1.value, hval2.value, hval3.value);
+										zui_float_input(_ui_nodes_hval0, tr("R"));
+										zui_float_input(_ui_nodes_hval1, tr("G"));
+										zui_float_input(_ui_nodes_hval2, tr("B"));
+										zui_float_input(_ui_nodes_hval3, tr("A"));
+										default_value = f32_array_create_xyzw(_ui_nodes_hval0.value, _ui_nodes_hval1.value, _ui_nodes_hval2.value, _ui_nodes_hval3.value);
 									}
 									else if (type == 1) {
 										zui_row([1 / 3, 1 / 3, 1 / 3]);
-										hval0.value = zui_float_input(hval0, tr("X"));
-										hval1.value = zui_float_input(hval1, tr("Y"));
-										hval2.value = zui_float_input(hval2, tr("Z"));
-										default_value = f32_array_create_xyz(hval0.value, hval1.value, hval2.value);
+										_ui_nodes_hval0.value = zui_float_input(_ui_nodes_hval0, tr("X"));
+										_ui_nodes_hval1.value = zui_float_input(_ui_nodes_hval1, tr("Y"));
+										_ui_nodes_hval2.value = zui_float_input(_ui_nodes_hval2, tr("Z"));
+										default_value = f32_array_create_xyz(_ui_nodes_hval0.value, _ui_nodes_hval1.value, _ui_nodes_hval2.value);
 									}
 									else {
-										default_value = zui_float_input(hval0, tr("default_value"));
+										default_value = zui_float_input(_ui_nodes_hval0, tr("default_value"));
 									}
 									if (zui_button(tr("OK"))) { // || ui.isReturnDown
 										socket.name = name;
@@ -212,6 +237,8 @@ function ui_nodes_on_socket_released(socket_id: i32) {
 	}
 }
 
+let _ui_nodes_on_canvas_released_selected: zui_node_t;
+
 function ui_nodes_on_canvas_released() {
 	if (ui_nodes_ui.input_released_r && math_abs(ui_nodes_ui.input_x - ui_nodes_ui.input_started_x) < 2 && math_abs(ui_nodes_ui.input_y - ui_nodes_ui.input_started_y) < 2) {
 		// Node selection
@@ -242,18 +269,23 @@ function ui_nodes_on_canvas_released() {
 				++number_of_entries;
 			}
 
+			_ui_nodes_on_canvas_released_selected = selected;
+
 			ui_menu_draw(function (ui_menu: zui_t) {
+
+				let selected: zui_node_t = _ui_nodes_on_canvas_released_selected;
+
 				ui_menu._y += 1;
 				let is_protected: bool = selected == null ||
-								///if (is_paint || is_sculpt)
-								selected.type == "OUTPUT_MATERIAL_PBR" ||
-								///end
-								selected.type == "GROUP_INPUT" ||
-								selected.type == "GROUP_OUTPUT" ||
-								selected.type == "brush_output_node";
+					///if (is_paint || is_sculpt)
+					selected.type == "OUTPUT_MATERIAL_PBR" ||
+					///end
+					selected.type == "GROUP_INPUT" ||
+					selected.type == "GROUP_OUTPUT" ||
+					selected.type == "brush_output_node";
 				ui_menu.enabled = !is_protected;
 				if (ui_menu_button(ui_menu, tr("Cut"), "ctrl+x")) {
-					base_notify_on_next_frame(function () {
+					app_notify_on_next_frame(function () {
 						ui_nodes_hwnd.redraws = 2;
 						zui_set_is_copy(true);
 						zui_set_is_cut(true);
@@ -261,14 +293,14 @@ function ui_nodes_on_canvas_released() {
 					});
 				}
 				if (ui_menu_button(ui_menu, tr("Copy"), "ctrl+c")) {
-					base_notify_on_next_frame(function () {
+					app_notify_on_next_frame(function () {
 						zui_set_is_copy(true);
 						ui_nodes_is_node_menu_op = true;
 					});
 				}
 				ui_menu.enabled = zui_clipboard != "";
 				if (ui_menu_button(ui_menu, tr("Paste"), "ctrl+v")) {
-					base_notify_on_next_frame(function () {
+					app_notify_on_next_frame(function () {
 						ui_nodes_hwnd.redraws = 2;
 						zui_set_is_paste(true);
 						ui_nodes_is_node_menu_op = true;
@@ -276,14 +308,14 @@ function ui_nodes_on_canvas_released() {
 				}
 				ui_menu.enabled = !is_protected;
 				if (ui_menu_button(ui_menu, tr("Delete"), "delete")) {
-					base_notify_on_next_frame(function () {
+					app_notify_on_next_frame(function () {
 						ui_nodes_hwnd.redraws = 2;
 						ui_nodes_ui.is_delete_down = true;
 						ui_nodes_is_node_menu_op = true;
 					});
 				}
 				if (ui_menu_button(ui_menu, tr("Duplicate"))) {
-					base_notify_on_next_frame(function () {
+					app_notify_on_next_frame(function () {
 						ui_nodes_hwnd.redraws = 2;
 						zui_set_is_copy(true);
 						zui_set_is_paste(true);
@@ -532,18 +564,24 @@ function ui_nodes_canvas_changed() {
 	ui_nodes_recompile_mat_final = true;
 }
 
+let _ui_nodes_node_search_first: bool;
+let _ui_nodes_node_search_done: ()=>void;
+
 function ui_nodes_node_search(x: i32 = -1, y: i32 = -1, done: ()=>void = null) {
-	let search_handle: zui_handle_t = zui_handle(__ID__);
-	let first: bool = true;
+	_ui_nodes_node_search_first = true;
+	_ui_nodes_node_search_done = done;
+
 	ui_menu_draw(function (ui: zui_t) {
+		let search_handle: zui_handle_t = zui_handle(__ID__);
+
 		g2_set_color(ui.t.SEPARATOR_COL);
 		zui_draw_rect(true, ui._x, ui._y, ui._w, zui_ELEMENT_H(ui) * 8);
 		g2_set_color(0xffffffff);
 
 		let search: string = to_lower_case(zui_text_input(search_handle, "", zui_align_t.LEFT, true, true));
 		ui.changed = false;
-		if (first) {
-			first = false;
+		if (_ui_nodes_node_search_first) {
+			_ui_nodes_node_search_first = false;
 			search_handle.text = "";
 			zui_start_text_edit(search_handle); // Focus search bar
 		}
@@ -595,8 +633,8 @@ function ui_nodes_node_search(x: i32 = -1, y: i32 = -1, done: ()=>void = null) {
 							ui.changed = true;
 							count = 6; // Trigger break
 						}
-						if (done != null) {
-							done();
+						if (_ui_nodes_node_search_done != null) {
+							_ui_nodes_node_search_done();
 						}
 					}
 					if (++count > 6) {
@@ -668,6 +706,8 @@ function ui_nodes_draw_grid() {
 	g2_end();
 }
 
+let _ui_nodes_render_tmp: (col: i32)=>void;
+
 function ui_nodes_render() {
 	if (ui_nodes_recompile_mat) {
 		///if (is_paint || is_sculpt)
@@ -825,9 +865,10 @@ function ui_nodes_render() {
 		if (nodes.color_picker_callback != null) {
 			context_raw.color_picker_previous_tool = context_raw.tool;
 			context_select_tool(workspace_tool_t.PICKER);
-			let tmp: (col: i32)=>void = nodes.color_picker_callback;
+			_ui_nodes_render_tmp = nodes.color_picker_callback;
+
 			context_raw.color_picker_callback = function (color: swatch_color_t) {
-				tmp(color.base);
+				_ui_nodes_render_tmp(color.base);
 				ui_nodes_hwnd.redraws = 2;
 
 				///if (is_paint || is_sculpt)

+ 62 - 53
base/Sources/ui_toolbar.ts

@@ -27,6 +27,45 @@ let ui_toolbar_tool_names: string[] = [
 function ui_toolbar_init() {
 }
 
+let _ui_toolbar_i: i32;
+
+function ui_toolbar_draw_tool(i: i32, ui: zui_t, img: image_t, icon_accent: i32, keys: string[]) {
+	ui._x += 2;
+	if (context_raw.tool == i) {
+		ui_toolbar_draw_highlight();
+	}
+	let tile_y: i32 = math_floor(i / 12);
+	let tile_x: i32 = tile_y % 2 == 0 ? i % 12 : (11 - (i % 12));
+	let rect: rect_t = resource_tile50(img, tile_x, tile_y);
+	let _y: i32 = ui._y;
+
+	let image_state: zui_state_t = zui_image(img, icon_accent, -1.0, rect.x, rect.y, rect.w, rect.h);
+	if (image_state == zui_state_t.STARTED) {
+		_ui_toolbar_i = i;
+		app_notify_on_next_frame(function() {
+			context_select_tool(_ui_toolbar_i);
+		});
+	}
+	else if (image_state == zui_state_t.RELEASED && config_raw.layout[layout_size_t.HEADER] == 0) {
+		if (ui_toolbar_last_tool == i) {
+			ui_toolbar_tool_properties_menu();
+		}
+		ui_toolbar_last_tool = i;
+	}
+
+	///if is_paint
+	if (i == workspace_tool_t.COLORID && context_raw.colorid_picked) {
+		g2_draw_scaled_sub_image(map_get(render_path_render_targets, "texpaint_colorid")._image, 0, 0, 1, 1, 0, _y + 1.5 * zui_SCALE(ui), 5 * zui_SCALE(ui), 34 * zui_SCALE(ui));
+	}
+	///end
+
+	if (ui.is_hovered) {
+		zui_tooltip(tr(ui_toolbar_tool_names[i]) + " " + keys[i]);
+	}
+	ui._x -= 2;
+	ui._y += 2;
+}
+
 function ui_toolbar_render_ui() {
 	let ui: zui_t = ui_base_ui;
 
@@ -114,58 +153,24 @@ function ui_toolbar_render_ui() {
 			"(" + config_keymap.tool_material + ")",
 		];
 
-		let draw_tool = function (i: i32) {
-			ui._x += 2;
-			if (context_raw.tool == i) {
-				ui_toolbar_draw_highlight();
-			}
-			let tile_y: i32 = math_floor(i / 12);
-			let tile_x: i32 = tile_y % 2 == 0 ? i % 12 : (11 - (i % 12));
-			let rect: rect_t = resource_tile50(img, tile_x, tile_y);
-			let _y: i32 = ui._y;
-
-			let image_state: zui_state_t = zui_image(img, icon_accent, -1.0, rect.x, rect.y, rect.w, rect.h);
-			if (image_state == zui_state_t.STARTED) {
-				context_select_tool(i);
-			}
-			else if (image_state == zui_state_t.RELEASED && config_raw.layout[layout_size_t.HEADER] == 0) {
-				if (ui_toolbar_last_tool == i) {
-					ui_toolbar_tool_properties_menu();
-				}
-				ui_toolbar_last_tool = i;
-			}
-
-			///if is_paint
-			if (i == workspace_tool_t.COLORID && context_raw.colorid_picked) {
-				g2_draw_scaled_sub_image(map_get(render_path_render_targets, "texpaint_colorid")._image, 0, 0, 1, 1, 0, _y + 1.5 * zui_SCALE(ui), 5 * zui_SCALE(ui), 34 * zui_SCALE(ui));
-			}
-			///end
-
-			if (ui.is_hovered) {
-				zui_tooltip(tr(ui_toolbar_tool_names[i]) + " " + keys[i]);
-			}
-			ui._x -= 2;
-			ui._y += 2;
-		}
-
-		draw_tool(workspace_tool_t.BRUSH);
+		ui_toolbar_draw_tool(workspace_tool_t.BRUSH, ui, img, icon_accent, keys);
 		///if is_paint
-		draw_tool(workspace_tool_t.ERASER);
-		draw_tool(workspace_tool_t.FILL);
-		draw_tool(workspace_tool_t.DECAL);
-		draw_tool(workspace_tool_t.TEXT);
-		draw_tool(workspace_tool_t.CLONE);
-		draw_tool(workspace_tool_t.BLUR);
-		draw_tool(workspace_tool_t.SMUDGE);
-		draw_tool(workspace_tool_t.PARTICLE);
-		draw_tool(workspace_tool_t.COLORID);
-		draw_tool(workspace_tool_t.PICKER);
-		draw_tool(workspace_tool_t.BAKE);
-		draw_tool(workspace_tool_t.MATERIAL);
+		ui_toolbar_draw_tool(workspace_tool_t.ERASER, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.FILL, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.DECAL, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.TEXT, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.CLONE, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.BLUR, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.SMUDGE, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.PARTICLE, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.COLORID, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.PICKER, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.BAKE, ui, img, icon_accent, keys);
+		ui_toolbar_draw_tool(workspace_tool_t.MATERIAL, ui, img, icon_accent, keys);
 		///end
 
 		///if is_forge
-		draw_tool(workspace_tool_t.GIZMO);
+		ui_toolbar_draw_tool(workspace_tool_t.GIZMO, ui, img, icon_accent, keys);
 		///end
 
 		ui.image_scroll_align = true;
@@ -180,11 +185,15 @@ function ui_toolbar_render_ui() {
 	}
 }
 
+let _ui_toolbar_tool_properties_menu_x: i32;
+let _ui_toolbar_tool_properties_menu_y: i32;
+let _ui_toolbar_tool_properties_menu_w: i32;
+
 function ui_toolbar_tool_properties_menu() {
 	let ui: zui_t = ui_base_ui;
-	let _x: i32 = ui._x;
-	let _y: i32 = ui._y;
-	let _w: i32 = ui._w;
+	_ui_toolbar_tool_properties_menu_x = ui._x;
+	_ui_toolbar_tool_properties_menu_y = ui._y;
+	_ui_toolbar_tool_properties_menu_w = ui._w;
 	ui_menu_draw(function (ui: zui_t) {
 		let start_y: i32 = ui._y;
 		ui.changed = false;
@@ -201,8 +210,8 @@ function ui_toolbar_tool_properties_menu() {
 
 		let h: i32 = ui._y - start_y;
 		ui_menu_elements = math_floor(h / zui_ELEMENT_H(ui));
-		ui_menu_x = math_floor(_x + _w + 2);
-		ui_menu_y = math_floor(_y - 6 * zui_SCALE(ui));
+		ui_menu_x = math_floor(_ui_toolbar_tool_properties_menu_x + _ui_toolbar_tool_properties_menu_w + 2);
+		ui_menu_y = math_floor(_ui_toolbar_tool_properties_menu_y - 6 * zui_SCALE(ui));
 		ui_menu_fit_to_screen();
 
 	}, 0);

+ 14 - 4
base/Sources/ui_view2d.ts

@@ -49,6 +49,12 @@ function ui_view2d_init() {
 	ui_view2d_ui.scroll_enabled = false;
 }
 
+let _ui_view2d_render_tex: image_t;
+let _ui_view2d_render_x: f32;
+let _ui_view2d_render_y: f32;
+let _ui_view2d_render_tw: f32;
+let _ui_view2d_render_th: f32;
+
 function ui_view2d_render() {
 
 	ui_view2d_ww = config_raw.layout[layout_size_t.NODES_W];
@@ -240,12 +246,16 @@ function ui_view2d_render() {
 
 			// Texture and node preview color picking
 			if ((context_in_2d_view(view_2d_type_t.ASSET) || context_in_2d_view(view_2d_type_t.NODE)) && context_raw.tool == workspace_tool_t.PICKER && ui_view2d_ui.input_down) {
-				let x: f32 = ui_view2d_ui.input_x - tx - ui_view2d_wx;
-				let y: f32 = ui_view2d_ui.input_y - ty - ui_view2d_wy;
-				base_notify_on_next_frame(function () {
+				_ui_view2d_render_tex = tex;
+				_ui_view2d_render_x = ui_view2d_ui.input_x - tx - ui_view2d_wx;;
+				_ui_view2d_render_y = ui_view2d_ui.input_y - ty - ui_view2d_wy;
+				_ui_view2d_render_tw = tw;
+				_ui_view2d_render_th = th;
+
+				app_notify_on_next_frame(function () {
 					let texpaint_picker: image_t = map_get(render_path_render_targets, "texpaint_picker")._image;
 					g2_begin(texpaint_picker);
-					g2_draw_scaled_image(tex, -x, -y, tw, th);
+					g2_draw_scaled_image(_ui_view2d_render_tex, -_ui_view2d_render_x, -_ui_view2d_render_y, _ui_view2d_render_tw, _ui_view2d_render_th);
 					g2_end();
 					let a: buffer_view_t = buffer_view_create(image_get_pixels(texpaint_picker));
 					///if (krom_metal || krom_vulkan)

+ 3 - 12
base/Sources/uniforms_ext.ts

@@ -398,27 +398,18 @@ function uniforms_ext_tex_link(object: object_t, mat: material_data_t, link: str
 	///if is_paint
 	else if (link == "_texuvmap") {
 		if (!util_uv_uvmap_cached) {
-			let _init = function () {
-				util_uv_cache_uv_map();
-			}
-			app_notify_on_init(_init);
+			app_notify_on_init(util_uv_cache_uv_map);
 		}
 		return util_uv_uvmap;
 	}
 	else if (link == "_textrianglemap") {
 		if (!util_uv_trianglemap_cached) {
-			let _init = function () {
-				util_uv_cache_triangle_map();
-			}
-			app_notify_on_init(_init);
+			app_notify_on_init(util_uv_cache_triangle_map);
 		}
 		return util_uv_trianglemap;
 	}
 	else if (link == "_texuvislandmap") {
-		let _init = function () {
-			util_uv_cache_uv_island_map();
-		}
-		app_notify_on_init(_init);
+		app_notify_on_init(util_uv_cache_uv_island_map);
 		return util_uv_uvislandmap_cached ? util_uv_uvislandmap :map_get(render_path_render_targets, "empty_black")._image;
 	}
 	else if (link == "_texdilatemap") {

+ 2 - 3
base/Sources/util_render.ts

@@ -325,10 +325,9 @@ function util_render_make_brush_preview() {
 	context_raw.layer = _layer;
 	context_raw.material = _material;
 	context_raw.tool = _tool;
-	let _init = function () {
+	app_notify_on_init(function () {
 		make_material_parse_paint_material(false);
-	}
-	app_notify_on_init(_init);
+	});
 
 	// Restore paint mesh
 	context_raw.material_preview = false;

+ 19 - 19
base/Sources/util_uv.ts

@@ -147,6 +147,24 @@ function util_uv_cache_dilate_map() {
 	util_uv_dilate_bytes = null;
 }
 
+function _util_uv_check(c: coord_t, w: i32, h: i32, r: i32, view: buffer_view_t, coords: coord_t[]) {
+	if (c.x < 0 || c.x >= w || c.y < 0 || c.y >= h) {
+		return;
+	}
+	if (buffer_view_get_u8(view, c.y * w + c.x) == 255) {
+		return;
+	}
+	let dilate_view: buffer_view_t = buffer_view_create(util_uv_dilate_bytes);
+	if (buffer_view_get_u8(dilate_view, c.y * r * util_uv_dilatemap.width + c.x * r) == 0) {
+		return;
+	}
+	buffer_view_set_u8(view, c.y * w + c.x, 255);
+	array_push(coords, { x: c.x + 1, y: c.y });
+	array_push(coords, { x: c.x - 1, y: c.y });
+	array_push(coords, { x: c.x, y: c.y + 1 });
+	array_push(coords, { x: c.x, y: c.y - 1 });
+}
+
 function util_uv_cache_uv_island_map() {
 	util_uv_cache_dilate_map();
 	if (util_uv_dilate_bytes == null) {
@@ -162,26 +180,8 @@ function util_uv_cache_uv_island_map() {
 	let coords: coord_t[] = [{ x: x, y: y }];
 	let r: i32 = math_floor(util_uv_dilatemap.width / w);
 
-	let check = function (c: coord_t) {
-		if (c.x < 0 || c.x >= w || c.y < 0 || c.y >= h) {
-			return;
-		}
-		if (buffer_view_get_u8(view, c.y * w + c.x) == 255) {
-			return;
-		}
-		let dilate_view: buffer_view_t = buffer_view_create(util_uv_dilate_bytes);
-		if (buffer_view_get_u8(dilate_view, c.y * r * util_uv_dilatemap.width + c.x * r) == 0) {
-			return;
-		}
-		buffer_view_set_u8(view, c.y * w + c.x, 255);
-		array_push(coords, { x: c.x + 1, y: c.y });
-		array_push(coords, { x: c.x - 1, y: c.y });
-		array_push(coords, { x: c.x, y: c.y + 1 });
-		array_push(coords, { x: c.x, y: c.y - 1 });
-	}
-
 	while (coords.length > 0) {
-		check(coords.pop());
+		_util_uv_check(coords.pop(), w, h, r, view, coords);
 	}
 
 	if (util_uv_uvislandmap != null) {

+ 1 - 1
base/project.js

@@ -27,7 +27,7 @@ flags.on_c_project_created = async function(c_project, platform, graphics) {
 	let dir = flags.name.toLowerCase();
 
 	if (graphics === "vulkan") {
-		c_project.addDefine("KORE_VKRT");
+		c_project.addDefine("KINC_VKRT");
 		await c_project.addProject("../" + dir + "/glsl_to_spirv");
 	}