luboslenco 5 일 전
부모
커밋
7a265e2292

+ 0 - 0
paint/assets/plugins/import_exr.js


+ 1352 - 66
paint/sources/base.ts

@@ -33,6 +33,26 @@ let base_drag_layer: slot_layer_t = null;
 let base_default_fov: f32 = 0.69;
 let _base_material_count: i32;
 
+let ui: ui_t;
+let ui_base_show: bool = true;
+let ui_base_border_started: i32 = 0;
+let ui_base_border_handle: ui_handle_t = null;
+let ui_base_action_paint_remap: string = "";
+let ui_base_operator_search_offset: i32 = 0;
+let ui_base_undo_tap_time: f32 = 0.0;
+let ui_base_redo_tap_time: f32 = 0.0;
+let ui_base_viewport_col: i32;
+let _ui_base_operator_search_first: bool;
+
+type tab_draw_t = {
+	f: (h: ui_handle_t)=>void;
+};
+type tab_draw_array_t = tab_draw_t[];
+
+let ui_base_hwnds: ui_handle_t[] = ui_base_init_hwnds();
+let ui_base_htabs: ui_handle_t[] = ui_base_init_htabs();
+let ui_base_hwnd_tabs: tab_draw_array_t[] = ui_base_init_hwnd_tabs();
+
 function base_init() {
 	base_last_window_width = iron_window_width();
 	base_last_window_height = iron_window_height();
@@ -156,7 +176,7 @@ function base_w(): i32 {
 
 	let res: i32 = 0;
 	if (config_raw.layout == null) {
-		let sidebarw: i32 = ui_base_default_sidebar_w;
+		let sidebarw: i32 = ui_sidebar_default_w;
 		res = iron_window_width() - sidebarw - ui_toolbar_default_w;
 	}
 	else if (ui_nodes_show || ui_view2d_show) {
@@ -192,7 +212,7 @@ function base_h(): i32 {
 	let res: i32 = iron_window_height();
 
 	if (config_raw.layout == null) {
-		res -= ui_header_default_h * 2 + ui_status_default_status_h;
+		res -= ui_header_default_h * 2 + ui_statusbar_default_h;
 		///if (arm_android || arm_ios)
 		res += ui_header_h;
 		///end
@@ -587,7 +607,7 @@ function base_render() {
 	}
 
 	let using_menu: bool = ui_menu_show && mouse_y > ui_header_h;
-	base_ui_enabled = !ui_box_show && !using_menu && !base_is_combo_selected();
+	base_ui_enabled = !ui_box_show && !using_menu && ui.combo_selected_handle == null;
 	if (ui_box_show) {
 		ui_box_render();
 	}
@@ -670,14 +690,6 @@ function base_toggle_fullscreen() {
 	}
 }
 
-function base_is_scrolling(): bool {
-	return ui.is_scrolling;
-}
-
-function base_is_combo_selected(): bool {
-	return ui.combo_selected_handle != null;
-}
-
 function base_is_decal_layer(): bool {
 	let is_painting: bool = context_raw.tool != tool_type_t.MATERIAL && context_raw.tool != tool_type_t.BAKE;
 	return is_painting && context_raw.layer.fill_layer != null && context_raw.layer.uv_type == uv_type_t.PROJECT;
@@ -689,7 +701,7 @@ function base_redraw_status() {
 
 function base_redraw_console() {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui != null && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui != null && statush > ui_statusbar_default_h * UI_SCALE()) {
 		ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
 	}
 }
@@ -711,7 +723,7 @@ function base_init_layout() {
 	let show2d: bool = (ui_nodes_show || ui_view2d_show) && raw.layout != null;
 	let new_layout: i32[] = [];
 
-	array_push(new_layout, math_floor(ui_base_default_sidebar_w * raw.window_scale)); // LayoutSidebarW
+	array_push(new_layout, math_floor(ui_sidebar_default_w * raw.window_scale)); // LayoutSidebarW
 	array_push(new_layout, math_floor(iron_window_height() / 2)); // LayoutSidebarH0
 	array_push(new_layout, math_floor(iron_window_height() / 2)); // LayoutSidebarH1
 
@@ -724,7 +736,7 @@ function base_init_layout() {
 	///end
 
 	array_push(new_layout, math_floor(sys_h() / 2)); // LayoutNodesH
-	array_push(new_layout, math_floor(ui_status_default_status_h * raw.window_scale)); // LayoutStatusH
+	array_push(new_layout, math_floor(ui_statusbar_default_h * raw.window_scale)); // LayoutStatusH
 
 	///if (arm_android || arm_ios)
 	array_push(new_layout, 0); // LayoutHeader
@@ -741,60 +753,1334 @@ function base_init_layout() {
 	raw.layout = new_layout;
 }
 
-function base_init_config() {
-	let raw: config_t = config_raw;
-	raw.recent_projects = [];
-	raw.bookmarks = [];
-	raw.plugins = [];
-	///if (arm_android || arm_ios)
-	raw.keymap = "touch.json";
-	///else
-	raw.keymap = "default.json";
+function ui_base_init_hwnds(): ui_handle_t[] {
+	let hwnds: ui_handle_t[] = [ui_handle_create(), ui_handle_create(), ui_handle_create()];
+	return hwnds;
+}
+
+function ui_base_init_htabs(): ui_handle_t[] {
+	let htabs: ui_handle_t[] = [ui_handle_create(), ui_handle_create(), ui_handle_create()];
+	return htabs;
+}
+
+function _draw_callback_create(f: (h: ui_handle_t)=>void): tab_draw_t {
+	let cb: tab_draw_t = { f: f };
+	return cb;
+}
+
+function ui_base_init_hwnd_tabs(): tab_draw_array_t[] {
+	let a0: tab_draw_array_t = [
+		_draw_callback_create(tab_layers_draw),
+		_draw_callback_create(tab_history_draw),
+		_draw_callback_create(tab_plugins_draw)
+	];
+	let a1: tab_draw_array_t = [
+		_draw_callback_create(tab_materials_draw),
+		_draw_callback_create(tab_brushes_draw),
+		_draw_callback_create(tab_scripts_draw)
+
+	];
+	let a2: tab_draw_array_t = [
+		_draw_callback_create(tab_browser_draw),
+		_draw_callback_create(tab_meshes_draw),
+		_draw_callback_create(tab_textures_draw),
+		_draw_callback_create(tab_fonts_draw),
+		_draw_callback_create(tab_swatches_draw),
+		_draw_callback_create(tab_console_draw),
+		_draw_callback_create(ui_statusbar_draw_version_tab)
+	];
+
+    let r: tab_draw_array_t[] = [];
+	array_push(r, a0);
+	array_push(r, a1);
+	array_push(r, a2);
+	return r;
+}
+
+function ui_base_init() {
+	ui_toolbar_init();
+	context_raw.text_tool_text = tr("Text");
+	ui_header_init();
+	ui_statusbar_init();
+	ui_menubar_init();
+
+	ui_header_h = math_floor(ui_header_default_h * config_raw.window_scale);
+	ui_menubar_w = math_floor(ui_menubar_default_w * config_raw.window_scale);
+
+	if (context_raw.empty_envmap == null) {
+		ui_base_make_empty_envmap(base_theme.VIEWPORT_COL);
+	}
+	if (context_raw.preview_envmap == null) {
+		let b: u8_array_t = u8_array_create(4);
+		b[0] = 0;
+		b[1] = 0;
+		b[2] = 0;
+		b[3] = 255;
+		context_raw.preview_envmap = gpu_create_texture_from_bytes(b, 1, 1);
+	}
+
+	if (context_raw.saved_envmap == null) {
+		// raw.saved_envmap = scene_world._envmap;
+		context_raw.default_irradiance = scene_world._.irradiance;
+		context_raw.default_radiance = scene_world._.radiance;
+		context_raw.default_radiance_mipmaps = scene_world._.radiance_mipmaps;
+	}
+	scene_world._.envmap = context_raw.show_envmap ? context_raw.saved_envmap : context_raw.empty_envmap;
+	context_raw.ddirty = 1;
+
+	let resources: string[] = ["cursor.k", "icons.k"];
+	resource_load(resources);
+
+	let scale: f32 = config_raw.window_scale;
+	let ops: ui_options_t = {
+		theme: base_theme,
+		font: base_font,
+		scale_factor: scale,
+		color_wheel: base_color_wheel,
+		black_white_gradient: base_color_wheel_gradient
+	};
+	ui = ui_create(ops);
+	ui_on_border_hover = ui_base_on_border_hover;
+	ui_on_tab_drop = ui_base_on_tab_drop;
+	if (UI_SCALE() > 1) {
+		ui_base_set_icon_scale();
+	}
+
+	context_raw.gizmo = scene_get_child(".Gizmo");
+	context_raw.gizmo_translate_x = object_get_child(context_raw.gizmo, ".TranslateX");
+	context_raw.gizmo_translate_y = object_get_child(context_raw.gizmo, ".TranslateY");
+	context_raw.gizmo_translate_z = object_get_child(context_raw.gizmo, ".TranslateZ");
+	context_raw.gizmo_scale_x = object_get_child(context_raw.gizmo, ".ScaleX");
+	context_raw.gizmo_scale_y = object_get_child(context_raw.gizmo, ".ScaleY");
+	context_raw.gizmo_scale_z = object_get_child(context_raw.gizmo, ".ScaleZ");
+	context_raw.gizmo_rotate_x = object_get_child(context_raw.gizmo, ".RotateX");
+	context_raw.gizmo_rotate_y = object_get_child(context_raw.gizmo, ".RotateY");
+	context_raw.gizmo_rotate_z = object_get_child(context_raw.gizmo, ".RotateZ");
+
+	project_new(false);
+
+	if (project_filepath == "") {
+		sys_notify_on_next_frame(layers_init);
+	}
+
+	context_raw.project_objects = [];
+	for (let i: i32 = 0; i < scene_meshes.length; ++i) {
+		let m: mesh_object_t = scene_meshes[i];
+		array_push(context_raw.project_objects, m);
+	}
+
+	operator_register("view_top", ui_base_view_top);
+}
+
+function ui_base_update() {
+	ui_base_update_ui();
+	operator_update();
+
+	let keys: string[] = map_keys(plugin_map);
+	for (let i: i32 = 0; i < keys.length; ++i) {
+		let p: plugin_t = map_get(plugin_map, keys[i]);
+		if (p.on_update != null) {
+			js_call(p.on_update);
+		}
+	}
+
+	if (!base_ui_enabled) {
+		return;
+	}
+
+	if (!ui.is_typing) {
+		if (operator_shortcut(map_get(config_keymap, "toggle_node_editor"))) {
+			ui_nodes_canvas_type == canvas_type_t.MATERIAL ? ui_base_show_material_nodes() : ui_base_show_brush_nodes();
+		}
+		else if (operator_shortcut(map_get(config_keymap, "toggle_browser"))) {
+			ui_base_toggle_browser();
+		}
+
+		else if (operator_shortcut(map_get(config_keymap, "toggle_2d_view"))) {
+			ui_base_show_2d_view(view_2d_type_t.LAYER);
+		}
+	}
+
+	if (operator_shortcut(map_get(config_keymap, "file_save_as"))) {
+		project_save_as();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_save"))) {
+		project_save();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_open"))) {
+		project_open();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_open_recent"))) {
+		box_projects_show();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_reimport_mesh"))) {
+		project_reimport_mesh();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_reimport_textures"))) {
+		project_reimport_textures();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_new"))) {
+		project_new_box();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_export_textures"))) {
+		if (context_raw.texture_export_path == "") { // First export, ask for path
+			context_raw.layers_export = export_mode_t.VISIBLE;
+			box_export_show_textures();
+		}
+		else {
+			sys_notify_on_next_frame(function () {
+				export_texture_run(context_raw.texture_export_path);
+			});
+		}
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_export_textures_as"))) {
+		context_raw.layers_export = export_mode_t.VISIBLE;
+		box_export_show_textures();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "file_import_assets"))) {
+		project_import_asset();
+	}
+	else if (operator_shortcut(map_get(config_keymap, "edit_prefs"))) {
+		box_preferences_show();
+	}
+
+	if (keyboard_started(map_get(config_keymap, "view_distract_free")) || (keyboard_started("escape") && !ui_base_show && !ui_box_show)) {
+		ui_base_toggle_distract_free();
+	}
+
+	///if arm_linux
+	if (operator_shortcut("alt+enter", shortcut_type_t.STARTED)) {
+		base_toggle_fullscreen();
+	}
 	///end
-	raw.theme = "default.json";
-	raw.server = "https://armorpaint.fra1.digitaloceanspaces.com";
-	raw.undo_steps = 4;
-	raw.pressure_radius = true;
-	///if (arm_ios || arm_linux)
-	raw.pressure_sensitivity = 1.0;
-	///else
-	raw.pressure_sensitivity = 2.0;
+
+	let decal_mask: bool = context_is_decal_mask();
+
+	if ((context_raw.brush_can_lock || context_raw.brush_locked) && mouse_moved) {
+		if (operator_shortcut(map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN) ||
+			operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN) ||
+			operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN) ||
+			(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN))) {
+			if (context_raw.brush_locked) {
+				if (operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN)) {
+					context_raw.brush_opacity += mouse_movement_x / 500;
+					context_raw.brush_opacity = math_max(0.0, math_min(1.0, context_raw.brush_opacity));
+					context_raw.brush_opacity = math_round(context_raw.brush_opacity * 100) / 100;
+					context_raw.brush_opacity_handle.value = context_raw.brush_opacity;
+				}
+				else if (operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN)) {
+					context_raw.brush_angle += mouse_movement_x / 5;
+					let i: i32 = math_floor(context_raw.brush_angle);
+					context_raw.brush_angle = i % 360;
+					if (context_raw.brush_angle < 0) context_raw.brush_angle += 360;
+					context_raw.brush_angle_handle.value = context_raw.brush_angle;
+					make_material_parse_paint_material();
+				}
+				else if (decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN)) {
+					context_raw.brush_decal_mask_radius += mouse_movement_x / 150;
+					context_raw.brush_decal_mask_radius = math_max(0.01, math_min(4.0, context_raw.brush_decal_mask_radius));
+					context_raw.brush_decal_mask_radius = math_round(context_raw.brush_decal_mask_radius * 100) / 100;
+					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
+				}
+				else {
+					context_raw.brush_radius += mouse_movement_x / 150;
+					context_raw.brush_radius = math_max(0.01, math_min(4.0, context_raw.brush_radius));
+					context_raw.brush_radius = math_round(context_raw.brush_radius * 100) / 100;
+					context_raw.brush_radius_handle.value = context_raw.brush_radius;
+				}
+				ui_header_handle.redraws = 2;
+			}
+			else if (context_raw.brush_can_lock) {
+				context_raw.brush_can_lock = false;
+				context_raw.brush_locked = true;
+			}
+		}
+	}
+
+	let is_typing: bool = ui.is_typing;
+	if (!is_typing) {
+		if (operator_shortcut(map_get(config_keymap, "select_material"), shortcut_type_t.DOWN)) {
+			ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
+			for (let i: i32 = 1; i < 10; ++i) {
+				if (keyboard_started(i + "")) {
+					context_select_material(i - 1);
+				}
+			}
+		}
+		else if (operator_shortcut(map_get(config_keymap, "select_layer"), shortcut_type_t.DOWN)) {
+			ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+			for (let i: i32 = 1; i < 10; ++i) {
+				if (keyboard_started(i + "")) {
+					context_select_layer(i - 1);
+				}
+			}
+		}
+	}
+
+	// Viewport shortcuts
+	if (context_in_paint_area() && !is_typing) {
+
+		if (!mouse_down("right")) { // Fly mode off
+			if (operator_shortcut(map_get(config_keymap, "tool_brush"))) {
+				context_select_tool(tool_type_t.BRUSH);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_eraser"))) {
+				context_select_tool(tool_type_t.ERASER);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_fill"))) {
+				context_select_tool(tool_type_t.FILL);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_colorid"))) {
+				context_select_tool(tool_type_t.COLORID);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_decal"))) {
+				context_select_tool(tool_type_t.DECAL);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_text"))) {
+				context_select_tool(tool_type_t.TEXT);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_clone"))) {
+				context_select_tool(tool_type_t.CLONE);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_blur"))) {
+				context_select_tool(tool_type_t.BLUR);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_smudge"))) {
+				context_select_tool(tool_type_t.SMUDGE);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_particle"))) {
+				context_select_tool(tool_type_t.PARTICLE);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_picker"))) {
+				context_select_tool(tool_type_t.PICKER);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_bake"))) {
+				context_select_tool(tool_type_t.BAKE);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_gizmo"))) {
+				context_select_tool(tool_type_t.GIZMO);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "tool_material"))) {
+				context_select_tool(tool_type_t.MATERIAL);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "swap_brush_eraser"))) {
+				context_select_tool(context_raw.tool == tool_type_t.BRUSH ? tool_type_t.ERASER : tool_type_t.BRUSH);
+			}
+		}
+
+		// Radius
+		if (context_raw.tool == tool_type_t.BRUSH  ||
+			context_raw.tool == tool_type_t.ERASER ||
+			context_raw.tool == tool_type_t.DECAL  ||
+			context_raw.tool == tool_type_t.TEXT   ||
+			context_raw.tool == tool_type_t.CLONE  ||
+			context_raw.tool == tool_type_t.BLUR   ||
+			context_raw.tool == tool_type_t.SMUDGE   ||
+			context_raw.tool == tool_type_t.PARTICLE) {
+			if (operator_shortcut(map_get(config_keymap, "brush_radius")) ||
+				operator_shortcut(map_get(config_keymap, "brush_opacity")) ||
+				operator_shortcut(map_get(config_keymap, "brush_angle")) ||
+				(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius")))) {
+				context_raw.brush_can_lock = true;
+				if (!pen_connected) {
+					mouse_lock();
+				}
+				context_raw.lock_started_x = mouse_x;
+				context_raw.lock_started_y = mouse_y;
+			}
+			else if (operator_shortcut(map_get(config_keymap, "brush_radius_decrease"), shortcut_type_t.REPEAT)) {
+				context_raw.brush_radius -= ui_base_get_radius_increment();
+				context_raw.brush_radius = math_max(math_round(context_raw.brush_radius * 100) / 100, 0.01);
+				context_raw.brush_radius_handle.value = context_raw.brush_radius;
+				ui_header_handle.redraws = 2;
+			}
+			else if (operator_shortcut(map_get(config_keymap, "brush_radius_increase"), shortcut_type_t.REPEAT)) {
+				context_raw.brush_radius += ui_base_get_radius_increment();
+				context_raw.brush_radius = math_round(context_raw.brush_radius * 100) / 100;
+				context_raw.brush_radius_handle.value = context_raw.brush_radius;
+				ui_header_handle.redraws = 2;
+			}
+			else if (decal_mask) {
+				if (operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius_decrease"), shortcut_type_t.REPEAT)) {
+					context_raw.brush_decal_mask_radius -= ui_base_get_radius_increment();
+					context_raw.brush_decal_mask_radius = math_max(math_round(context_raw.brush_decal_mask_radius * 100) / 100, 0.01);
+					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
+					ui_header_handle.redraws = 2;
+				}
+				else if (operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius_increase"), shortcut_type_t.REPEAT)) {
+					context_raw.brush_decal_mask_radius += ui_base_get_radius_increment();
+					context_raw.brush_decal_mask_radius = math_round(context_raw.brush_decal_mask_radius * 100) / 100;
+					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
+					ui_header_handle.redraws = 2;
+				}
+			}
+		}
+
+		if (decal_mask && (operator_shortcut(map_get(config_keymap, "decal_mask"), shortcut_type_t.STARTED) || operator_shortcut(map_get(config_keymap, "decal_mask"), shortcut_type_t.RELEASED))) {
+			ui_header_handle.redraws = 2;
+		}
+
+		// Viewpoint
+		if (mouse_view_x() < sys_w()) {
+			if (operator_shortcut(map_get(config_keymap, "view_reset"))) {
+				viewport_reset();
+				viewport_scale_to_bounds();
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_back"))) {
+				viewport_set_view(0, 1, 0, math_pi() / 2, 0, math_pi());
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_front"))) {
+				viewport_set_view(0, -1, 0, math_pi() / 2, 0, 0);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_left"))) {
+				viewport_set_view(-1, 0, 0, math_pi() / 2, 0, -math_pi() / 2);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_right"))) {
+				viewport_set_view(1, 0, 0, math_pi() / 2, 0, math_pi() / 2);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_bottom"))) {
+				viewport_set_view(0, 0, -1, math_pi(), 0, math_pi());
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_camera_type"))) {
+				context_raw.camera_type = context_raw.camera_type == camera_type_t.PERSPECTIVE ? camera_type_t.ORTHOGRAPHIC : camera_type_t.PERSPECTIVE;
+				context_raw.cam_handle.position = context_raw.camera_type;
+				viewport_update_camera_type(context_raw.camera_type);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_orbit_left"), shortcut_type_t.REPEAT)) {
+				viewport_orbit(-math_pi() / 12, 0);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_orbit_right"), shortcut_type_t.REPEAT)) {
+				viewport_orbit(math_pi() / 12, 0);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_orbit_up"), shortcut_type_t.REPEAT)) {
+				viewport_orbit(0, -math_pi() / 12);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_orbit_down"), shortcut_type_t.REPEAT)) {
+				viewport_orbit(0, math_pi() / 12);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_orbit_opposite"))) {
+				viewport_orbit_opposite();
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_zoom_in"), shortcut_type_t.REPEAT)) {
+				viewport_zoom(0.2);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "view_zoom_out"), shortcut_type_t.REPEAT)) {
+				viewport_zoom(-0.2);
+			}
+			else if (operator_shortcut(map_get(config_keymap, "viewport_mode"))) {
+				ui.is_key_pressed = false;
+				ui_menu_draw(function () {
+					let mode_handle: ui_handle_t = ui_handle(__ID__);
+					mode_handle.position = context_raw.viewport_mode;
+					ui_text(tr("Viewport Mode"), ui_align_t.RIGHT);
+					let modes: string[] = [
+						tr("Lit"),
+						tr("Base Color"),
+						tr("Normal"),
+						tr("Occlusion"),
+						tr("Roughness"),
+						tr("Metallic"),
+						tr("Opacity"),
+						tr("Height"),
+						tr("Emission"),
+						tr("Subsurface"),
+						tr("TexCoord"),
+						tr("Object Normal"),
+						tr("Material ID"),
+						tr("Object ID"),
+						tr("Mask")
+					];
+
+					let shortcuts: string[] = ["l", "b", "n", "o", "r", "m", "a", "h", "e", "s", "t", "1", "2", "3", "4"];
+
+					if (gpu_raytrace_supported()) {
+						array_push(modes, tr("Path Traced"));
+						array_push(shortcuts, "p");
+					}
+
+					for (let i: i32 = 0; i < modes.length; ++i) {
+						ui_radio(mode_handle, i, modes[i], shortcuts[i]);
+					}
+
+					let index: i32 = array_index_of(shortcuts, keyboard_key_code(ui.key_code));
+					if (ui.is_key_pressed && index != -1) {
+						mode_handle.position = index;
+						ui.changed = true;
+						context_set_viewport_mode(mode_handle.position);
+					}
+					else if (mode_handle.changed) {
+						context_set_viewport_mode(mode_handle.position);
+						ui.changed = true;
+					}
+				});
+			}
+		}
+
+		if (operator_shortcut(map_get(config_keymap, "operator_search"))) {
+			ui_base_operator_search();
+		}
+	}
+
+	if (context_raw.brush_can_lock || context_raw.brush_locked) {
+		if (mouse_moved && context_raw.brush_can_unlock) {
+			context_raw.brush_locked = false;
+			context_raw.brush_can_unlock = false;
+		}
+
+		let b: bool = (context_raw.brush_can_lock || context_raw.brush_locked) &&
+			!operator_shortcut(map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN) &&
+			!operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN) &&
+			!operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN) &&
+			!(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN));
+
+		if (b) {
+			mouse_unlock();
+			context_raw.last_paint_x = -1;
+			context_raw.last_paint_y = -1;
+			if (context_raw.brush_can_lock) {
+				context_raw.brush_can_lock = false;
+				context_raw.brush_can_unlock = false;
+				context_raw.brush_locked = false;
+			}
+			else {
+				context_raw.brush_can_unlock = true;
+			}
+		}
+	}
+
+	// Resizing
+	if (ui_base_border_handle != null) {
+		if (ui_base_border_handle == ui_nodes_hwnd || ui_base_border_handle == ui_view2d_hwnd) {
+			if (ui_base_border_started == border_side_t.LEFT) {
+				config_raw.layout[layout_size_t.NODES_W] -= math_floor(mouse_movement_x);
+				if (config_raw.layout[layout_size_t.NODES_W] < 32) {
+					config_raw.layout[layout_size_t.NODES_W] = 32;
+				}
+				else if (config_raw.layout[layout_size_t.NODES_W] > iron_window_width() * 0.7) {
+					config_raw.layout[layout_size_t.NODES_W] = math_floor(iron_window_width() * 0.7);
+				}
+			}
+			else { // UINodes / UIView2D ratio
+				config_raw.layout[layout_size_t.NODES_H] -= math_floor(mouse_movement_y);
+				if (config_raw.layout[layout_size_t.NODES_H] < 32) {
+					config_raw.layout[layout_size_t.NODES_H] = 32;
+				}
+				else if (config_raw.layout[layout_size_t.NODES_H] > sys_h() * 0.95) {
+					config_raw.layout[layout_size_t.NODES_H] = math_floor(sys_h() * 0.95);
+				}
+			}
+		}
+		else if (ui_base_border_handle == ui_base_hwnds[tab_area_t.STATUS]) {
+			let my: i32 = math_floor(mouse_movement_y);
+			if (config_raw.layout[layout_size_t.STATUS_H] - my >= ui_statusbar_default_h * config_raw.window_scale && config_raw.layout[layout_size_t.STATUS_H] - my < iron_window_height() * 0.7) {
+				config_raw.layout[layout_size_t.STATUS_H] -= my;
+			}
+		}
+		else {
+			if (ui_base_border_started == border_side_t.LEFT) {
+				config_raw.layout[layout_size_t.SIDEBAR_W] -= math_floor(mouse_movement_x);
+				if (config_raw.layout[layout_size_t.SIDEBAR_W] < ui_sidebar_w_mini) {
+					config_raw.layout[layout_size_t.SIDEBAR_W] = ui_sidebar_w_mini;
+				}
+				else if (config_raw.layout[layout_size_t.SIDEBAR_W] > iron_window_width() - ui_sidebar_w_mini) {
+					config_raw.layout[layout_size_t.SIDEBAR_W] = iron_window_width() - ui_sidebar_w_mini;
+				}
+			}
+			else {
+				let my: i32 = math_floor(mouse_movement_y);
+				if (ui_base_border_handle == ui_base_hwnds[tab_area_t.SIDEBAR1] && ui_base_border_started == border_side_t.TOP) {
+					if (config_raw.layout[layout_size_t.SIDEBAR_H0] + my > 32 && config_raw.layout[layout_size_t.SIDEBAR_H1] - my > 32) {
+						config_raw.layout[layout_size_t.SIDEBAR_H0] += my;
+						config_raw.layout[layout_size_t.SIDEBAR_H1] -= my;
+					}
+				}
+			}
+		}
+	}
+
+	if (!mouse_down()) {
+		ui_base_border_handle = null;
+		base_is_resizing = false;
+	}
+
+	if (context_raw.tool == tool_type_t.PARTICLE && context_in_paint_area() && !context_raw.paint2d) {
+		util_particle_init_physics();
+		let world: physics_world_t = physics_world_active;
+		physics_world_update(world);
+		context_raw.ddirty = 2;
+		context_raw.rdirty = 2;
+		if (mouse_started()) {
+			if (context_raw.particle_timer != null) {
+				tween_stop(context_raw.particle_timer);
+				let timer: tween_anim_t = context_raw.particle_timer;
+				timer.done(timer.done_data);
+				context_raw.particle_timer = null;
+			}
+			history_push_undo = true;
+			context_raw.particle_hit_x = context_raw.particle_hit_y = context_raw.particle_hit_z = 0;
+			let o: object_t = scene_spawn_object(".Sphere");
+			let mo: mesh_object_t = o.ext;
+			mo.base.name = ".Bullet";
+			mo.base.visible = true;
+
+			let camera: camera_object_t = scene_camera;
+			let ct: transform_t = camera.base.transform;
+			mo.base.transform.loc = vec4_create(transform_world_x(ct), transform_world_y(ct), transform_world_z(ct));
+			mo.base.transform.scale = vec4_create(context_raw.brush_radius * 0.2, context_raw.brush_radius * 0.2, context_raw.brush_radius * 0.2);
+			transform_build_matrix(mo.base.transform);
+
+			let body: physics_body_t = physics_body_create();
+			body.shape = physics_shape_t.SPHERE;
+			body.mass = 1.0;
+			physics_body_init(body, mo.base);
+
+			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 (mo: mesh_object_t) {
+				mesh_object_remove(mo);
+			}, mo);
+		}
+
+		let pairs: physics_pair_t[] = physics_world_get_contact_pairs(world, context_raw.paint_body);
+		if (pairs != null) {
+			for (let i: i32 = 0; i < pairs.length; ++i) {
+				let p: physics_pair_t = pairs[i];
+				context_raw.last_particle_hit_x = context_raw.particle_hit_x != 0 ? context_raw.particle_hit_x : p.pos_a_x;
+				context_raw.last_particle_hit_y = context_raw.particle_hit_y != 0 ? context_raw.particle_hit_y : p.pos_a_y;
+				context_raw.last_particle_hit_z = context_raw.particle_hit_z != 0 ? context_raw.particle_hit_z : p.pos_a_z;
+				context_raw.particle_hit_x = p.pos_a_x;
+				context_raw.particle_hit_y = p.pos_a_y;
+				context_raw.particle_hit_z = p.pos_a_z;
+				context_raw.pdirty = 1;
+				break; // 1 pair for now
+			}
+		}
+	}
+}
+
+function ui_base_view_top() {
+	let is_typing: bool = ui.is_typing;
+
+	if (context_in_paint_area() && !is_typing) {
+		if (mouse_view_x() < sys_w()) {
+			viewport_set_view(0, 0, 1, 0, 0, 0);
+		}
+	}
+}
+
+function ui_base_operator_search() {
+	_ui_base_operator_search_first = true;
+
+	ui_menu_draw(function () {
+		ui_menu_h = UI_ELEMENT_H() * 8;
+		let search_handle: ui_handle_t = ui_handle(__ID__);
+		let search: string = ui_text_input(search_handle, "", ui_align_t.LEFT, true, true);
+		ui.changed = false;
+		if (_ui_base_operator_search_first) {
+			_ui_base_operator_search_first = false;
+			search_handle.text = "";
+			ui_start_text_edit(search_handle); // Focus search bar
+		}
+
+		if (search_handle.changed) {
+			ui_base_operator_search_offset = 0;
+		}
+
+		if (ui.is_key_pressed) { // Move selection
+			if (ui.key_code == key_code_t.DOWN && ui_base_operator_search_offset < 6) {
+				ui_base_operator_search_offset++;
+			}
+			if (ui.key_code == key_code_t.UP && ui_base_operator_search_offset > 0) {
+				ui_base_operator_search_offset--;
+			}
+		}
+		let enter: bool = keyboard_down("enter");
+		let count: i32 = 0;
+		let BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
+
+		let keys: string[] = map_keys(config_keymap);
+		for (let i: i32 = 0; i < keys.length; ++i) {
+			let n: string = keys[i];
+			if (string_index_of(n, search) >= 0) {
+				ui.ops.theme.BUTTON_COL = count == ui_base_operator_search_offset ? ui.ops.theme.HIGHLIGHT_COL : ui.ops.theme.SEPARATOR_COL;
+				if (ui_button(n, ui_align_t.LEFT, map_get(config_keymap, n)) || (enter && count == ui_base_operator_search_offset)) {
+					if (enter) {
+						ui.changed = true;
+						count = 6; // Trigger break
+					}
+					operator_run(n);
+				}
+				if (++count > 6) {
+					break;
+				}
+			}
+		}
+
+		if (enter && count == 0) { // Hide popup on enter when command is not found
+			ui.changed = true;
+			search_handle.text = "";
+		}
+		ui.ops.theme.BUTTON_COL = BUTTON_COL;
+	});
+}
+
+function ui_base_toggle_distract_free() {
+	ui_base_show = !ui_base_show;
+	base_resize();
+}
+
+function ui_base_get_radius_increment(): f32 {
+	return 0.1;
+}
+
+function ui_base_hit_rect(mx: f32, my: f32, x: i32, y: i32, w: i32, h: i32): bool {
+	return mx > x && mx < x + w && my > y && my < y + h;
+}
+
+function ui_base_get_brush_stencil_rect(): rect_t {
+	let w: i32 = math_floor(context_raw.brush_stencil_image.width * (base_h() / context_raw.brush_stencil_image.height) * context_raw.brush_stencil_scale);
+	let h: i32 = math_floor(base_h() * context_raw.brush_stencil_scale);
+	let x: i32 = math_floor(base_x() + context_raw.brush_stencil_x * base_w());
+	let y: i32 = math_floor(base_y() + context_raw.brush_stencil_y * base_h());
+	let r: rect_t = {
+		w: w,
+		h: h,
+		x: x,
+		y: y
+	};
+	return r;
+}
+
+function ui_base_update_ui() {
+	if (console_message_timer > 0) {
+		console_message_timer -= sys_delta();
+		if (console_message_timer <= 0) {
+			ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
+		}
+	}
+
+	ui_sidebar_w_mini = math_floor(ui_sidebar_default_w_mini * UI_SCALE());
+
+	if (!base_ui_enabled) {
+		return;
+	}
+
+	// Same mapping for paint and rotate (predefined in touch keymap)
+	if (context_in_viewport()) {
+		let paint_key: string = map_get(config_keymap, "action_paint");
+		let rotate_key: string = map_get(config_keymap, "action_rotate");
+		if (mouse_started() && paint_key == rotate_key) {
+			ui_base_action_paint_remap = paint_key;
+			util_render_pick_pos_nor_tex();
+			let is_mesh: bool = math_abs(context_raw.posx_picked) < 50 && math_abs(context_raw.posy_picked) < 50 && math_abs(context_raw.posz_picked) < 50;
+			///if arm_android
+			// Allow rotating with both pen and touch, because hovering a pen prevents touch input on android
+			let pen_only: bool = false;
+			///else
+			let pen_only: bool = context_raw.pen_painting_only;
+			///end
+			let is_pen: bool = pen_only && pen_down();
+			// Mesh picked - disable rotate
+			// Pen painting only - rotate with touch, paint with pen
+			if ((is_mesh && !pen_only) || is_pen) {
+				map_set(config_keymap, "action_rotate", "");
+				map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
+			}
+			// World sphere picked - disable paint
+			else {
+				map_set(config_keymap, "action_paint", "");
+				map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
+			}
+		}
+		else if (!mouse_down() && ui_base_action_paint_remap != "") {
+			map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
+			map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
+			ui_base_action_paint_remap = "";
+		}
+	}
+
+	if (context_raw.brush_stencil_image != null && operator_shortcut(map_get(config_keymap, "stencil_transform"), shortcut_type_t.DOWN)) {
+		let r: rect_t = ui_base_get_brush_stencil_rect();
+		if (mouse_started("left")) {
+			context_raw.brush_stencil_scaling =
+				ui_base_hit_rect(mouse_x, mouse_y, r.x - 8,       r.y - 8,       16, 16) ||
+				ui_base_hit_rect(mouse_x, mouse_y, r.x - 8,       r.h + r.y - 8, 16, 16) ||
+				ui_base_hit_rect(mouse_x, mouse_y, r.w + r.x - 8, r.y - 8,       16, 16) ||
+				ui_base_hit_rect(mouse_x, mouse_y, r.w + r.x - 8, r.h + r.y - 8, 16, 16);
+			let cosa: f32 = math_cos(-context_raw.brush_stencil_angle);
+			let sina: f32 = math_sin(-context_raw.brush_stencil_angle);
+			let ox: f32 = 0;
+			let oy: f32 = -r.h / 2;
+			let x: f32 = ox * cosa - oy * sina;
+			let y: f32 = ox * sina + oy * cosa;
+			x += r.x + r.w / 2;
+			y += r.y + r.h / 2;
+			context_raw.brush_stencil_rotating =
+				ui_base_hit_rect(mouse_x, mouse_y, math_floor(x - 16), math_floor(y - 16), 32, 32);
+		}
+		let _scale: f32 = context_raw.brush_stencil_scale;
+		if (mouse_down("left")) {
+			if (context_raw.brush_stencil_scaling) {
+				let mult: i32 = mouse_x > r.x + r.w / 2 ? 1 : -1;
+				context_raw.brush_stencil_scale += mouse_movement_x / 400 * mult;
+			}
+			else if (context_raw.brush_stencil_rotating) {
+				let gizmo_x: f32 = r.x + r.w / 2;
+				let gizmo_y: f32 = r.y + r.h / 2;
+				context_raw.brush_stencil_angle = -math_atan2(mouse_y - gizmo_y, mouse_x - gizmo_x) - math_pi() / 2;
+			}
+			else {
+				context_raw.brush_stencil_x += mouse_movement_x / base_w();
+				context_raw.brush_stencil_y += mouse_movement_y / base_h();
+			}
+		}
+		else {
+			context_raw.brush_stencil_scaling = false;
+		}
+		if (mouse_wheel_delta != 0) {
+			context_raw.brush_stencil_scale -= mouse_wheel_delta / 10;
+		}
+		// Center after scale
+		let ratio: f32 = base_h() / context_raw.brush_stencil_image.height;
+		let old_w: f32 = _scale * context_raw.brush_stencil_image.width * ratio;
+		let new_w: f32 = context_raw.brush_stencil_scale * context_raw.brush_stencil_image.width * ratio;
+		let old_h: f32 = _scale * base_h();
+		let new_h: f32 = context_raw.brush_stencil_scale * base_h();
+		context_raw.brush_stencil_x += (old_w - new_w) / base_w() / 2;
+		context_raw.brush_stencil_y += (old_h - new_h) / base_h() / 2;
+	}
+
+	let set_clone_source: bool = context_raw.tool == tool_type_t.CLONE && operator_shortcut(map_get(config_keymap, "set_clone_source") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN);
+
+	let decal: bool = context_is_decal();
+	let decal_mask: bool = context_is_decal_mask_paint();
+
+	let down: bool = operator_shortcut(map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN) ||
+					 decal_mask ||
+					 set_clone_source ||
+					 operator_shortcut(map_get(config_keymap, "brush_ruler") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN) ||
+					 (pen_down() && !keyboard_down("alt"));
+
+	if (config_raw.touch_ui) {
+		if (pen_down()) {
+			context_raw.pen_painting_only = true;
+		}
+		else if (context_raw.pen_painting_only) {
+			down = false;
+		}
+	}
+
+	if (context_raw.tool == tool_type_t.PARTICLE) {
+		down = false;
+	}
+
+	if (down) {
+		let mx: i32 = mouse_view_x();
+		let my: i32 = mouse_view_y();
+		let ww: i32 = sys_w();
+
+		if (context_raw.paint2d) {
+			mx -= sys_w();
+			ww = ui_view2d_ww;
+		}
+
+		if (mx < ww &&
+			mx > sys_x() &&
+			my < sys_h() &&
+			my > sys_y()) {
+
+			if (set_clone_source) {
+				context_raw.clone_start_x = mx;
+				context_raw.clone_start_y = my;
+			}
+			else {
+				if (context_raw.brush_time == 0 &&
+					!base_is_dragging &&
+					!base_is_resizing &&
+					ui.combo_selected_handle == null) { // Paint started
+
+					// Draw line
+					if (operator_shortcut(map_get(config_keymap, "brush_ruler") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN)) {
+						context_raw.last_paint_vec_x = context_raw.last_paint_x;
+						context_raw.last_paint_vec_y = context_raw.last_paint_y;
+					}
+
+					history_push_undo = true;
+
+					if (context_raw.tool == tool_type_t.CLONE && context_raw.clone_start_x >= 0.0) { // Clone delta
+						context_raw.clone_delta_x = (context_raw.clone_start_x - mx) / ww;
+						context_raw.clone_delta_y = (context_raw.clone_start_y - my) / sys_h();
+						context_raw.clone_start_x = -1;
+					}
+					else if (context_raw.tool == tool_type_t.FILL && context_raw.fill_type_handle.position == fill_type_t.UV_ISLAND) {
+						util_uv_uvislandmap_cached = false;
+					}
+				}
+
+				context_raw.brush_time += sys_delta();
+
+				if (context_raw.run_brush != null) {
+					context_raw.run_brush(context_raw.brush_output_node_inst, 0);
+				}
+			}
+		}
+	}
+	else if (context_raw.brush_time > 0) { // Brush released
+		context_raw.brush_time = 0;
+		context_raw.prev_paint_vec_x = -1;
+		context_raw.prev_paint_vec_y = -1;
+		///if (arm_opengl || arm_direct3d11) // Keep accumulated samples for D3D12
+		context_raw.ddirty = 3;
+		///end
+		context_raw.brush_blend_dirty = true; // Update brush mask
+
+		context_raw.layer_preview_dirty = true; // Update layer preview
+
+		// New color id picked, update fill layer
+		if (context_raw.tool == tool_type_t.COLORID && context_raw.layer.fill_layer != null) {
+			sys_notify_on_next_frame(function () {
+				layers_update_fill_layer();
+				make_material_parse_paint_material(false);
+			});
+		}
+	}
+
+	if (context_raw.layers_preview_dirty) {
+		context_raw.layers_preview_dirty = false;
+		context_raw.layer_preview_dirty = false;
+		context_raw.mask_preview_last = null;
+		// Update all layer previews
+		for (let i: i32 = 0; i < project_layers.length; ++i) {
+			let l: slot_layer_t = project_layers[i];
+			if (slot_layer_is_group(l)) {
+				continue;
+			}
+
+			let target: gpu_texture_t = l.texpaint_preview;
+			if (target == null) {
+				continue;
+			}
+
+			let source: gpu_texture_t = l.texpaint;
+			draw_begin(target, true, 0x00000000);
+			// draw_set_pipeline(l.is_mask() ? pipes_copy8 : pipes_copy);
+			draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
+			draw_scaled_image(source, 0, 0, target.width, target.height);
+			draw_set_pipeline(null);
+			draw_end();
+		}
+		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+	}
+	if (context_raw.layer != null && context_raw.layer_preview_dirty && !slot_layer_is_group(context_raw.layer)) {
+		context_raw.layer_preview_dirty = false;
+		context_raw.mask_preview_last = null;
+		// Update layer preview
+		let l: slot_layer_t = context_raw.layer;
+
+		let target: gpu_texture_t = l.texpaint_preview;
+		if (target != null) {
+
+			let source: gpu_texture_t = l.texpaint;
+			draw_begin(target, true, 0x00000000);
+			// draw_set_pipeline(raw.layer.is_mask() ? pipes_copy8 : pipes_copy);
+			draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
+			draw_scaled_image(source, 0, 0, target.width, target.height);
+			draw_set_pipeline(null);
+			draw_end();
+			ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+		}
+	}
+
+	let undo_pressed: bool = operator_shortcut(map_get(config_keymap, "edit_undo"));
+	let redo_pressed: bool = operator_shortcut(map_get(config_keymap, "edit_redo")) ||
+							 (keyboard_down("control") && keyboard_started("y"));
+
+	// Two-finger tap to undo, three-finger tap to redo
+	if (context_in_viewport() && config_raw.touch_ui) {
+		if (mouse_started("middle")) {
+			ui_base_redo_tap_time = sys_time();
+		}
+		else if (mouse_started("right")) {
+			ui_base_undo_tap_time = sys_time();
+		}
+		else if (mouse_released("middle") && sys_time() - ui_base_redo_tap_time < 0.1) {
+			ui_base_redo_tap_time = ui_base_undo_tap_time = 0;
+			redo_pressed = true;
+		}
+		else if (mouse_released("right") && sys_time() - ui_base_undo_tap_time < 0.1) {
+			ui_base_redo_tap_time = ui_base_undo_tap_time = 0;
+			undo_pressed = true;
+		}
+	}
+
+	if (undo_pressed) {
+		history_undo();
+	}
+	else if (redo_pressed) {
+		history_redo();
+	}
+
+	gizmo_update();
+}
+
+function ui_base_render() {
+	if (!ui_base_show && config_raw.touch_ui) {
+		ui.input_enabled = true;
+		ui_begin(ui);
+		if (ui_window(ui_handle(__ID__), 0, 0, 150, math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET() + 1))) {
+			if (ui_button(tr("Close"))) {
+				ui_base_toggle_distract_free();
+			}
+		}
+		ui_end();
+	}
+
+	if (!ui_base_show) {
+		return;
+	}
+
+	ui.input_enabled = base_ui_enabled;
+
+	// Remember last tab positions
+	for (let i: i32 = 0; i < ui_base_htabs.length; ++i) {
+		if (ui_base_htabs[i].changed) {
+			config_raw.layout_tabs[i] = ui_base_htabs[i].position;
+			config_save();
+		}
+	}
+
+	// Set tab positions
+	for (let i: i32 = 0; i < ui_base_htabs.length; ++i) {
+		ui_base_htabs[i].position = config_raw.layout_tabs[i];
+	}
+
+	ui_begin(ui);
+	ui_toolbar_render_ui();
+	ui_menubar_render_ui();
+	ui_header_render_ui();
+	ui_statusbar_render_ui();
+	ui_sidebar_render_ui();
+	ui_end();
+
+	ui.input_enabled = true;
+}
+
+function ui_base_render_cursor() {
+	if (!base_ui_enabled) {
+		return;
+	}
+
+	if (context_raw.tool == tool_type_t.MATERIAL || context_raw.tool == tool_type_t.BAKE) {
+		return;
+	}
+
+	draw_begin();
+	draw_set_color(0xffffffff);
+
+	context_raw.view_index = context_raw.view_index_last;
+	let mx: i32 = base_x() + context_raw.paint_vec.x * base_w();
+	let my: i32 = base_y() + context_raw.paint_vec.y * base_h();
+	context_raw.view_index = -1;
+
+	// Radius being scaled
+	if (context_raw.brush_locked) {
+		mx += context_raw.lock_started_x - iron_window_width() / 2;
+		my += context_raw.lock_started_y - iron_window_height() / 2;
+	}
+
+	if (context_raw.brush_stencil_image != null &&
+		context_raw.tool != tool_type_t.BAKE &&
+		context_raw.tool != tool_type_t.PICKER &&
+		context_raw.tool != tool_type_t.MATERIAL &&
+		context_raw.tool != tool_type_t.COLORID) {
+		let r: rect_t = ui_base_get_brush_stencil_rect();
+		if (!operator_shortcut(map_get(config_keymap, "stencil_hide"), shortcut_type_t.DOWN)) {
+			draw_set_color(0x88ffffff);
+			let angle: f32 = context_raw.brush_stencil_angle;
+			draw_set_transform(mat3_multmat(mat3_multmat(mat3_translation(0.5, 0.5), mat3_rotation(-angle)), mat3_translation(-0.5, -0.5)));
+			draw_scaled_image(context_raw.brush_stencil_image, r.x, r.y, r.w, r.h);
+			draw_set_transform(mat3_nan());
+			draw_set_color(0xffffffff);
+		}
+		let transform: bool = operator_shortcut(map_get(config_keymap, "stencil_transform"), shortcut_type_t.DOWN);
+		if (transform) {
+			// Outline
+			draw_rect(r.x, r.y, r.w, r.h);
+			// Scale
+			draw_rect(r.x - 8,       r.y - 8,       16, 16);
+			draw_rect(r.x - 8 + r.w, r.y - 8,       16, 16);
+			draw_rect(r.x - 8,       r.y - 8 + r.h, 16, 16);
+			draw_rect(r.x - 8 + r.w, r.y - 8 + r.h, 16, 16);
+			// Rotate
+			let cosa: f32 = math_cos(-context_raw.brush_stencil_angle);
+			let sina: f32 = math_sin(-context_raw.brush_stencil_angle);
+			let ox: f32 = 0;
+			let oy: f32 = -r.h / 2;
+			let x: f32 = ox * cosa - oy * sina;
+			let y: f32 = ox * sina + oy * cosa;
+			x += r.x + r.w / 2;
+			y += r.y + r.h / 2;
+			draw_filled_circle(x, y, 8);
+		}
+	}
+
+	// Show picked material next to cursor
+	if (context_raw.tool == tool_type_t.PICKER && context_raw.picker_select_material && context_raw.color_picker_callback == null) {
+		let img: gpu_texture_t = context_raw.material.image_icon;
+		draw_image(img, mx + 10, my + 10);
+	}
+	if (context_raw.tool == tool_type_t.PICKER && context_raw.color_picker_callback != null) {
+		let img: gpu_texture_t = resource_get("icons.k");
+		let rect: rect_t = resource_tile50(img, tool_type_t.PICKER, 0);
+		draw_sub_image(img, mx + 10, my + 10, rect.x, rect.y, rect.w, rect.h);
+	}
+
+	let cursor_img: gpu_texture_t = resource_get("cursor.k");
+	let psize: i32 = math_floor(182 * (context_raw.brush_radius * context_raw.brush_nodes_radius) * UI_SCALE());
+
+	// Clone source cursor
+	if (context_raw.tool == tool_type_t.CLONE && !keyboard_down("alt") && (mouse_down() || pen_down())) {
+		draw_set_color(0x66ffffff);
+		draw_scaled_image(cursor_img, mx + context_raw.clone_delta_x * sys_w() - psize / 2, my + context_raw.clone_delta_y * sys_h() - psize / 2, psize, psize);
+		draw_set_color(0xffffffff);
+	}
+
+	let decal: bool = context_is_decal();
+
+	if (!config_raw.brush_3d || context_in_2d_view() || decal) {
+		let decal_mask: bool = context_is_decal_mask();
+		if (decal && !context_in_nodes()) {
+			let decal_alpha: f32 = 0.5;
+			if (!decal_mask) {
+				context_raw.decal_x = context_raw.paint_vec.x;
+				context_raw.decal_y = context_raw.paint_vec.y;
+				decal_alpha = context_raw.brush_opacity;
+
+				// Radius being scaled
+				if (context_raw.brush_locked) {
+					context_raw.decal_x += (context_raw.lock_started_x - iron_window_width() / 2) / base_w();
+					context_raw.decal_y += (context_raw.lock_started_y - iron_window_height() / 2) / base_h();
+				}
+			}
+
+			if (!config_raw.brush_live) {
+				let psizex: i32 = math_floor(256 * UI_SCALE() * (context_raw.brush_radius * context_raw.brush_nodes_radius * context_raw.brush_scale_x));
+				let psizey: i32 = math_floor(256 * UI_SCALE() * (context_raw.brush_radius * context_raw.brush_nodes_radius));
+
+				context_raw.view_index = context_raw.view_index_last;
+				let decalx: f32 = base_x() + context_raw.decal_x * base_w() - psizex / 2;
+				let decaly: f32 = base_y() + context_raw.decal_y * base_h() - psizey / 2;
+				context_raw.view_index = -1;
+
+				draw_set_color(color_from_floats(1, 1, 1, decal_alpha));
+				let angle: f32 = (context_raw.brush_angle + context_raw.brush_nodes_angle) * (math_pi() / 180);
+				draw_set_transform(mat3_multmat(mat3_multmat(mat3_translation(0.5, 0.5), mat3_rotation(angle)), mat3_translation(-0.5, -0.5)));
+				draw_scaled_image(context_raw.decal_image, decalx, decaly, psizex, psizey);
+				draw_set_transform(mat3_nan());
+				draw_set_color(0xffffffff);
+			}
+		}
+		if (context_raw.tool == tool_type_t.BRUSH  ||
+			context_raw.tool == tool_type_t.ERASER ||
+			context_raw.tool == tool_type_t.CLONE  ||
+			context_raw.tool == tool_type_t.BLUR   ||
+			context_raw.tool == tool_type_t.SMUDGE   ||
+			context_raw.tool == tool_type_t.PARTICLE ||
+			(decal_mask && !config_raw.brush_3d) ||
+			(decal_mask && context_in_2d_view())) {
+			if (decal_mask) {
+				psize = math_floor(cursor_img.width * (context_raw.brush_decal_mask_radius * context_raw.brush_nodes_radius) * UI_SCALE());
+			}
+			if (config_raw.brush_3d && context_in_2d_view()) {
+				psize = math_floor(psize * ui_view2d_pan_scale);
+			}
+			draw_scaled_image(cursor_img, mx - psize / 2, my - psize / 2, psize, psize);
+		}
+	}
+
+	if (context_raw.brush_lazy_radius > 0 && !context_raw.brush_locked &&
+		(context_raw.tool == tool_type_t.BRUSH ||
+			context_raw.tool == tool_type_t.ERASER ||
+			context_raw.tool == tool_type_t.DECAL ||
+			context_raw.tool == tool_type_t.TEXT ||
+			context_raw.tool == tool_type_t.CLONE ||
+			context_raw.tool == tool_type_t.BLUR ||
+			context_raw.tool == tool_type_t.SMUDGE ||
+			context_raw.tool == tool_type_t.PARTICLE)) {
+		draw_filled_rect(mx - 1, my - 1, 2, 2);
+		mx = context_raw.brush_lazy_x * base_w() + base_x();
+		my = context_raw.brush_lazy_y * base_h() + base_y();
+		let radius: f32 = context_raw.brush_lazy_radius * 180;
+		draw_set_color(0xff666666);
+		draw_scaled_image(cursor_img, mx - radius / 2, my - radius / 2, radius, radius);
+		draw_set_color(0xffffffff);
+	}
+	draw_end();
+}
+
+function ui_base_show_material_nodes() {
+	// Clear input state as ui receives input events even when not drawn
+	ui_end_input();
+
+	ui_nodes_show = !ui_nodes_show || ui_nodes_canvas_type != canvas_type_t.MATERIAL;
+	ui_nodes_canvas_type = canvas_type_t.MATERIAL;
+
+	///if (arm_ios || arm_android)
+	if (ui_view2d_show) {
+		ui_view2d_show = false;
+	}
 	///end
-	raw.camera_zoom_speed = 1.0;
-	raw.camera_pan_speed = 1.0;
-	raw.camera_rotation_speed = 1.0;
-	raw.zoom_direction = zoom_direction_t.VERTICAL;
-	raw.displace_strength = 0.0;
-	raw.wrap_mouse = false;
-	raw.workspace = space_type_t.SPACE3D;
-	raw.camera_controls = camera_controls_t.ORBIT;
-	raw.layer_res = texture_res_t.RES2048;
-	///if (arm_android || arm_ios)
-	raw.touch_ui = true;
-	raw.splash_screen = true;
-	///else
-	raw.touch_ui = false;
-	raw.splash_screen = false;
+
+	base_resize();
+}
+
+function ui_base_show_brush_nodes() {
+	// Clear input state as ui receives input events even when not drawn
+	ui_end_input();
+	ui_nodes_show = !ui_nodes_show || ui_nodes_canvas_type != canvas_type_t.BRUSH;
+	ui_nodes_canvas_type = canvas_type_t.BRUSH;
+
+	///if (arm_ios || arm_android)
+	if (ui_view2d_show) {
+		ui_view2d_show = false;
+	}
 	///end
-	raw.node_preview = true;
-	raw.pressure_hardness = true;
-	raw.pressure_angle = false;
-	raw.pressure_opacity = false;
-	///if (arm_android || arm_ios)
-	raw.material_live = false;
-	///else
-	raw.material_live = true;
+
+	base_resize();
+}
+
+function ui_base_show_2d_view(type: view_2d_type_t) {
+	// Clear input state as ui receives input events even when not drawn
+	ui_end_input();
+	if (ui_view2d_type != type) {
+		ui_view2d_show = true;
+	}
+	else {
+		ui_view2d_show = !ui_view2d_show;
+	}
+	ui_view2d_type = type;
+	ui_view2d_hwnd.redraws = 2;
+
+	///if (arm_ios || arm_android)
+	if (ui_nodes_show) {
+		ui_nodes_show = false;
+	}
 	///end
-	raw.brush_3d = true;
-	raw.brush_depth_reject = true;
-	raw.brush_angle_reject = true;
-	raw.brush_live = false;
-	raw.show_asset_names = false;
-	raw.dilate = dilate_type_t.INSTANT;
-	raw.dilate_radius = 2;
-	raw.gpu_inference = true;
-	raw.blender = "";
-	raw.scene_atlas_res = texture_res_t.RES8192;
-	raw.pathtrace_mode = pathtrace_mode_t.FAST;
-	raw.grid_snap = false;
+
+	base_resize();
+}
+
+function ui_base_toggle_browser() {
+	let minimized: bool = config_raw.layout[layout_size_t.STATUS_H] <= (ui_statusbar_default_h * config_raw.window_scale);
+	config_raw.layout[layout_size_t.STATUS_H] = minimized ? 240 : ui_statusbar_default_h;
+	config_raw.layout[layout_size_t.STATUS_H] = math_floor(config_raw.layout[layout_size_t.STATUS_H] * config_raw.window_scale);
+}
+
+function ui_base_set_icon_scale() {
+	if (UI_SCALE() > 1) {
+		let res: string[] = ["icons2x.k"];
+		resource_load(res);
+		map_set(resource_bundled, "icons.k", resource_get("icons2x.k"));
+	}
+	else {
+		let res: string[] = ["icons.k"];
+		resource_load(res);
+	}
+}
+
+function ui_base_on_border_hover(handle: ui_handle_t, side: i32) {
+	if (!base_ui_enabled) {
+		return;
+	}
+
+	if (handle != ui_base_hwnds[tab_area_t.SIDEBAR0] &&
+		handle != ui_base_hwnds[tab_area_t.SIDEBAR1] &&
+		handle != ui_base_hwnds[tab_area_t.STATUS] &&
+		handle != ui_nodes_hwnd &&
+		handle != ui_view2d_hwnd) {
+		return; // Scalable handles
+	}
+	if (handle == ui_view2d_hwnd && side != border_side_t.LEFT) {
+		return;
+	}
+	if (handle == ui_nodes_hwnd && side == border_side_t.TOP && !ui_view2d_show) {
+		return;
+	}
+	if (handle == ui_base_hwnds[tab_area_t.SIDEBAR0] && side == border_side_t.TOP) {
+		return;
+	}
+
+	if (handle == ui_nodes_hwnd && side != border_side_t.LEFT && side != border_side_t.TOP) {
+		return;
+	}
+	if (handle == ui_base_hwnds[tab_area_t.STATUS] && side != border_side_t.TOP) {
+		return;
+	}
+	if (side == border_side_t.RIGHT) {
+		return; // UI is snapped to the right side
+	}
+
+	side == border_side_t.LEFT || side == border_side_t.RIGHT ?
+		iron_mouse_set_cursor(cursor_t.SIZEWE) :
+		iron_mouse_set_cursor(cursor_t.SIZENS);
+
+	if (ui.input_started) {
+		ui_base_border_started = side;
+		ui_base_border_handle = handle;
+		base_is_resizing = true;
+	}
+}
+
+function ui_base_on_tab_drop(to: ui_handle_t, to_position: i32, from: ui_handle_t, from_position: i32) {
+	let i: i32 = -1;
+	let j: i32 = -1;
+	for (let k: i32 = 0; k < ui_base_htabs.length; ++k) {
+		if (ui_base_htabs[k] == to) {
+			i = k;
+		}
+		if (ui_base_htabs[k] == from) {
+			j = k;
+		}
+	}
+	if (i == j && to_position == from_position) {
+		return;
+	}
+	if (i > -1 && j > -1) {
+		let tabsi: tab_draw_t[] = ui_base_hwnd_tabs[i];
+		let tabsj: tab_draw_t[] = ui_base_hwnd_tabs[j];
+		let element: tab_draw_t = tabsj[from_position];
+		array_splice(tabsj, from_position, 1);
+		array_insert(tabsi, to_position, element);
+		ui_base_hwnds[i].redraws = 2;
+		ui_base_hwnds[j].redraws = 2;
+	}
+}
+
+function ui_base_tag_ui_redraw() {
+	ui_header_handle.redraws = 2;
+	ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
+	ui_menubar_workspace_handle.redraws = 2;
+	ui_menubar_menu_handle.redraws = 2;
+	ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+	ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
+	ui_toolbar_handle.redraws = 2;
+}
+
+function ui_base_make_empty_envmap(col: i32) {
+	ui_base_viewport_col = col;
+	let b: u8_array_t = u8_array_create(4);
+	b[0] = color_get_rb(col);
+	b[1] = color_get_gb(col);
+	b[2] = color_get_bb(col);
+	b[3] = 255;
+	context_raw.empty_envmap = gpu_create_texture_from_bytes(b, 1, 1);
+}
+
+function ui_base_set_viewport_col(col: i32) {
+	ui_base_make_empty_envmap(col);
+	context_raw.ddirty = 2;
+	if (!context_raw.show_envmap) {
+		scene_world._.envmap = context_raw.empty_envmap;
+	}
 }

+ 2 - 2
paint/sources/box_preferences.ts

@@ -866,11 +866,11 @@ function box_preferences_set_scale() {
 	let scale: f32 = config_raw.window_scale;
 	ui_set_scale(scale);
 	ui_header_h = math_floor(ui_header_default_h * scale);
-	config_raw.layout[layout_size_t.STATUS_H] = math_floor(ui_status_default_status_h * scale);
+	config_raw.layout[layout_size_t.STATUS_H] = math_floor(ui_statusbar_default_h * scale);
 	ui_menubar_w = math_floor(ui_menubar_default_w * scale);
 	ui_base_set_icon_scale();
 	base_resize();
-	config_raw.layout[layout_size_t.SIDEBAR_W] = math_floor(ui_base_default_sidebar_w * scale);
+	config_raw.layout[layout_size_t.SIDEBAR_W] = math_floor(ui_sidebar_default_w * scale);
 }
 
 function box_preferences_theme_to_json(theme: ui_theme_t): string {

+ 2 - 2
paint/sources/brush_nodes/brush_output_node.ts

@@ -146,8 +146,8 @@ function brush_output_node_run(self: brush_output_node_t, from: i32) {
 	if (ui.is_hovered ||
 		base_is_dragging ||
 		base_is_resizing ||
-		base_is_scrolling() ||
-		base_is_combo_selected()) {
+		ui.is_scrolling ||
+		ui.combo_selected_handle != null) {
 		return;
 	}
 

+ 1 - 1
paint/sources/camera.ts

@@ -64,7 +64,7 @@ function camera_update() {
 		camera_controls_down = false;
 	}
 
-	if (_input_occupied || !base_ui_enabled || base_is_dragging || base_is_scrolling() || base_is_combo_selected() || !camera_controls_down) {
+	if (_input_occupied || !base_ui_enabled || base_is_dragging || ui.is_scrolling || ui.combo_selected_handle != null || !camera_controls_down) {
 		return;
 	}
 

+ 55 - 1
paint/sources/config.ts

@@ -155,7 +155,61 @@ function config_init() {
 		///end
 		config_raw.version = manifest_version;
 		config_raw.sha = config_get_sha();
-		base_init_config();
+
+		config_raw.recent_projects = [];
+		config_raw.bookmarks = [];
+		config_raw.plugins = [];
+		///if (arm_android || arm_ios)
+		config_raw.keymap = "touch.json";
+		///else
+		config_raw.keymap = "default.json";
+		///end
+		config_raw.theme = "default.json";
+		config_raw.server = "https://armorpaint.fra1.digitaloceanspaces.com";
+		config_raw.undo_steps = 4;
+		config_raw.pressure_radius = true;
+		///if (arm_ios || arm_linux)
+		config_raw.pressure_sensitivity = 1.0;
+		///else
+		config_raw.pressure_sensitivity = 2.0;
+		///end
+		config_raw.camera_zoom_speed = 1.0;
+		config_raw.camera_pan_speed = 1.0;
+		config_raw.camera_rotation_speed = 1.0;
+		config_raw.zoom_direction = zoom_direction_t.VERTICAL;
+		config_raw.displace_strength = 0.0;
+		config_raw.wrap_mouse = false;
+		config_raw.workspace = space_type_t.SPACE3D;
+		config_raw.camera_controls = camera_controls_t.ORBIT;
+		config_raw.layer_res = texture_res_t.RES2048;
+		///if (arm_android || arm_ios)
+		config_raw.touch_ui = true;
+		config_raw.splash_screen = true;
+		///else
+		config_raw.touch_ui = false;
+		config_raw.splash_screen = false;
+		///end
+		config_raw.node_preview = true;
+		config_raw.pressure_hardness = true;
+		config_raw.pressure_angle = false;
+		config_raw.pressure_opacity = false;
+		///if (arm_android || arm_ios)
+		config_raw.material_live = false;
+		///else
+		config_raw.material_live = true;
+		///end
+		config_raw.brush_3d = true;
+		config_raw.brush_depth_reject = true;
+		config_raw.brush_angle_reject = true;
+		config_raw.brush_live = false;
+		config_raw.show_asset_names = false;
+		config_raw.dilate = dilate_type_t.INSTANT;
+		config_raw.dilate_radius = 2;
+		config_raw.gpu_inference = true;
+		config_raw.blender = "";
+		config_raw.scene_atlas_res = texture_res_t.RES8192;
+		config_raw.pathtrace_mode = pathtrace_mode_t.FAST;
+		config_raw.grid_snap = false;
 	}
 	else {
 		// Upgrade config format created by older ArmorPaint build

+ 49 - 49
paint/sources/parser_exr.ts → paint/sources/export_exr.ts

@@ -2,49 +2,49 @@
 // https://github.com/aras-p/miniexr
 // https://www.openexr.com/documentation/openexrfilelayout.pdf
 
-let _parser_exr_width: i32;
-let _parser_exr_stride: i32;
-let _parser_exr_out: u8[];
-let _parser_exr_src_view: buffer_t;
-let _parser_exr_write_line: (byte_pos: i32)=> void;
+let _export_exr_width: i32;
+let _export_exr_stride: i32;
+let _export_exr_out: u8[];
+let _export_exr_src_view: buffer_t;
+let _export_exr_write_line: (byte_pos: i32)=> void;
 
-function parser_exr_write_string(out: u8[], str: string) {
+function export_exr_write_string(out: u8[], str: string) {
 	for (let i: i32 = 0; i < str.length; ++i) {
 		array_push(out, char_code_at(str, i));
 	}
 }
 
-function parser_exr_write_line16(byte_pos: i32) {
-	for (let x: i32 = 0; x < _parser_exr_width; ++x) {
-		array_push(_parser_exr_out, buffer_get_u8(_parser_exr_src_view, byte_pos    ));
-		array_push(_parser_exr_out, buffer_get_u8(_parser_exr_src_view, byte_pos + 1));
-		byte_pos += _parser_exr_stride;
+function export_exr_write_line16(byte_pos: i32) {
+	for (let x: i32 = 0; x < _export_exr_width; ++x) {
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos    ));
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos + 1));
+		byte_pos += _export_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_get_u8(_parser_exr_src_view, byte_pos    ));
-		array_push(_parser_exr_out, buffer_get_u8(_parser_exr_src_view, byte_pos + 1));
-		array_push(_parser_exr_out, buffer_get_u8(_parser_exr_src_view, byte_pos + 2));
-		array_push(_parser_exr_out, buffer_get_u8(_parser_exr_src_view, byte_pos + 3));
-		byte_pos += _parser_exr_stride;
+function export_exr_write_line32(byte_pos: i32) {
+	for (let x: i32 = 0; x < _export_exr_width; ++x) {
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos    ));
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos + 1));
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos + 2));
+		array_push(_export_exr_out, buffer_get_u8(_export_exr_src_view, byte_pos + 3));
+		byte_pos += _export_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 export_exr_write_bgr(off: i32, pos: i32, byte_size: i32) {
+	_export_exr_write_line(pos + byte_size * 2);
+	_export_exr_write_line(pos + byte_size);
+	_export_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 export_exr_write_single(off: i32, pos: i32, byte_size: i32) {
+	_export_exr_write_line(pos + off * byte_size);
+	_export_exr_write_line(pos + off * byte_size);
+	_export_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 {
+function export_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
 	array_push(out, 0x2f);
@@ -54,9 +54,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, 0);
 	array_push(out, 0);
 	array_push(out, 0);
-	parser_exr_write_string(out, "channels");
+	export_exr_write_string(out, "channels");
 	array_push(out, 0);
-	parser_exr_write_string(out, "chlist");
+	export_exr_write_string(out, "chlist");
 	array_push(out, 0);
 
 	array_push(out, 55);
@@ -137,9 +137,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 
 	array_push(out, 0);
 
-	parser_exr_write_string(out, "compression");
+	export_exr_write_string(out, "compression");
 	array_push(out, 0);
-	parser_exr_write_string(out, "compression");
+	export_exr_write_string(out, "compression");
 	array_push(out, 0);
 
 	array_push(out, 1);
@@ -148,9 +148,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, 0);
 	array_push(out, 0); // no compression
 
-	parser_exr_write_string(out, "dataWindow");
+	export_exr_write_string(out, "dataWindow");
 	array_push(out, 0);
-	parser_exr_write_string(out, "box2i");
+	export_exr_write_string(out, "box2i");
 	array_push(out, 0);
 
 	array_push(out, 16);
@@ -181,9 +181,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, (hh >> 16) & 0xff);
 	array_push(out, (hh >> 24) & 0xff);
 
-	parser_exr_write_string(out, "displayWindow");
+	export_exr_write_string(out, "displayWindow");
 	array_push(out, 0);
-	parser_exr_write_string(out, "box2i");
+	export_exr_write_string(out, "box2i");
 	array_push(out, 0);
 
 	array_push(out, 16);
@@ -211,9 +211,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, (hh >> 16) & 0xff);
 	array_push(out, (hh >> 24) & 0xff);
 
-	parser_exr_write_string(out, "lineOrder");
+	export_exr_write_string(out, "lineOrder");
 	array_push(out, 0);
-	parser_exr_write_string(out, "lineOrder");
+	export_exr_write_string(out, "lineOrder");
 	array_push(out, 0);
 
 	array_push(out, 1);
@@ -222,9 +222,9 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, 0);
 	array_push(out, 0); // increasing Y
 
-	parser_exr_write_string(out, "pixelAspectRatio");
+	export_exr_write_string(out, "pixelAspectRatio");
 	array_push(out, 0);
-	parser_exr_write_string(out, "float");
+	export_exr_write_string(out, "float");
 	array_push(out, 0);
 
 	array_push(out, 4);
@@ -237,10 +237,10 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, 0x80);
 	array_push(out, 0x3f);
 
-	parser_exr_write_string(out, "screenWindowCenter");
+	export_exr_write_string(out, "screenWindowCenter");
 	array_push(out, 0);
 
-	parser_exr_write_string(out, "v2f");
+	export_exr_write_string(out, "v2f");
 	array_push(out, 0);
 
 	array_push(out, 8);
@@ -258,10 +258,10 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	array_push(out, 0);
 	array_push(out, 0);
 
-	parser_exr_write_string(out, "screenWindowWidth");
+	export_exr_write_string(out, "screenWindowWidth");
 	array_push(out, 0);
 
-	parser_exr_write_string(out, "float");
+	export_exr_write_string(out, "float");
 	array_push(out, 0);
 
 	array_push(out, 4);
@@ -301,13 +301,13 @@ function parser_exr_run(width: i32, height: i32, src: buffer_t, bits: i32 = 16,
 	let stride: i32 = channels * byte_size;
 	let pos: i32 = 0;
 
-	_parser_exr_width = width;
-	_parser_exr_stride = stride;
-	_parser_exr_out = out;
-	_parser_exr_src_view = src;
+	_export_exr_width = width;
+	_export_exr_stride = stride;
+	_export_exr_out = out;
+	_export_exr_src_view = src;
 
-	_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;
+	_export_exr_write_line = bits == 16 ? export_exr_write_line16 : export_exr_write_line32;
+	let write_data: (off: i32, pos: i32, byte_size: i32)=>void = type == 1 ? export_exr_write_bgr : export_exr_write_single;
 
 	for (let y: i32 = 0; y < height; ++y) {
 		// coordinate

+ 1 - 1
paint/sources/export_texture.ts

@@ -465,7 +465,7 @@ function export_texture_write_texture(file: string, pixels: buffer_t, type: i32
 		iron_write_jpg(file, pixels, res_x, res_y, format, math_floor(context_raw.format_quality));
 	}
 	else { // Exr
-		let b: buffer_t = parser_exr_run(res_x, res_y, pixels, bits, type, off);
+		let b: buffer_t = export_exr_run(res_x, res_y, pixels, bits, type, off);
 		iron_file_save_bytes(file, b, b.length);
 	}
 }

+ 1 - 1
paint/sources/import_mesh.ts

@@ -201,7 +201,7 @@ function _import_mesh_add_mesh(mesh: raw_mesh_t) {
 	let raw: mesh_data_t = import_mesh_raw_mesh(mesh);
 
 	if (context_raw.tool == tool_type_t.GIZMO) {
-		util_mesh_ext_pack_uvs(mesh.texa);
+		util_mesh_pack_uvs(mesh.texa);
 	}
 
 	let md: mesh_data_t = mesh_data_create(raw);

+ 2 - 2
paint/sources/neural_nodes/brush_output_node_ext.ts

@@ -103,8 +103,8 @@ function brush_output_node_run(from: i32) {
 		context_raw.paint_vec.y < 1 &&
 		!base_is_dragging &&
 		!base_is_resizing &&
-		!base_is_scrolling() &&
-		!base_is_combo_selected()) {
+		!ui.is_scrolling &&
+		ui.combo_selected_handle == null) {
 
 		let down: bool = mouse_down() || pen_down();
 

+ 131 - 0
paint/sources/node_shader.ts

@@ -240,3 +240,134 @@ function node_shader_get(raw: node_shader_t): string {
 
 	return s;
 }
+
+
+type material_t = {
+	name?: string;
+	canvas?: ui_node_canvas_t;
+};
+
+type node_shader_context_t = {
+	kong?: node_shader_t;
+	data?: shader_context_t;
+	allow_vcols?: bool;
+	material?: material_t;
+};
+
+function node_shader_context_create(material: material_t, props: shader_context_t): node_shader_context_t {
+	let raw: node_shader_context_t = {};
+	raw.material = material;
+
+	let vertex_elements_default: vertex_element_t[] = [
+		{
+			name: "pos",
+			data: "short4norm"
+		},
+		{
+			name: "nor",
+			data: "short2norm"
+		}
+	];
+
+	raw.data = {
+		name: props.name,
+		depth_write: props.depth_write,
+		compare_mode: props.compare_mode,
+		cull_mode: props.cull_mode,
+		blend_source: props.blend_source,
+		blend_destination: props.blend_destination,
+		alpha_blend_source: props.alpha_blend_source,
+		alpha_blend_destination: props.alpha_blend_destination,
+		fragment_shader: "",
+		vertex_shader: "",
+		vertex_elements: props.vertex_elements != null ? props.vertex_elements : vertex_elements_default,
+		color_attachments: props.color_attachments,
+		depth_attachment: props.depth_attachment
+	};
+
+	let rw: shader_context_t = raw.data;
+	rw._ = {};
+
+	if (props.color_writes_red != null) {
+		raw.data.color_writes_red = props.color_writes_red;
+	}
+	if (props.color_writes_green != null) {
+		raw.data.color_writes_green = props.color_writes_green;
+	}
+	if (props.color_writes_blue != null) {
+		raw.data.color_writes_blue = props.color_writes_blue;
+	}
+	if (props.color_writes_alpha != null) {
+		raw.data.color_writes_alpha = props.color_writes_alpha;
+	}
+
+	raw.data.texture_units = [];
+	raw.data.constants = [];
+	return raw;
+}
+
+function node_shader_context_add_elem(raw: node_shader_context_t, name: string, data_type: string) {
+	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
+		let e: vertex_element_t = raw.data.vertex_elements[i];
+		if (e.name == name) {
+			return;
+		}
+	}
+	let elem: vertex_element_t = { name: name, data: data_type };
+	array_push(raw.data.vertex_elements, elem);
+}
+
+function node_shader_context_is_elem(raw: node_shader_context_t, name: string): bool {
+	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
+		let elem: vertex_element_t = raw.data.vertex_elements[i];
+		if (elem.name == name) {
+			return true;
+		}
+	}
+	return false;
+}
+
+function node_shader_context_get_elem(raw: node_shader_context_t, name: string): vertex_element_t {
+	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
+		let elem: vertex_element_t = raw.data.vertex_elements[i];
+		if (elem.name == name) {
+			return elem;
+		}
+	}
+	return null;
+}
+
+function node_shader_context_add_constant(raw: node_shader_context_t, ctype: string, name: string, link: string = null) {
+	for (let i: i32 = 0; i < raw.data.constants.length; ++i) {
+		let c: shader_const_t = raw.data.constants[i];
+		if (c.name == name) {
+			return;
+		}
+	}
+
+	let c: shader_const_t = { name: name, type: ctype };
+	if (link != null) {
+		c.link = link;
+	}
+	let consts: shader_const_t[] = raw.data.constants;
+	array_push(consts, c);
+}
+
+function node_shader_context_add_texture_unit(raw: node_shader_context_t, name: string, link: string = null) {
+	for (let i: i32 = 0; i < raw.data.texture_units.length; ++i) {
+		let c: tex_unit_t = raw.data.texture_units[i];
+		if (c.name == name) {
+			return;
+		}
+	}
+
+	let c: tex_unit_t = { name: name, link: link };
+	array_push(raw.data.texture_units, c);
+}
+
+function node_shader_context_make_kong(raw: node_shader_context_t): node_shader_t {
+	raw.data.vertex_shader = raw.material.name + "_" + raw.data.name + ".vert";
+	raw.data.fragment_shader = raw.material.name + "_" + raw.data.name + ".frag";
+	raw.kong = node_shader_create(raw);
+	return raw.kong;
+}

+ 0 - 130
paint/sources/node_shader_context.ts

@@ -1,130 +0,0 @@
-
-type material_t = {
-	name?: string;
-	canvas?: ui_node_canvas_t;
-};
-
-type node_shader_context_t = {
-	kong?: node_shader_t;
-	data?: shader_context_t;
-	allow_vcols?: bool;
-	material?: material_t;
-};
-
-function node_shader_context_create(material: material_t, props: shader_context_t): node_shader_context_t {
-	let raw: node_shader_context_t = {};
-	raw.material = material;
-
-	let vertex_elements_default: vertex_element_t[] = [
-		{
-			name: "pos",
-			data: "short4norm"
-		},
-		{
-			name: "nor",
-			data: "short2norm"
-		}
-	];
-
-	raw.data = {
-		name: props.name,
-		depth_write: props.depth_write,
-		compare_mode: props.compare_mode,
-		cull_mode: props.cull_mode,
-		blend_source: props.blend_source,
-		blend_destination: props.blend_destination,
-		alpha_blend_source: props.alpha_blend_source,
-		alpha_blend_destination: props.alpha_blend_destination,
-		fragment_shader: "",
-		vertex_shader: "",
-		vertex_elements: props.vertex_elements != null ? props.vertex_elements : vertex_elements_default,
-		color_attachments: props.color_attachments,
-		depth_attachment: props.depth_attachment
-	};
-
-	let rw: shader_context_t = raw.data;
-	rw._ = {};
-
-	if (props.color_writes_red != null) {
-		raw.data.color_writes_red = props.color_writes_red;
-	}
-	if (props.color_writes_green != null) {
-		raw.data.color_writes_green = props.color_writes_green;
-	}
-	if (props.color_writes_blue != null) {
-		raw.data.color_writes_blue = props.color_writes_blue;
-	}
-	if (props.color_writes_alpha != null) {
-		raw.data.color_writes_alpha = props.color_writes_alpha;
-	}
-
-	raw.data.texture_units = [];
-	raw.data.constants = [];
-	return raw;
-}
-
-function node_shader_context_add_elem(raw: node_shader_context_t, name: string, data_type: string) {
-	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
-		let e: vertex_element_t = raw.data.vertex_elements[i];
-		if (e.name == name) {
-			return;
-		}
-	}
-	let elem: vertex_element_t = { name: name, data: data_type };
-	array_push(raw.data.vertex_elements, elem);
-}
-
-function node_shader_context_is_elem(raw: node_shader_context_t, name: string): bool {
-	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
-		let elem: vertex_element_t = raw.data.vertex_elements[i];
-		if (elem.name == name) {
-			return true;
-		}
-	}
-	return false;
-}
-
-function node_shader_context_get_elem(raw: node_shader_context_t, name: string): vertex_element_t {
-	for (let i: i32 = 0; i < raw.data.vertex_elements.length; ++i) {
-		let elem: vertex_element_t = raw.data.vertex_elements[i];
-		if (elem.name == name) {
-			return elem;
-		}
-	}
-	return null;
-}
-
-function node_shader_context_add_constant(raw: node_shader_context_t, ctype: string, name: string, link: string = null) {
-	for (let i: i32 = 0; i < raw.data.constants.length; ++i) {
-		let c: shader_const_t = raw.data.constants[i];
-		if (c.name == name) {
-			return;
-		}
-	}
-
-	let c: shader_const_t = { name: name, type: ctype };
-	if (link != null) {
-		c.link = link;
-	}
-	let consts: shader_const_t[] = raw.data.constants;
-	array_push(consts, c);
-}
-
-function node_shader_context_add_texture_unit(raw: node_shader_context_t, name: string, link: string = null) {
-	for (let i: i32 = 0; i < raw.data.texture_units.length; ++i) {
-		let c: tex_unit_t = raw.data.texture_units[i];
-		if (c.name == name) {
-			return;
-		}
-	}
-
-	let c: tex_unit_t = { name: name, link: link };
-	array_push(raw.data.texture_units, c);
-}
-
-function node_shader_context_make_kong(raw: node_shader_context_t): node_shader_t {
-	raw.data.vertex_shader = raw.material.name + "_" + raw.data.name + ".vert";
-	raw.data.fragment_shader = raw.material.name + "_" + raw.data.name + ".frag";
-	raw.kong = node_shader_create(raw);
-	return raw.kong;
-}

+ 0 - 0
paint/sources/sim.ts → paint/sources/physics_sim.ts


+ 1 - 1
paint/sources/tab_browser.ts

@@ -15,7 +15,7 @@ function tab_browser_show_directory(directory: string) {
 
 function tab_browser_draw(htab: ui_handle_t) {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, tr("Browser")) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, tr("Browser")) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		if (config_raw.bookmarks == null) {
 			config_raw.bookmarks = [];

+ 1 - 1
paint/sources/tab_console.ts

@@ -4,7 +4,7 @@ function tab_console_draw(htab: ui_handle_t) {
 	let color: i32 = console_message_timer > 0 ? console_message_color : -1;
 
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, title, false, color) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, title, false, color) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		ui_begin_sticky();
 		///if (arm_windows || arm_linux || arm_macos) // Copy

+ 1 - 1
paint/sources/tab_fonts.ts

@@ -3,7 +3,7 @@ let _tab_fonts_draw_i: i32;
 
 function tab_fonts_draw(htab: ui_handle_t) {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, tr("Fonts")) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, tr("Fonts")) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		ui_begin_sticky();
 		if (config_raw.touch_ui) {

+ 2 - 2
paint/sources/tab_layers.ts

@@ -6,7 +6,7 @@ let tab_layers_l: slot_layer_t;
 let tab_layers_mini: bool;
 
 function tab_layers_draw(htab: ui_handle_t) {
-	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
+	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_sidebar_w_mini;
 	mini ? tab_layers_draw_mini(htab) : tab_layers_draw_full(htab);
 }
 
@@ -14,7 +14,7 @@ function tab_layers_draw_mini(htab: ui_handle_t) {
 	ui_set_hovered_tab_name(tr("Layers"));
 
 	let _ELEMENT_H: i32 = ui.ops.theme.ELEMENT_H;
-	ui.ops.theme.ELEMENT_H = math_floor(ui_base_sidebar_mini_w / 2 / UI_SCALE());
+	ui.ops.theme.ELEMENT_H = math_floor(ui_sidebar_w_mini / 2 / UI_SCALE());
 
 	ui_begin_sticky();
 	ui_separator(5);

+ 2 - 2
paint/sources/tab_materials.ts

@@ -2,7 +2,7 @@
 let _tab_materials_draw_slots: i32;
 
 function tab_materials_draw(htab: ui_handle_t) {
-	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
+	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_sidebar_w_mini;
 	mini ? tab_materials_draw_mini(htab) : tab_materials_draw_full(htab);
 }
 
@@ -104,7 +104,7 @@ function tab_materials_draw_slots(mini: bool) {
 			let uix: f32 = ui._x;
 			let uiy: f32 = ui._y;
 			let tile: i32 = UI_SCALE() > 1 ? 100 : 50;
-			let imgh: f32 = mini ? ui_base_default_sidebar_mini_w * 0.85 * UI_SCALE() : -1.0;
+			let imgh: f32 = mini ? ui_sidebar_default_w_mini * 0.85 * UI_SCALE() : -1.0;
 			let state: ui_state_t = project_materials[i].preview_ready ?
 				ui_image(img, 0xffffffff, imgh) :
 				ui_sub_image(resource_get("icons.k"), 0xffffffff, -1.0, tile, tile, tile, tile);

+ 2 - 2
paint/sources/tab_meshes.ts

@@ -3,7 +3,7 @@ let _tab_meshes_draw_i: i32;
 
 function tab_meshes_draw(htab: ui_handle_t) {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, tr("Meshes")) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, tr("Meshes")) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		ui_begin_sticky();
 
@@ -347,7 +347,7 @@ function tab_meshes_draw(htab: ui_handle_t) {
 function tab_meshes_append_shape(mesh_name: string) {
 	let blob: buffer_t = iron_load_blob(data_path() + "meshes/" + mesh_name + ".arm");
 	let raw: scene_t = armpack_decode(blob);
-	util_mesh_ext_pack_uvs(raw.mesh_datas[0].vertex_arrays[2].values);
+	util_mesh_pack_uvs(raw.mesh_datas[0].vertex_arrays[2].values);
 	let md: mesh_data_t = mesh_data_create(raw.mesh_datas[0]);
 	md._.handle = md.name;
 	let mo: mesh_object_t = scene_add_mesh_object(md, project_paint_objects[0].material);

+ 1 - 1
paint/sources/tab_swatches.ts

@@ -21,7 +21,7 @@ function tab_swatches_empty_get(): gpu_texture_t {
 
 function tab_swatches_draw(htab: ui_handle_t) {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, tr("Swatches")) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, tr("Swatches")) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		ui_begin_sticky();
 		if (config_raw.touch_ui) {

+ 1 - 1
paint/sources/tab_textures.ts

@@ -7,7 +7,7 @@ let _tab_textures_draw_is_packed: bool;
 
 function tab_textures_draw(htab: ui_handle_t) {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-	if (ui_tab(htab, tr("Textures")) && statush > ui_status_default_status_h * UI_SCALE()) {
+	if (ui_tab(htab, tr("Textures")) && statush > ui_statusbar_default_h * UI_SCALE()) {
 
 		ui_begin_sticky();
 

+ 0 - 1437
paint/sources/ui_base.ts

@@ -1,1437 +0,0 @@
-
-type tab_draw_t = {
-	f: (h: ui_handle_t)=>void;
-};
-type tab_draw_array_t = tab_draw_t[];
-
-let ui: ui_t;
-let ui_base_show: bool = true;
-let ui_base_border_started: i32 = 0;
-let ui_base_border_handle: ui_handle_t = null;
-let ui_base_action_paint_remap: string = "";
-let ui_base_operator_search_offset: i32 = 0;
-let ui_base_undo_tap_time: f32 = 0.0;
-let ui_base_redo_tap_time: f32 = 0.0;
-let ui_base_hwnds: ui_handle_t[] = ui_base_init_hwnds();
-let ui_base_htabs: ui_handle_t[] = ui_base_init_htabs();
-let ui_base_hwnd_tabs: tab_draw_array_t[] = ui_base_init_hwnd_tabs();
-let ui_base_viewport_col: i32;
-let ui_base_default_sidebar_mini_w: i32 = 56;
-let ui_base_default_sidebar_full_w: i32 = 280;
-
-///if (arm_android || arm_ios)
-let ui_base_default_sidebar_w: i32 = ui_base_default_sidebar_mini_w;
-///else
-let ui_base_default_sidebar_w: i32 = ui_base_default_sidebar_full_w;
-///end
-
-let ui_base_tabx: i32 = 0;
-let ui_base_hminimized: ui_handle_t = ui_handle_create();
-let ui_base_sidebar_mini_w: i32 = ui_base_default_sidebar_mini_w;
-let _ui_base_operator_search_first: bool;
-
-function ui_base_init_hwnds(): ui_handle_t[] {
-	let hwnds: ui_handle_t[] = [ui_handle_create(), ui_handle_create(), ui_handle_create()];
-	return hwnds;
-}
-
-function ui_base_init_htabs(): ui_handle_t[] {
-	let htabs: ui_handle_t[] = [ui_handle_create(), ui_handle_create(), ui_handle_create()];
-	return htabs;
-}
-
-function _draw_callback_create(f: (h: ui_handle_t)=>void): tab_draw_t {
-	let cb: tab_draw_t = { f: f };
-	return cb;
-}
-
-function ui_base_init_hwnd_tabs(): tab_draw_array_t[] {
-	let a0: tab_draw_array_t = [
-		_draw_callback_create(tab_layers_draw),
-		_draw_callback_create(tab_history_draw),
-		_draw_callback_create(tab_plugins_draw)
-	];
-	let a1: tab_draw_array_t = [
-		_draw_callback_create(tab_materials_draw),
-		_draw_callback_create(tab_brushes_draw),
-		_draw_callback_create(tab_scripts_draw)
-
-	];
-	let a2: tab_draw_array_t = [
-		_draw_callback_create(tab_browser_draw),
-		_draw_callback_create(tab_meshes_draw),
-		_draw_callback_create(tab_textures_draw),
-		_draw_callback_create(tab_fonts_draw),
-		_draw_callback_create(tab_swatches_draw),
-		_draw_callback_create(tab_console_draw),
-		_draw_callback_create(ui_status_draw_version_tab)
-	];
-
-    let r: tab_draw_array_t[] = [];
-	array_push(r, a0);
-	array_push(r, a1);
-	array_push(r, a2);
-	return r;
-}
-
-function ui_base_init() {
-	ui_toolbar_init();
-	context_raw.text_tool_text = tr("Text");
-	ui_header_init();
-	ui_status_init();
-	ui_menubar_init();
-
-	ui_header_h = math_floor(ui_header_default_h * config_raw.window_scale);
-	ui_menubar_w = math_floor(ui_menubar_default_w * config_raw.window_scale);
-
-	if (context_raw.empty_envmap == null) {
-		ui_base_make_empty_envmap(base_theme.VIEWPORT_COL);
-	}
-	if (context_raw.preview_envmap == null) {
-		let b: u8_array_t = u8_array_create(4);
-		b[0] = 0;
-		b[1] = 0;
-		b[2] = 0;
-		b[3] = 255;
-		context_raw.preview_envmap = gpu_create_texture_from_bytes(b, 1, 1);
-	}
-
-	if (context_raw.saved_envmap == null) {
-		// raw.saved_envmap = scene_world._envmap;
-		context_raw.default_irradiance = scene_world._.irradiance;
-		context_raw.default_radiance = scene_world._.radiance;
-		context_raw.default_radiance_mipmaps = scene_world._.radiance_mipmaps;
-	}
-	scene_world._.envmap = context_raw.show_envmap ? context_raw.saved_envmap : context_raw.empty_envmap;
-	context_raw.ddirty = 1;
-
-	let resources: string[] = ["cursor.k", "icons.k"];
-	resource_load(resources);
-
-	let scale: f32 = config_raw.window_scale;
-	let ops: ui_options_t = {
-		theme: base_theme,
-		font: base_font,
-		scale_factor: scale,
-		color_wheel: base_color_wheel,
-		black_white_gradient: base_color_wheel_gradient
-	};
-	ui = ui_create(ops);
-	ui_on_border_hover = ui_base_on_border_hover;
-	ui_on_tab_drop = ui_base_on_tab_drop;
-	if (UI_SCALE() > 1) {
-		ui_base_set_icon_scale();
-	}
-
-	context_raw.gizmo = scene_get_child(".Gizmo");
-	context_raw.gizmo_translate_x = object_get_child(context_raw.gizmo, ".TranslateX");
-	context_raw.gizmo_translate_y = object_get_child(context_raw.gizmo, ".TranslateY");
-	context_raw.gizmo_translate_z = object_get_child(context_raw.gizmo, ".TranslateZ");
-	context_raw.gizmo_scale_x = object_get_child(context_raw.gizmo, ".ScaleX");
-	context_raw.gizmo_scale_y = object_get_child(context_raw.gizmo, ".ScaleY");
-	context_raw.gizmo_scale_z = object_get_child(context_raw.gizmo, ".ScaleZ");
-	context_raw.gizmo_rotate_x = object_get_child(context_raw.gizmo, ".RotateX");
-	context_raw.gizmo_rotate_y = object_get_child(context_raw.gizmo, ".RotateY");
-	context_raw.gizmo_rotate_z = object_get_child(context_raw.gizmo, ".RotateZ");
-
-	project_new(false);
-
-	if (project_filepath == "") {
-		sys_notify_on_next_frame(layers_init);
-	}
-
-	context_raw.project_objects = [];
-	for (let i: i32 = 0; i < scene_meshes.length; ++i) {
-		let m: mesh_object_t = scene_meshes[i];
-		array_push(context_raw.project_objects, m);
-	}
-
-	operator_register("view_top", ui_base_view_top);
-}
-
-function ui_base_update() {
-	ui_base_update_ui();
-	operator_update();
-
-	let keys: string[] = map_keys(plugin_map);
-	for (let i: i32 = 0; i < keys.length; ++i) {
-		let p: plugin_t = map_get(plugin_map, keys[i]);
-		if (p.on_update != null) {
-			js_call(p.on_update);
-		}
-	}
-
-	if (!base_ui_enabled) {
-		return;
-	}
-
-	if (!ui.is_typing) {
-		if (operator_shortcut(map_get(config_keymap, "toggle_node_editor"))) {
-			ui_nodes_canvas_type == canvas_type_t.MATERIAL ? ui_base_show_material_nodes() : ui_base_show_brush_nodes();
-		}
-		else if (operator_shortcut(map_get(config_keymap, "toggle_browser"))) {
-			ui_base_toggle_browser();
-		}
-
-		else if (operator_shortcut(map_get(config_keymap, "toggle_2d_view"))) {
-			ui_base_show_2d_view(view_2d_type_t.LAYER);
-		}
-	}
-
-	if (operator_shortcut(map_get(config_keymap, "file_save_as"))) {
-		project_save_as();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_save"))) {
-		project_save();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_open"))) {
-		project_open();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_open_recent"))) {
-		box_projects_show();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_reimport_mesh"))) {
-		project_reimport_mesh();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_reimport_textures"))) {
-		project_reimport_textures();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_new"))) {
-		project_new_box();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_export_textures"))) {
-		if (context_raw.texture_export_path == "") { // First export, ask for path
-			context_raw.layers_export = export_mode_t.VISIBLE;
-			box_export_show_textures();
-		}
-		else {
-			sys_notify_on_next_frame(function () {
-				export_texture_run(context_raw.texture_export_path);
-			});
-		}
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_export_textures_as"))) {
-		context_raw.layers_export = export_mode_t.VISIBLE;
-		box_export_show_textures();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "file_import_assets"))) {
-		project_import_asset();
-	}
-	else if (operator_shortcut(map_get(config_keymap, "edit_prefs"))) {
-		box_preferences_show();
-	}
-
-	if (keyboard_started(map_get(config_keymap, "view_distract_free")) || (keyboard_started("escape") && !ui_base_show && !ui_box_show)) {
-		ui_base_toggle_distract_free();
-	}
-
-	///if arm_linux
-	if (operator_shortcut("alt+enter", shortcut_type_t.STARTED)) {
-		base_toggle_fullscreen();
-	}
-	///end
-
-	let decal: bool = context_is_decal();
-	let decal_mask: bool = context_is_decal_mask();
-
-	if ((context_raw.brush_can_lock || context_raw.brush_locked) && mouse_moved) {
-		if (operator_shortcut(map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN) ||
-			operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN) ||
-			operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN) ||
-			(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN))) {
-			if (context_raw.brush_locked) {
-				if (operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN)) {
-					context_raw.brush_opacity += mouse_movement_x / 500;
-					context_raw.brush_opacity = math_max(0.0, math_min(1.0, context_raw.brush_opacity));
-					context_raw.brush_opacity = math_round(context_raw.brush_opacity * 100) / 100;
-					context_raw.brush_opacity_handle.value = context_raw.brush_opacity;
-				}
-				else if (operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN)) {
-					context_raw.brush_angle += mouse_movement_x / 5;
-					let i: i32 = math_floor(context_raw.brush_angle);
-					context_raw.brush_angle = i % 360;
-					if (context_raw.brush_angle < 0) context_raw.brush_angle += 360;
-					context_raw.brush_angle_handle.value = context_raw.brush_angle;
-					make_material_parse_paint_material();
-				}
-				else if (decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN)) {
-					context_raw.brush_decal_mask_radius += mouse_movement_x / 150;
-					context_raw.brush_decal_mask_radius = math_max(0.01, math_min(4.0, context_raw.brush_decal_mask_radius));
-					context_raw.brush_decal_mask_radius = math_round(context_raw.brush_decal_mask_radius * 100) / 100;
-					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
-				}
-				else {
-					context_raw.brush_radius += mouse_movement_x / 150;
-					context_raw.brush_radius = math_max(0.01, math_min(4.0, context_raw.brush_radius));
-					context_raw.brush_radius = math_round(context_raw.brush_radius * 100) / 100;
-					context_raw.brush_radius_handle.value = context_raw.brush_radius;
-				}
-				ui_header_handle.redraws = 2;
-			}
-			else if (context_raw.brush_can_lock) {
-				context_raw.brush_can_lock = false;
-				context_raw.brush_locked = true;
-			}
-		}
-	}
-
-	let is_typing: bool = ui.is_typing;
-
-	if (!is_typing) {
-		if (operator_shortcut(map_get(config_keymap, "select_material"), shortcut_type_t.DOWN)) {
-			ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
-			for (let i: i32 = 1; i < 10; ++i) {
-				if (keyboard_started(i + "")) {
-					context_select_material(i - 1);
-				}
-			}
-		}
-		else if (operator_shortcut(map_get(config_keymap, "select_layer"), shortcut_type_t.DOWN)) {
-			ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-			for (let i: i32 = 1; i < 10; ++i) {
-				if (keyboard_started(i + "")) {
-					context_select_layer(i - 1);
-				}
-			}
-		}
-	}
-
-	// Viewport shortcuts
-	if (context_in_paint_area() && !is_typing) {
-
-		if (!mouse_down("right")) { // Fly mode off
-			if (operator_shortcut(map_get(config_keymap, "tool_brush"))) {
-				context_select_tool(tool_type_t.BRUSH);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_eraser"))) {
-				context_select_tool(tool_type_t.ERASER);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_fill"))) {
-				context_select_tool(tool_type_t.FILL);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_colorid"))) {
-				context_select_tool(tool_type_t.COLORID);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_decal"))) {
-				context_select_tool(tool_type_t.DECAL);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_text"))) {
-				context_select_tool(tool_type_t.TEXT);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_clone"))) {
-				context_select_tool(tool_type_t.CLONE);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_blur"))) {
-				context_select_tool(tool_type_t.BLUR);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_smudge"))) {
-				context_select_tool(tool_type_t.SMUDGE);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_particle"))) {
-				context_select_tool(tool_type_t.PARTICLE);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_picker"))) {
-				context_select_tool(tool_type_t.PICKER);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_bake"))) {
-				context_select_tool(tool_type_t.BAKE);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_gizmo"))) {
-				context_select_tool(tool_type_t.GIZMO);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "tool_material"))) {
-				context_select_tool(tool_type_t.MATERIAL);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "swap_brush_eraser"))) {
-				context_select_tool(context_raw.tool == tool_type_t.BRUSH ? tool_type_t.ERASER : tool_type_t.BRUSH);
-			}
-		}
-
-		// Radius
-		if (context_raw.tool == tool_type_t.BRUSH  ||
-			context_raw.tool == tool_type_t.ERASER ||
-			context_raw.tool == tool_type_t.DECAL  ||
-			context_raw.tool == tool_type_t.TEXT   ||
-			context_raw.tool == tool_type_t.CLONE  ||
-			context_raw.tool == tool_type_t.BLUR   ||
-			context_raw.tool == tool_type_t.SMUDGE   ||
-			context_raw.tool == tool_type_t.PARTICLE) {
-			if (operator_shortcut(map_get(config_keymap, "brush_radius")) ||
-				operator_shortcut(map_get(config_keymap, "brush_opacity")) ||
-				operator_shortcut(map_get(config_keymap, "brush_angle")) ||
-				(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius")))) {
-				context_raw.brush_can_lock = true;
-				if (!pen_connected) {
-					mouse_lock();
-				}
-				context_raw.lock_started_x = mouse_x;
-				context_raw.lock_started_y = mouse_y;
-			}
-			else if (operator_shortcut(map_get(config_keymap, "brush_radius_decrease"), shortcut_type_t.REPEAT)) {
-				context_raw.brush_radius -= ui_base_get_radius_increment();
-				context_raw.brush_radius = math_max(math_round(context_raw.brush_radius * 100) / 100, 0.01);
-				context_raw.brush_radius_handle.value = context_raw.brush_radius;
-				ui_header_handle.redraws = 2;
-			}
-			else if (operator_shortcut(map_get(config_keymap, "brush_radius_increase"), shortcut_type_t.REPEAT)) {
-				context_raw.brush_radius += ui_base_get_radius_increment();
-				context_raw.brush_radius = math_round(context_raw.brush_radius * 100) / 100;
-				context_raw.brush_radius_handle.value = context_raw.brush_radius;
-				ui_header_handle.redraws = 2;
-			}
-			else if (decal_mask) {
-				if (operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius_decrease"), shortcut_type_t.REPEAT)) {
-					context_raw.brush_decal_mask_radius -= ui_base_get_radius_increment();
-					context_raw.brush_decal_mask_radius = math_max(math_round(context_raw.brush_decal_mask_radius * 100) / 100, 0.01);
-					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
-					ui_header_handle.redraws = 2;
-				}
-				else if (operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius_increase"), shortcut_type_t.REPEAT)) {
-					context_raw.brush_decal_mask_radius += ui_base_get_radius_increment();
-					context_raw.brush_decal_mask_radius = math_round(context_raw.brush_decal_mask_radius * 100) / 100;
-					context_raw.brush_decal_mask_radius_handle.value = context_raw.brush_decal_mask_radius;
-					ui_header_handle.redraws = 2;
-				}
-			}
-		}
-
-		if (decal_mask && (operator_shortcut(map_get(config_keymap, "decal_mask"), shortcut_type_t.STARTED) || operator_shortcut(map_get(config_keymap, "decal_mask"), shortcut_type_t.RELEASED))) {
-			ui_header_handle.redraws = 2;
-		}
-
-		// Viewpoint
-		if (mouse_view_x() < sys_w()) {
-			if (operator_shortcut(map_get(config_keymap, "view_reset"))) {
-				viewport_reset();
-				viewport_scale_to_bounds();
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_back"))) {
-				viewport_set_view(0, 1, 0, math_pi() / 2, 0, math_pi());
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_front"))) {
-				viewport_set_view(0, -1, 0, math_pi() / 2, 0, 0);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_left"))) {
-				viewport_set_view(-1, 0, 0, math_pi() / 2, 0, -math_pi() / 2);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_right"))) {
-				viewport_set_view(1, 0, 0, math_pi() / 2, 0, math_pi() / 2);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_bottom"))) {
-				viewport_set_view(0, 0, -1, math_pi(), 0, math_pi());
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_camera_type"))) {
-				context_raw.camera_type = context_raw.camera_type == camera_type_t.PERSPECTIVE ? camera_type_t.ORTHOGRAPHIC : camera_type_t.PERSPECTIVE;
-				context_raw.cam_handle.position = context_raw.camera_type;
-				viewport_update_camera_type(context_raw.camera_type);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_orbit_left"), shortcut_type_t.REPEAT)) {
-				viewport_orbit(-math_pi() / 12, 0);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_orbit_right"), shortcut_type_t.REPEAT)) {
-				viewport_orbit(math_pi() / 12, 0);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_orbit_up"), shortcut_type_t.REPEAT)) {
-				viewport_orbit(0, -math_pi() / 12);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_orbit_down"), shortcut_type_t.REPEAT)) {
-				viewport_orbit(0, math_pi() / 12);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_orbit_opposite"))) {
-				viewport_orbit_opposite();
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_zoom_in"), shortcut_type_t.REPEAT)) {
-				viewport_zoom(0.2);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "view_zoom_out"), shortcut_type_t.REPEAT)) {
-				viewport_zoom(-0.2);
-			}
-			else if (operator_shortcut(map_get(config_keymap, "viewport_mode"))) {
-				ui.is_key_pressed = false;
-				ui_menu_draw(function () {
-					let mode_handle: ui_handle_t = ui_handle(__ID__);
-					mode_handle.position = context_raw.viewport_mode;
-					ui_text(tr("Viewport Mode"), ui_align_t.RIGHT);
-					let modes: string[] = [
-						tr("Lit"),
-						tr("Base Color"),
-						tr("Normal"),
-						tr("Occlusion"),
-						tr("Roughness"),
-						tr("Metallic"),
-						tr("Opacity"),
-						tr("Height"),
-						tr("Emission"),
-						tr("Subsurface"),
-						tr("TexCoord"),
-						tr("Object Normal"),
-						tr("Material ID"),
-						tr("Object ID"),
-						tr("Mask")
-					];
-
-					let shortcuts: string[] = ["l", "b", "n", "o", "r", "m", "a", "h", "e", "s", "t", "1", "2", "3", "4"];
-
-					if (gpu_raytrace_supported()) {
-						array_push(modes, tr("Path Traced"));
-						array_push(shortcuts, "p");
-					}
-
-					for (let i: i32 = 0; i < modes.length; ++i) {
-						ui_radio(mode_handle, i, modes[i], shortcuts[i]);
-					}
-
-					let index: i32 = array_index_of(shortcuts, keyboard_key_code(ui.key_code));
-					if (ui.is_key_pressed && index != -1) {
-						mode_handle.position = index;
-						ui.changed = true;
-						context_set_viewport_mode(mode_handle.position);
-					}
-					else if (mode_handle.changed) {
-						context_set_viewport_mode(mode_handle.position);
-						ui.changed = true;
-					}
-				});
-			}
-		}
-
-		if (operator_shortcut(map_get(config_keymap, "operator_search"))) {
-			ui_base_operator_search();
-		}
-	}
-
-	if (context_raw.brush_can_lock || context_raw.brush_locked) {
-		if (mouse_moved && context_raw.brush_can_unlock) {
-			context_raw.brush_locked = false;
-			context_raw.brush_can_unlock = false;
-		}
-
-		let b: bool = (context_raw.brush_can_lock || context_raw.brush_locked) &&
-			!operator_shortcut(map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN) &&
-			!operator_shortcut(map_get(config_keymap, "brush_opacity"), shortcut_type_t.DOWN) &&
-			!operator_shortcut(map_get(config_keymap, "brush_angle"), shortcut_type_t.DOWN) &&
-			!(decal_mask && operator_shortcut(map_get(config_keymap, "decal_mask") + "+" + map_get(config_keymap, "brush_radius"), shortcut_type_t.DOWN));
-
-		if (b) {
-			mouse_unlock();
-			context_raw.last_paint_x = -1;
-			context_raw.last_paint_y = -1;
-			if (context_raw.brush_can_lock) {
-				context_raw.brush_can_lock = false;
-				context_raw.brush_can_unlock = false;
-				context_raw.brush_locked = false;
-			}
-			else {
-				context_raw.brush_can_unlock = true;
-			}
-		}
-	}
-
-	if (ui_base_border_handle != null) {
-		if (ui_base_border_handle == ui_nodes_hwnd || ui_base_border_handle == ui_view2d_hwnd) {
-			if (ui_base_border_started == border_side_t.LEFT) {
-				config_raw.layout[layout_size_t.NODES_W] -= math_floor(mouse_movement_x);
-				if (config_raw.layout[layout_size_t.NODES_W] < 32) {
-					config_raw.layout[layout_size_t.NODES_W] = 32;
-				}
-				else if (config_raw.layout[layout_size_t.NODES_W] > iron_window_width() * 0.7) {
-					config_raw.layout[layout_size_t.NODES_W] = math_floor(iron_window_width() * 0.7);
-				}
-			}
-			else { // UINodes / UIView2D ratio
-				config_raw.layout[layout_size_t.NODES_H] -= math_floor(mouse_movement_y);
-				if (config_raw.layout[layout_size_t.NODES_H] < 32) {
-					config_raw.layout[layout_size_t.NODES_H] = 32;
-				}
-				else if (config_raw.layout[layout_size_t.NODES_H] > sys_h() * 0.95) {
-					config_raw.layout[layout_size_t.NODES_H] = math_floor(sys_h() * 0.95);
-				}
-			}
-		}
-		else if (ui_base_border_handle == ui_base_hwnds[tab_area_t.STATUS]) {
-			let my: i32 = math_floor(mouse_movement_y);
-			if (config_raw.layout[layout_size_t.STATUS_H] - my >= ui_status_default_status_h * config_raw.window_scale && config_raw.layout[layout_size_t.STATUS_H] - my < iron_window_height() * 0.7) {
-				config_raw.layout[layout_size_t.STATUS_H] -= my;
-			}
-		}
-		else {
-			if (ui_base_border_started == border_side_t.LEFT) {
-				config_raw.layout[layout_size_t.SIDEBAR_W] -= math_floor(mouse_movement_x);
-				if (config_raw.layout[layout_size_t.SIDEBAR_W] < ui_base_sidebar_mini_w) {
-					config_raw.layout[layout_size_t.SIDEBAR_W] = ui_base_sidebar_mini_w;
-				}
-				else if (config_raw.layout[layout_size_t.SIDEBAR_W] > iron_window_width() - ui_base_sidebar_mini_w) {
-					config_raw.layout[layout_size_t.SIDEBAR_W] = iron_window_width() - ui_base_sidebar_mini_w;
-				}
-			}
-			else {
-				let my: i32 = math_floor(mouse_movement_y);
-				if (ui_base_border_handle == ui_base_hwnds[tab_area_t.SIDEBAR1] && ui_base_border_started == border_side_t.TOP) {
-					if (config_raw.layout[layout_size_t.SIDEBAR_H0] + my > 32 && config_raw.layout[layout_size_t.SIDEBAR_H1] - my > 32) {
-						config_raw.layout[layout_size_t.SIDEBAR_H0] += my;
-						config_raw.layout[layout_size_t.SIDEBAR_H1] -= my;
-					}
-				}
-			}
-		}
-	}
-
-	if (!mouse_down()) {
-		ui_base_border_handle = null;
-		base_is_resizing = false;
-	}
-
-	if (context_raw.tool == tool_type_t.PARTICLE && context_in_paint_area() && !context_raw.paint2d) {
-		util_particle_init_physics();
-		let world: physics_world_t = physics_world_active;
-		physics_world_update(world);
-		context_raw.ddirty = 2;
-		context_raw.rdirty = 2;
-		if (mouse_started()) {
-			if (context_raw.particle_timer != null) {
-				tween_stop(context_raw.particle_timer);
-				let timer: tween_anim_t = context_raw.particle_timer;
-				timer.done(timer.done_data);
-				context_raw.particle_timer = null;
-			}
-			history_push_undo = true;
-			context_raw.particle_hit_x = context_raw.particle_hit_y = context_raw.particle_hit_z = 0;
-			let o: object_t = scene_spawn_object(".Sphere");
-			let mo: mesh_object_t = o.ext;
-			mo.base.name = ".Bullet";
-			mo.base.visible = true;
-
-			let camera: camera_object_t = scene_camera;
-			let ct: transform_t = camera.base.transform;
-			mo.base.transform.loc = vec4_create(transform_world_x(ct), transform_world_y(ct), transform_world_z(ct));
-			mo.base.transform.scale = vec4_create(context_raw.brush_radius * 0.2, context_raw.brush_radius * 0.2, context_raw.brush_radius * 0.2);
-			transform_build_matrix(mo.base.transform);
-
-			let body: physics_body_t = physics_body_create();
-			body.shape = physics_shape_t.SPHERE;
-			body.mass = 1.0;
-			physics_body_init(body, mo.base);
-
-			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 (mo: mesh_object_t) {
-				mesh_object_remove(mo);
-			}, mo);
-		}
-
-		let pairs: physics_pair_t[] = physics_world_get_contact_pairs(world, context_raw.paint_body);
-		if (pairs != null) {
-			for (let i: i32 = 0; i < pairs.length; ++i) {
-				let p: physics_pair_t = pairs[i];
-				context_raw.last_particle_hit_x = context_raw.particle_hit_x != 0 ? context_raw.particle_hit_x : p.pos_a_x;
-				context_raw.last_particle_hit_y = context_raw.particle_hit_y != 0 ? context_raw.particle_hit_y : p.pos_a_y;
-				context_raw.last_particle_hit_z = context_raw.particle_hit_z != 0 ? context_raw.particle_hit_z : p.pos_a_z;
-				context_raw.particle_hit_x = p.pos_a_x;
-				context_raw.particle_hit_y = p.pos_a_y;
-				context_raw.particle_hit_z = p.pos_a_z;
-				context_raw.pdirty = 1;
-				break; // 1 pair for now
-			}
-		}
-	}
-}
-
-function ui_base_view_top() {
-	let is_typing: bool = ui.is_typing;
-
-	if (context_in_paint_area() && !is_typing) {
-		if (mouse_view_x() < sys_w()) {
-			viewport_set_view(0, 0, 1, 0, 0, 0);
-		}
-	}
-}
-
-function ui_base_operator_search() {
-	_ui_base_operator_search_first = true;
-
-	ui_menu_draw(function () {
-		ui_menu_h = UI_ELEMENT_H() * 8;
-		let search_handle: ui_handle_t = ui_handle(__ID__);
-		let search: string = ui_text_input(search_handle, "", ui_align_t.LEFT, true, true);
-		ui.changed = false;
-		if (_ui_base_operator_search_first) {
-			_ui_base_operator_search_first = false;
-			search_handle.text = "";
-			ui_start_text_edit(search_handle); // Focus search bar
-		}
-
-		if (search_handle.changed) {
-			ui_base_operator_search_offset = 0;
-		}
-
-		if (ui.is_key_pressed) { // Move selection
-			if (ui.key_code == key_code_t.DOWN && ui_base_operator_search_offset < 6) {
-				ui_base_operator_search_offset++;
-			}
-			if (ui.key_code == key_code_t.UP && ui_base_operator_search_offset > 0) {
-				ui_base_operator_search_offset--;
-			}
-		}
-		let enter: bool = keyboard_down("enter");
-		let count: i32 = 0;
-		let BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
-
-		let keys: string[] = map_keys(config_keymap);
-		for (let i: i32 = 0; i < keys.length; ++i) {
-			let n: string = keys[i];
-			if (string_index_of(n, search) >= 0) {
-				ui.ops.theme.BUTTON_COL = count == ui_base_operator_search_offset ? ui.ops.theme.HIGHLIGHT_COL : ui.ops.theme.SEPARATOR_COL;
-				if (ui_button(n, ui_align_t.LEFT, map_get(config_keymap, n)) || (enter && count == ui_base_operator_search_offset)) {
-					if (enter) {
-						ui.changed = true;
-						count = 6; // Trigger break
-					}
-					operator_run(n);
-				}
-				if (++count > 6) {
-					break;
-				}
-			}
-		}
-
-		if (enter && count == 0) { // Hide popup on enter when command is not found
-			ui.changed = true;
-			search_handle.text = "";
-		}
-		ui.ops.theme.BUTTON_COL = BUTTON_COL;
-	});
-}
-
-function ui_base_toggle_distract_free() {
-	ui_base_show = !ui_base_show;
-	base_resize();
-}
-
-function ui_base_get_radius_increment(): f32 {
-	return 0.1;
-}
-
-function ui_base_hit_rect(mx: f32, my: f32, x: i32, y: i32, w: i32, h: i32): bool {
-	return mx > x && mx < x + w && my > y && my < y + h;
-}
-
-function ui_base_get_brush_stencil_rect(): rect_t {
-	let w: i32 = math_floor(context_raw.brush_stencil_image.width * (base_h() / context_raw.brush_stencil_image.height) * context_raw.brush_stencil_scale);
-	let h: i32 = math_floor(base_h() * context_raw.brush_stencil_scale);
-	let x: i32 = math_floor(base_x() + context_raw.brush_stencil_x * base_w());
-	let y: i32 = math_floor(base_y() + context_raw.brush_stencil_y * base_h());
-	let r: rect_t = {
-		w: w,
-		h: h,
-		x: x,
-		y: y
-	};
-	return r;
-}
-
-function ui_base_update_ui() {
-	if (console_message_timer > 0) {
-		console_message_timer -= sys_delta();
-		if (console_message_timer <= 0) {
-			ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
-		}
-	}
-
-	ui_base_sidebar_mini_w = math_floor(ui_base_default_sidebar_mini_w * UI_SCALE());
-
-	if (!base_ui_enabled) {
-		return;
-	}
-
-	// Same mapping for paint and rotate (predefined in touch keymap)
-	if (context_in_viewport()) {
-		let paint_key: string = map_get(config_keymap, "action_paint");
-		let rotate_key: string = map_get(config_keymap, "action_rotate");
-		if (mouse_started() && paint_key == rotate_key) {
-			ui_base_action_paint_remap = paint_key;
-			util_render_pick_pos_nor_tex();
-			let is_mesh: bool = math_abs(context_raw.posx_picked) < 50 && math_abs(context_raw.posy_picked) < 50 && math_abs(context_raw.posz_picked) < 50;
-			///if arm_android
-			// Allow rotating with both pen and touch, because hovering a pen prevents touch input on android
-			let pen_only: bool = false;
-			///else
-			let pen_only: bool = context_raw.pen_painting_only;
-			///end
-			let is_pen: bool = pen_only && pen_down();
-			// Mesh picked - disable rotate
-			// Pen painting only - rotate with touch, paint with pen
-			if ((is_mesh && !pen_only) || is_pen) {
-				map_set(config_keymap, "action_rotate", "");
-				map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
-			}
-			// World sphere picked - disable paint
-			else {
-				map_set(config_keymap, "action_paint", "");
-				map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
-			}
-		}
-		else if (!mouse_down() && ui_base_action_paint_remap != "") {
-			map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
-			map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
-			ui_base_action_paint_remap = "";
-		}
-	}
-
-	if (context_raw.brush_stencil_image != null && operator_shortcut(map_get(config_keymap, "stencil_transform"), shortcut_type_t.DOWN)) {
-		let r: rect_t = ui_base_get_brush_stencil_rect();
-		if (mouse_started("left")) {
-			context_raw.brush_stencil_scaling =
-				ui_base_hit_rect(mouse_x, mouse_y, r.x - 8,       r.y - 8,       16, 16) ||
-				ui_base_hit_rect(mouse_x, mouse_y, r.x - 8,       r.h + r.y - 8, 16, 16) ||
-				ui_base_hit_rect(mouse_x, mouse_y, r.w + r.x - 8, r.y - 8,       16, 16) ||
-				ui_base_hit_rect(mouse_x, mouse_y, r.w + r.x - 8, r.h + r.y - 8, 16, 16);
-			let cosa: f32 = math_cos(-context_raw.brush_stencil_angle);
-			let sina: f32 = math_sin(-context_raw.brush_stencil_angle);
-			let ox: f32 = 0;
-			let oy: f32 = -r.h / 2;
-			let x: f32 = ox * cosa - oy * sina;
-			let y: f32 = ox * sina + oy * cosa;
-			x += r.x + r.w / 2;
-			y += r.y + r.h / 2;
-			context_raw.brush_stencil_rotating =
-				ui_base_hit_rect(mouse_x, mouse_y, math_floor(x - 16), math_floor(y - 16), 32, 32);
-		}
-		let _scale: f32 = context_raw.brush_stencil_scale;
-		if (mouse_down("left")) {
-			if (context_raw.brush_stencil_scaling) {
-				let mult: i32 = mouse_x > r.x + r.w / 2 ? 1 : -1;
-				context_raw.brush_stencil_scale += mouse_movement_x / 400 * mult;
-			}
-			else if (context_raw.brush_stencil_rotating) {
-				let gizmo_x: f32 = r.x + r.w / 2;
-				let gizmo_y: f32 = r.y + r.h / 2;
-				context_raw.brush_stencil_angle = -math_atan2(mouse_y - gizmo_y, mouse_x - gizmo_x) - math_pi() / 2;
-			}
-			else {
-				context_raw.brush_stencil_x += mouse_movement_x / base_w();
-				context_raw.brush_stencil_y += mouse_movement_y / base_h();
-			}
-		}
-		else {
-			context_raw.brush_stencil_scaling = false;
-		}
-		if (mouse_wheel_delta != 0) {
-			context_raw.brush_stencil_scale -= mouse_wheel_delta / 10;
-		}
-		// Center after scale
-		let ratio: f32 = base_h() / context_raw.brush_stencil_image.height;
-		let old_w: f32 = _scale * context_raw.brush_stencil_image.width * ratio;
-		let new_w: f32 = context_raw.brush_stencil_scale * context_raw.brush_stencil_image.width * ratio;
-		let old_h: f32 = _scale * base_h();
-		let new_h: f32 = context_raw.brush_stencil_scale * base_h();
-		context_raw.brush_stencil_x += (old_w - new_w) / base_w() / 2;
-		context_raw.brush_stencil_y += (old_h - new_h) / base_h() / 2;
-	}
-
-	let set_clone_source: bool = context_raw.tool == tool_type_t.CLONE && operator_shortcut(map_get(config_keymap, "set_clone_source") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN);
-
-	let decal: bool = context_is_decal();
-	let decal_mask: bool = context_is_decal_mask_paint();
-
-	let down: bool = operator_shortcut(map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN) ||
-					 decal_mask ||
-					 set_clone_source ||
-					 operator_shortcut(map_get(config_keymap, "brush_ruler") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN) ||
-					 (pen_down() && !keyboard_down("alt"));
-
-	if (config_raw.touch_ui) {
-		if (pen_down()) {
-			context_raw.pen_painting_only = true;
-		}
-		else if (context_raw.pen_painting_only) {
-			down = false;
-		}
-	}
-
-	if (context_raw.tool == tool_type_t.PARTICLE) {
-		down = false;
-	}
-
-	if (down) {
-		let mx: i32 = mouse_view_x();
-		let my: i32 = mouse_view_y();
-		let ww: i32 = sys_w();
-
-		if (context_raw.paint2d) {
-			mx -= sys_w();
-			ww = ui_view2d_ww;
-		}
-
-		if (mx < ww &&
-			mx > sys_x() &&
-			my < sys_h() &&
-			my > sys_y()) {
-
-			if (set_clone_source) {
-				context_raw.clone_start_x = mx;
-				context_raw.clone_start_y = my;
-			}
-			else {
-				if (context_raw.brush_time == 0 &&
-					!base_is_dragging &&
-					!base_is_resizing &&
-					!base_is_combo_selected()) { // Paint started
-
-					// Draw line
-					if (operator_shortcut(map_get(config_keymap, "brush_ruler") + "+" + map_get(config_keymap, "action_paint"), shortcut_type_t.DOWN)) {
-						context_raw.last_paint_vec_x = context_raw.last_paint_x;
-						context_raw.last_paint_vec_y = context_raw.last_paint_y;
-					}
-
-					history_push_undo = true;
-
-					if (context_raw.tool == tool_type_t.CLONE && context_raw.clone_start_x >= 0.0) { // Clone delta
-						context_raw.clone_delta_x = (context_raw.clone_start_x - mx) / ww;
-						context_raw.clone_delta_y = (context_raw.clone_start_y - my) / sys_h();
-						context_raw.clone_start_x = -1;
-					}
-					else if (context_raw.tool == tool_type_t.FILL && context_raw.fill_type_handle.position == fill_type_t.UV_ISLAND) {
-						util_uv_uvislandmap_cached = false;
-					}
-				}
-
-				context_raw.brush_time += sys_delta();
-
-				if (context_raw.run_brush != null) {
-					context_raw.run_brush(context_raw.brush_output_node_inst, 0);
-				}
-			}
-		}
-	}
-	else if (context_raw.brush_time > 0) { // Brush released
-		context_raw.brush_time = 0;
-		context_raw.prev_paint_vec_x = -1;
-		context_raw.prev_paint_vec_y = -1;
-		///if (arm_opengl || arm_direct3d11) // Keep accumulated samples for D3D12
-		context_raw.ddirty = 3;
-		///end
-		context_raw.brush_blend_dirty = true; // Update brush mask
-
-		context_raw.layer_preview_dirty = true; // Update layer preview
-
-		// New color id picked, update fill layer
-		if (context_raw.tool == tool_type_t.COLORID && context_raw.layer.fill_layer != null) {
-			sys_notify_on_next_frame(function () {
-				layers_update_fill_layer();
-				make_material_parse_paint_material(false);
-			});
-		}
-	}
-
-	if (context_raw.layers_preview_dirty) {
-		context_raw.layers_preview_dirty = false;
-		context_raw.layer_preview_dirty = false;
-		context_raw.mask_preview_last = null;
-		// Update all layer previews
-		for (let i: i32 = 0; i < project_layers.length; ++i) {
-			let l: slot_layer_t = project_layers[i];
-			if (slot_layer_is_group(l)) {
-				continue;
-			}
-
-			let target: gpu_texture_t = l.texpaint_preview;
-			if (target == null) {
-				continue;
-			}
-
-			let source: gpu_texture_t = l.texpaint;
-			draw_begin(target, true, 0x00000000);
-			// draw_set_pipeline(l.is_mask() ? pipes_copy8 : pipes_copy);
-			draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
-			draw_scaled_image(source, 0, 0, target.width, target.height);
-			draw_set_pipeline(null);
-			draw_end();
-		}
-		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-	}
-	if (context_raw.layer != null && context_raw.layer_preview_dirty && !slot_layer_is_group(context_raw.layer)) {
-		context_raw.layer_preview_dirty = false;
-		context_raw.mask_preview_last = null;
-		// Update layer preview
-		let l: slot_layer_t = context_raw.layer;
-
-		let target: gpu_texture_t = l.texpaint_preview;
-		if (target != null) {
-
-			let source: gpu_texture_t = l.texpaint;
-			draw_begin(target, true, 0x00000000);
-			// draw_set_pipeline(raw.layer.is_mask() ? pipes_copy8 : pipes_copy);
-			draw_set_pipeline(pipes_copy); // texpaint_preview is always RGBA32 for now
-			draw_scaled_image(source, 0, 0, target.width, target.height);
-			draw_set_pipeline(null);
-			draw_end();
-			ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-		}
-	}
-
-	let undo_pressed: bool = operator_shortcut(map_get(config_keymap, "edit_undo"));
-	let redo_pressed: bool = operator_shortcut(map_get(config_keymap, "edit_redo")) ||
-							 (keyboard_down("control") && keyboard_started("y"));
-
-	// Two-finger tap to undo, three-finger tap to redo
-	if (context_in_viewport() && config_raw.touch_ui) {
-		if (mouse_started("middle")) {
-			ui_base_redo_tap_time = sys_time();
-		}
-		else if (mouse_started("right")) {
-			ui_base_undo_tap_time = sys_time();
-		}
-		else if (mouse_released("middle") && sys_time() - ui_base_redo_tap_time < 0.1) {
-			ui_base_redo_tap_time = ui_base_undo_tap_time = 0;
-			redo_pressed = true;
-		}
-		else if (mouse_released("right") && sys_time() - ui_base_undo_tap_time < 0.1) {
-			ui_base_redo_tap_time = ui_base_undo_tap_time = 0;
-			undo_pressed = true;
-		}
-	}
-
-	if (undo_pressed) {
-		history_undo();
-	}
-	else if (redo_pressed) {
-		history_redo();
-	}
-
-	gizmo_update();
-}
-
-function ui_base_render() {
-	if (!ui_base_show && config_raw.touch_ui) {
-		ui.input_enabled = true;
-		ui_begin(ui);
-		if (ui_window(ui_handle(__ID__), 0, 0, 150, math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET() + 1))) {
-			if (ui_button(tr("Close"))) {
-				ui_base_toggle_distract_free();
-			}
-		}
-		ui_end();
-	}
-
-	if (!ui_base_show) {
-		return;
-	}
-
-	ui.input_enabled = base_ui_enabled;
-
-	// Remember last tab positions
-	for (let i: i32 = 0; i < ui_base_htabs.length; ++i) {
-		if (ui_base_htabs[i].changed) {
-			config_raw.layout_tabs[i] = ui_base_htabs[i].position;
-			config_save();
-		}
-	}
-
-	// Set tab positions
-	for (let i: i32 = 0; i < ui_base_htabs.length; ++i) {
-		ui_base_htabs[i].position = config_raw.layout_tabs[i];
-	}
-
-	ui_begin(ui);
-	ui_toolbar_render_ui();
-	ui_menubar_render_ui();
-	ui_header_render_ui();
-	ui_status_render_ui();
-	ui_base_draw_sidebar();
-	ui_end();
-
-	ui.input_enabled = true;
-}
-
-function ui_base_draw_sidebar() {
-	// Tabs
-	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
-	let expand_button_offset: i32 = config_raw.touch_ui ? math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET()) : 0;
-	ui_base_tabx = iron_window_width() - config_raw.layout[layout_size_t.SIDEBAR_W];
-
-	let _SCROLL_W: i32 = ui.ops.theme.SCROLL_W;
-	if (mini) {
-		ui.ops.theme.SCROLL_W = ui.ops.theme.SCROLL_MINI_W;
-	}
-
-	if (ui_window(ui_base_hwnds[tab_area_t.SIDEBAR0], ui_base_tabx, 0, config_raw.layout[layout_size_t.SIDEBAR_W], config_raw.layout[layout_size_t.SIDEBAR_H0])) {
-		let tabs: tab_draw_t[] = ui_base_hwnd_tabs[tab_area_t.SIDEBAR0];
-		for (let i: i32 = 0; i < (mini ? 1 : tabs.length); ++i) {
-			tabs[i].f(ui_base_htabs[tab_area_t.SIDEBAR0]);
-		}
-	}
-	if (ui_window(ui_base_hwnds[tab_area_t.SIDEBAR1], ui_base_tabx, config_raw.layout[layout_size_t.SIDEBAR_H0], config_raw.layout[layout_size_t.SIDEBAR_W], config_raw.layout[layout_size_t.SIDEBAR_H1] - expand_button_offset)) {
-		let tabs: tab_draw_t[] = ui_base_hwnd_tabs[tab_area_t.SIDEBAR1];
-		for (let i: i32 = 0; i < (mini ? 1 : tabs.length); ++i) {
-			tabs[i].f(ui_base_htabs[tab_area_t.SIDEBAR1]);
-		}
-	}
-
-	ui_end_window();
-	ui.ops.theme.SCROLL_W = _SCROLL_W;
-
-	// Collapse / expand button for mini sidebar
-	if (config_raw.touch_ui) {
-		let width: i32 = config_raw.layout[layout_size_t.SIDEBAR_W];
-		let height: i32 = math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET());
-		if (ui_window(ui_handle(__ID__), iron_window_width() - width, iron_window_height() - height, width, height + 1)) {
-			ui._w = width;
-			let _BUTTON_H: i32 = ui.ops.theme.BUTTON_H;
-			let _BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
-			ui.ops.theme.BUTTON_H = ui.ops.theme.ELEMENT_H;
-			ui.ops.theme.BUTTON_COL = ui.ops.theme.WINDOW_BG_COL;
-			if (ui_button(mini ? "<<" : ">>")) {
-				config_raw.layout[layout_size_t.SIDEBAR_W] = mini ? ui_base_default_sidebar_full_w : ui_base_default_sidebar_mini_w;
-				config_raw.layout[layout_size_t.SIDEBAR_W] = math_floor(config_raw.layout[layout_size_t.SIDEBAR_W] * UI_SCALE());
-			}
-			ui.ops.theme.BUTTON_H = _BUTTON_H;
-			ui.ops.theme.BUTTON_COL = _BUTTON_COL;
-		}
-	}
-
-	// Expand button
-	if (config_raw.layout[layout_size_t.SIDEBAR_W] == 0) {
-		let width: i32 = math_floor(draw_string_width(ui.ops.font, ui.font_size, "<<") + 25 * UI_SCALE());
-		if (ui_window(ui_base_hminimized, iron_window_width() - width, 0, width, math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET() + 1))) {
-			ui._w = width;
-			let _BUTTON_H: i32 = ui.ops.theme.BUTTON_H;
-			let _BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
-			ui.ops.theme.BUTTON_H = ui.ops.theme.ELEMENT_H;
-			ui.ops.theme.BUTTON_COL = ui.ops.theme.SEPARATOR_COL;
-
-			if (ui_button("<<")) {
-				config_raw.layout[layout_size_t.SIDEBAR_W] = context_raw.maximized_sidebar_width != 0 ? context_raw.maximized_sidebar_width : math_floor(ui_base_default_sidebar_w * config_raw.window_scale);
-			}
-			ui.ops.theme.BUTTON_H = _BUTTON_H;
-			ui.ops.theme.BUTTON_COL = _BUTTON_COL;
-		}
-	}
-	else if (ui_base_htabs[tab_area_t.SIDEBAR0].changed && ui_base_htabs[tab_area_t.SIDEBAR0].position == context_raw.last_htab0_pos) {
-		if (sys_time() - context_raw.select_time < 0.25) {
-			context_raw.maximized_sidebar_width = config_raw.layout[layout_size_t.SIDEBAR_W];
-			config_raw.layout[layout_size_t.SIDEBAR_W] = 0;
-		}
-		context_raw.select_time = sys_time();
-	}
-	context_raw.last_htab0_pos = ui_base_htabs[tab_area_t.SIDEBAR0].position;
-}
-
-function ui_base_render_cursor() {
-	if (!base_ui_enabled) {
-		return;
-	}
-
-	if (context_raw.tool == tool_type_t.MATERIAL || context_raw.tool == tool_type_t.BAKE) {
-		return;
-	}
-
-	draw_begin();
-	draw_set_color(0xffffffff);
-
-	context_raw.view_index = context_raw.view_index_last;
-	let mx: i32 = base_x() + context_raw.paint_vec.x * base_w();
-	let my: i32 = base_y() + context_raw.paint_vec.y * base_h();
-	context_raw.view_index = -1;
-
-	// Radius being scaled
-	if (context_raw.brush_locked) {
-		mx += context_raw.lock_started_x - iron_window_width() / 2;
-		my += context_raw.lock_started_y - iron_window_height() / 2;
-	}
-
-	if (context_raw.brush_stencil_image != null &&
-		context_raw.tool != tool_type_t.BAKE &&
-		context_raw.tool != tool_type_t.PICKER &&
-		context_raw.tool != tool_type_t.MATERIAL &&
-		context_raw.tool != tool_type_t.COLORID) {
-		let r: rect_t = ui_base_get_brush_stencil_rect();
-		if (!operator_shortcut(map_get(config_keymap, "stencil_hide"), shortcut_type_t.DOWN)) {
-			draw_set_color(0x88ffffff);
-			let angle: f32 = context_raw.brush_stencil_angle;
-			draw_set_transform(mat3_multmat(mat3_multmat(mat3_translation(0.5, 0.5), mat3_rotation(-angle)), mat3_translation(-0.5, -0.5)));
-			draw_scaled_image(context_raw.brush_stencil_image, r.x, r.y, r.w, r.h);
-			draw_set_transform(mat3_nan());
-			draw_set_color(0xffffffff);
-		}
-		let transform: bool = operator_shortcut(map_get(config_keymap, "stencil_transform"), shortcut_type_t.DOWN);
-		if (transform) {
-			// Outline
-			draw_rect(r.x, r.y, r.w, r.h);
-			// Scale
-			draw_rect(r.x - 8,       r.y - 8,       16, 16);
-			draw_rect(r.x - 8 + r.w, r.y - 8,       16, 16);
-			draw_rect(r.x - 8,       r.y - 8 + r.h, 16, 16);
-			draw_rect(r.x - 8 + r.w, r.y - 8 + r.h, 16, 16);
-			// Rotate
-			let cosa: f32 = math_cos(-context_raw.brush_stencil_angle);
-			let sina: f32 = math_sin(-context_raw.brush_stencil_angle);
-			let ox: f32 = 0;
-			let oy: f32 = -r.h / 2;
-			let x: f32 = ox * cosa - oy * sina;
-			let y: f32 = ox * sina + oy * cosa;
-			x += r.x + r.w / 2;
-			y += r.y + r.h / 2;
-			draw_filled_circle(x, y, 8);
-		}
-	}
-
-	// Show picked material next to cursor
-	if (context_raw.tool == tool_type_t.PICKER && context_raw.picker_select_material && context_raw.color_picker_callback == null) {
-		let img: gpu_texture_t = context_raw.material.image_icon;
-		draw_image(img, mx + 10, my + 10);
-	}
-	if (context_raw.tool == tool_type_t.PICKER && context_raw.color_picker_callback != null) {
-		let img: gpu_texture_t = resource_get("icons.k");
-		let rect: rect_t = resource_tile50(img, tool_type_t.PICKER, 0);
-		draw_sub_image(img, mx + 10, my + 10, rect.x, rect.y, rect.w, rect.h);
-	}
-
-	let cursor_img: gpu_texture_t = resource_get("cursor.k");
-	let psize: i32 = math_floor(182 * (context_raw.brush_radius * context_raw.brush_nodes_radius) * UI_SCALE());
-
-	// Clone source cursor
-	if (context_raw.tool == tool_type_t.CLONE && !keyboard_down("alt") && (mouse_down() || pen_down())) {
-		draw_set_color(0x66ffffff);
-		draw_scaled_image(cursor_img, mx + context_raw.clone_delta_x * sys_w() - psize / 2, my + context_raw.clone_delta_y * sys_h() - psize / 2, psize, psize);
-		draw_set_color(0xffffffff);
-	}
-
-	let decal: bool = context_is_decal();
-
-	if (!config_raw.brush_3d || context_in_2d_view() || decal) {
-		let decal_mask: bool = context_is_decal_mask();
-		if (decal && !context_in_nodes()) {
-			let decal_alpha: f32 = 0.5;
-			if (!decal_mask) {
-				context_raw.decal_x = context_raw.paint_vec.x;
-				context_raw.decal_y = context_raw.paint_vec.y;
-				decal_alpha = context_raw.brush_opacity;
-
-				// Radius being scaled
-				if (context_raw.brush_locked) {
-					context_raw.decal_x += (context_raw.lock_started_x - iron_window_width() / 2) / base_w();
-					context_raw.decal_y += (context_raw.lock_started_y - iron_window_height() / 2) / base_h();
-				}
-			}
-
-			if (!config_raw.brush_live) {
-				let psizex: i32 = math_floor(256 * UI_SCALE() * (context_raw.brush_radius * context_raw.brush_nodes_radius * context_raw.brush_scale_x));
-				let psizey: i32 = math_floor(256 * UI_SCALE() * (context_raw.brush_radius * context_raw.brush_nodes_radius));
-
-				context_raw.view_index = context_raw.view_index_last;
-				let decalx: f32 = base_x() + context_raw.decal_x * base_w() - psizex / 2;
-				let decaly: f32 = base_y() + context_raw.decal_y * base_h() - psizey / 2;
-				context_raw.view_index = -1;
-
-				draw_set_color(color_from_floats(1, 1, 1, decal_alpha));
-				let angle: f32 = (context_raw.brush_angle + context_raw.brush_nodes_angle) * (math_pi() / 180);
-				draw_set_transform(mat3_multmat(mat3_multmat(mat3_translation(0.5, 0.5), mat3_rotation(angle)), mat3_translation(-0.5, -0.5)));
-				draw_scaled_image(context_raw.decal_image, decalx, decaly, psizex, psizey);
-				draw_set_transform(mat3_nan());
-				draw_set_color(0xffffffff);
-			}
-		}
-		if (context_raw.tool == tool_type_t.BRUSH  ||
-			context_raw.tool == tool_type_t.ERASER ||
-			context_raw.tool == tool_type_t.CLONE  ||
-			context_raw.tool == tool_type_t.BLUR   ||
-			context_raw.tool == tool_type_t.SMUDGE   ||
-			context_raw.tool == tool_type_t.PARTICLE ||
-			(decal_mask && !config_raw.brush_3d) ||
-			(decal_mask && context_in_2d_view())) {
-			if (decal_mask) {
-				psize = math_floor(cursor_img.width * (context_raw.brush_decal_mask_radius * context_raw.brush_nodes_radius) * UI_SCALE());
-			}
-			if (config_raw.brush_3d && context_in_2d_view()) {
-				psize = math_floor(psize * ui_view2d_pan_scale);
-			}
-			draw_scaled_image(cursor_img, mx - psize / 2, my - psize / 2, psize, psize);
-		}
-	}
-
-	if (context_raw.brush_lazy_radius > 0 && !context_raw.brush_locked &&
-		(context_raw.tool == tool_type_t.BRUSH ||
-			context_raw.tool == tool_type_t.ERASER ||
-			context_raw.tool == tool_type_t.DECAL ||
-			context_raw.tool == tool_type_t.TEXT ||
-			context_raw.tool == tool_type_t.CLONE ||
-			context_raw.tool == tool_type_t.BLUR ||
-			context_raw.tool == tool_type_t.SMUDGE ||
-			context_raw.tool == tool_type_t.PARTICLE)) {
-		draw_filled_rect(mx - 1, my - 1, 2, 2);
-		mx = context_raw.brush_lazy_x * base_w() + base_x();
-		my = context_raw.brush_lazy_y * base_h() + base_y();
-		let radius: f32 = context_raw.brush_lazy_radius * 180;
-		draw_set_color(0xff666666);
-		draw_scaled_image(cursor_img, mx - radius / 2, my - radius / 2, radius, radius);
-		draw_set_color(0xffffffff);
-	}
-	draw_end();
-}
-
-function ui_base_show_material_nodes() {
-	// Clear input state as ui receives input events even when not drawn
-	ui_end_input();
-
-	ui_nodes_show = !ui_nodes_show || ui_nodes_canvas_type != canvas_type_t.MATERIAL;
-	ui_nodes_canvas_type = canvas_type_t.MATERIAL;
-
-	///if (arm_ios || arm_android)
-	if (ui_view2d_show) {
-		ui_view2d_show = false;
-	}
-	///end
-
-	base_resize();
-}
-
-function ui_base_show_brush_nodes() {
-	// Clear input state as ui receives input events even when not drawn
-	ui_end_input();
-	ui_nodes_show = !ui_nodes_show || ui_nodes_canvas_type != canvas_type_t.BRUSH;
-	ui_nodes_canvas_type = canvas_type_t.BRUSH;
-
-	///if (arm_ios || arm_android)
-	if (ui_view2d_show) {
-		ui_view2d_show = false;
-	}
-	///end
-
-	base_resize();
-}
-
-function ui_base_show_2d_view(type: view_2d_type_t) {
-	// Clear input state as ui receives input events even when not drawn
-	ui_end_input();
-	if (ui_view2d_type != type) {
-		ui_view2d_show = true;
-	}
-	else {
-		ui_view2d_show = !ui_view2d_show;
-	}
-	ui_view2d_type = type;
-	ui_view2d_hwnd.redraws = 2;
-
-	///if (arm_ios || arm_android)
-	if (ui_nodes_show) {
-		ui_nodes_show = false;
-	}
-	///end
-
-	base_resize();
-}
-
-function ui_base_toggle_browser() {
-	let minimized: bool = config_raw.layout[layout_size_t.STATUS_H] <= (ui_status_default_status_h * config_raw.window_scale);
-	config_raw.layout[layout_size_t.STATUS_H] = minimized ? 240 : ui_status_default_status_h;
-	config_raw.layout[layout_size_t.STATUS_H] = math_floor(config_raw.layout[layout_size_t.STATUS_H] * config_raw.window_scale);
-}
-
-function ui_base_set_icon_scale() {
-	if (UI_SCALE() > 1) {
-		let res: string[] = ["icons2x.k"];
-		resource_load(res);
-		map_set(resource_bundled, "icons.k", resource_get("icons2x.k"));
-	}
-	else {
-		let res: string[] = ["icons.k"];
-		resource_load(res);
-	}
-}
-
-function ui_base_on_border_hover(handle: ui_handle_t, side: i32) {
-	if (!base_ui_enabled) {
-		return;
-	}
-
-	if (handle != ui_base_hwnds[tab_area_t.SIDEBAR0] &&
-		handle != ui_base_hwnds[tab_area_t.SIDEBAR1] &&
-		handle != ui_base_hwnds[tab_area_t.STATUS] &&
-		handle != ui_nodes_hwnd &&
-		handle != ui_view2d_hwnd) {
-		return; // Scalable handles
-	}
-	if (handle == ui_view2d_hwnd && side != border_side_t.LEFT) {
-		return;
-	}
-	if (handle == ui_nodes_hwnd && side == border_side_t.TOP && !ui_view2d_show) {
-		return;
-	}
-	if (handle == ui_base_hwnds[tab_area_t.SIDEBAR0] && side == border_side_t.TOP) {
-		return;
-	}
-
-	if (handle == ui_nodes_hwnd && side != border_side_t.LEFT && side != border_side_t.TOP) {
-		return;
-	}
-	if (handle == ui_base_hwnds[tab_area_t.STATUS] && side != border_side_t.TOP) {
-		return;
-	}
-	if (side == border_side_t.RIGHT) {
-		return; // UI is snapped to the right side
-	}
-
-	side == border_side_t.LEFT || side == border_side_t.RIGHT ?
-		iron_mouse_set_cursor(cursor_t.SIZEWE) :
-		iron_mouse_set_cursor(cursor_t.SIZENS);
-
-	if (ui.input_started) {
-		ui_base_border_started = side;
-		ui_base_border_handle = handle;
-		base_is_resizing = true;
-	}
-}
-
-function ui_base_on_tab_drop(to: ui_handle_t, to_position: i32, from: ui_handle_t, from_position: i32) {
-	let i: i32 = -1;
-	let j: i32 = -1;
-	for (let k: i32 = 0; k < ui_base_htabs.length; ++k) {
-		if (ui_base_htabs[k] == to) {
-			i = k;
-		}
-		if (ui_base_htabs[k] == from) {
-			j = k;
-		}
-	}
-	if (i == j && to_position == from_position) {
-		return;
-	}
-	if (i > -1 && j > -1) {
-		let tabsi: tab_draw_t[] = ui_base_hwnd_tabs[i];
-		let tabsj: tab_draw_t[] = ui_base_hwnd_tabs[j];
-		let element: tab_draw_t = tabsj[from_position];
-		array_splice(tabsj, from_position, 1);
-		array_insert(tabsi, to_position, element);
-		ui_base_hwnds[i].redraws = 2;
-		ui_base_hwnds[j].redraws = 2;
-	}
-}
-
-function ui_base_tag_ui_redraw() {
-	ui_header_handle.redraws = 2;
-	ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
-	ui_menubar_workspace_handle.redraws = 2;
-	ui_menubar_menu_handle.redraws = 2;
-	ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-	ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
-	ui_toolbar_handle.redraws = 2;
-}
-
-function ui_base_make_empty_envmap(col: i32) {
-	ui_base_viewport_col = col;
-	let b: u8_array_t = u8_array_create(4);
-	b[0] = color_get_rb(col);
-	b[1] = color_get_gb(col);
-	b[2] = color_get_bb(col);
-	b[3] = 255;
-	context_raw.empty_envmap = gpu_create_texture_from_bytes(b, 1, 1);
-}
-
-function ui_base_set_viewport_col(col: i32) {
-	ui_base_make_empty_envmap(col);
-	context_raw.ddirty = 2;
-	if (!context_raw.show_envmap) {
-		scene_world._.envmap = context_raw.empty_envmap;
-	}
-}

+ 1 - 1
paint/sources/ui_nodes.ts

@@ -428,7 +428,7 @@ function ui_nodes_get_canvas_control(controls_down: bool): ui_canvas_control_t {
 		zoom: ui.input_wheel_delta != 0.0 ? -ui.input_wheel_delta / 10 : zoom_delta,
 		controls_down: controls_down
 	};
-	if (base_is_combo_selected()) {
+	if (ui.combo_selected_handle != null) {
 		control.zoom = 0.0;
 	}
 	if (control.zoom != 0.0) {

+ 86 - 0
paint/sources/ui_sidebar.ts

@@ -0,0 +1,86 @@
+
+let ui_sidebar_default_w_mini: i32 = 56;
+let ui_sidebar_default_w_full: i32 = 280;
+
+///if (arm_android || arm_ios)
+let ui_sidebar_default_w: i32 = ui_sidebar_default_w_mini;
+///else
+let ui_sidebar_default_w: i32 = ui_sidebar_default_w_full;
+///end
+
+let ui_sidebar_tabx: i32 = 0;
+let ui_sidebar_hminimized: ui_handle_t = ui_handle_create();
+let ui_sidebar_w_mini: i32 = ui_sidebar_default_w_mini;
+
+function ui_sidebar_render_ui() {
+	// Tabs
+	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_sidebar_w_mini;
+	let expand_button_offset: i32 = config_raw.touch_ui ? math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET()) : 0;
+	ui_sidebar_tabx = iron_window_width() - config_raw.layout[layout_size_t.SIDEBAR_W];
+
+	let _SCROLL_W: i32 = ui.ops.theme.SCROLL_W;
+	if (mini) {
+		ui.ops.theme.SCROLL_W = ui.ops.theme.SCROLL_MINI_W;
+	}
+
+	if (ui_window(ui_base_hwnds[tab_area_t.SIDEBAR0], ui_sidebar_tabx, 0, config_raw.layout[layout_size_t.SIDEBAR_W], config_raw.layout[layout_size_t.SIDEBAR_H0])) {
+		let tabs: tab_draw_t[] = ui_base_hwnd_tabs[tab_area_t.SIDEBAR0];
+		for (let i: i32 = 0; i < (mini ? 1 : tabs.length); ++i) {
+			tabs[i].f(ui_base_htabs[tab_area_t.SIDEBAR0]);
+		}
+	}
+	if (ui_window(ui_base_hwnds[tab_area_t.SIDEBAR1], ui_sidebar_tabx, config_raw.layout[layout_size_t.SIDEBAR_H0], config_raw.layout[layout_size_t.SIDEBAR_W], config_raw.layout[layout_size_t.SIDEBAR_H1] - expand_button_offset)) {
+		let tabs: tab_draw_t[] = ui_base_hwnd_tabs[tab_area_t.SIDEBAR1];
+		for (let i: i32 = 0; i < (mini ? 1 : tabs.length); ++i) {
+			tabs[i].f(ui_base_htabs[tab_area_t.SIDEBAR1]);
+		}
+	}
+
+	ui_end_window();
+	ui.ops.theme.SCROLL_W = _SCROLL_W;
+
+	// Collapse / expand button for mini sidebar
+	if (config_raw.touch_ui) {
+		let width: i32 = config_raw.layout[layout_size_t.SIDEBAR_W];
+		let height: i32 = math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET());
+		if (ui_window(ui_handle(__ID__), iron_window_width() - width, iron_window_height() - height, width, height + 1)) {
+			ui._w = width;
+			let _BUTTON_H: i32 = ui.ops.theme.BUTTON_H;
+			let _BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
+			ui.ops.theme.BUTTON_H = ui.ops.theme.ELEMENT_H;
+			ui.ops.theme.BUTTON_COL = ui.ops.theme.WINDOW_BG_COL;
+			if (ui_button(mini ? "<<" : ">>")) {
+				config_raw.layout[layout_size_t.SIDEBAR_W] = mini ? ui_sidebar_default_w_full : ui_sidebar_default_w_mini;
+				config_raw.layout[layout_size_t.SIDEBAR_W] = math_floor(config_raw.layout[layout_size_t.SIDEBAR_W] * UI_SCALE());
+			}
+			ui.ops.theme.BUTTON_H = _BUTTON_H;
+			ui.ops.theme.BUTTON_COL = _BUTTON_COL;
+		}
+	}
+
+	// Expand button
+	if (config_raw.layout[layout_size_t.SIDEBAR_W] == 0) {
+		let width: i32 = math_floor(draw_string_width(ui.ops.font, ui.font_size, "<<") + 25 * UI_SCALE());
+		if (ui_window(ui_sidebar_hminimized, iron_window_width() - width, 0, width, math_floor(UI_ELEMENT_H() + UI_ELEMENT_OFFSET() + 1))) {
+			ui._w = width;
+			let _BUTTON_H: i32 = ui.ops.theme.BUTTON_H;
+			let _BUTTON_COL: i32 = ui.ops.theme.BUTTON_COL;
+			ui.ops.theme.BUTTON_H = ui.ops.theme.ELEMENT_H;
+			ui.ops.theme.BUTTON_COL = ui.ops.theme.SEPARATOR_COL;
+
+			if (ui_button("<<")) {
+				config_raw.layout[layout_size_t.SIDEBAR_W] = context_raw.maximized_sidebar_width != 0 ? context_raw.maximized_sidebar_width : math_floor(ui_sidebar_default_w * config_raw.window_scale);
+			}
+			ui.ops.theme.BUTTON_H = _BUTTON_H;
+			ui.ops.theme.BUTTON_COL = _BUTTON_COL;
+		}
+	}
+	else if (ui_base_htabs[tab_area_t.SIDEBAR0].changed && ui_base_htabs[tab_area_t.SIDEBAR0].position == context_raw.last_htab0_pos) {
+		if (sys_time() - context_raw.select_time < 0.25) {
+			context_raw.maximized_sidebar_width = config_raw.layout[layout_size_t.SIDEBAR_W];
+			config_raw.layout[layout_size_t.SIDEBAR_W] = 0;
+		}
+		context_raw.select_time = sys_time();
+	}
+	context_raw.last_htab0_pos = ui_base_htabs[tab_area_t.SIDEBAR0].position;
+}

+ 7 - 7
paint/sources/ui_status.ts → paint/sources/ui_statusbar.ts

@@ -1,17 +1,17 @@
 
-let ui_status_default_status_h: i32 = 33;
+let ui_statusbar_default_h: i32 = 33;
 
-function ui_status_init() {
+function ui_statusbar_init() {
 }
 
-function ui_status_width(): i32 {
+function ui_statusbar_width(): i32 {
 	return iron_window_width() - ui_toolbar_w(true) - config_raw.layout[layout_size_t.SIDEBAR_W];
 }
 
-function ui_status_render_ui() {
+function ui_statusbar_render_ui() {
 	let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
 
-	if (ui_window(ui_base_hwnds[tab_area_t.STATUS], sys_x(), iron_window_height() - statush, ui_status_width(), statush)) {
+	if (ui_window(ui_base_hwnds[tab_area_t.STATUS], sys_x(), iron_window_height() - statush, ui_statusbar_width(), statush)) {
 		ui._y += 2;
 
 		// Border
@@ -27,7 +27,7 @@ function ui_status_render_ui() {
 			draw.f(htab);
 		}
 
-		let minimized: bool = statush <= ui_status_default_status_h * config_raw.window_scale;
+		let minimized: bool = statush <= ui_statusbar_default_h * config_raw.window_scale;
 		if (htab.changed && (htab.position == context_raw.last_status_position || minimized)) {
 			ui_base_toggle_browser();
 		}
@@ -35,7 +35,7 @@ function ui_status_render_ui() {
 	}
 }
 
-function ui_status_draw_version_tab(htab: ui_handle_t) {
+function ui_statusbar_draw_version_tab(htab: ui_handle_t) {
 	// Version label
 	if (!config_raw.touch_ui) {
 		ui.enabled = false;

+ 42 - 1
paint/sources/util_mesh.ts

@@ -4,7 +4,7 @@ let util_mesh_unwrappers: map_t<string, any> = map_create(); // JSValue * -> ((a
 function util_mesh_merge(paint_objects: mesh_object_t[] = null) {
 	if (paint_objects == null) {
 		if (context_raw.tool == tool_type_t.GIZMO) {
-			paint_objects = util_mesh_ext_get_unique();
+			paint_objects = util_mesh_get_unique();
 		}
 		else {
 			paint_objects = project_paint_objects;
@@ -433,3 +433,44 @@ function util_mesh_decimate() {
 	// decimate_mesh(mesh);
 	import_mesh_add_mesh(mesh);
 }
+
+function _util_mesh_unique_data_count(): i32 {
+	return util_mesh_get_unique().length;
+}
+
+function util_mesh_pack_uvs(texa: i16_array_t) {
+    // Scale tex coords into global atlas
+	let atlas_w: i32 = config_get_scene_atlas_res();
+	let item_i: i32 = _util_mesh_unique_data_count() - 1; // Add the one being imported
+	let item_w: i32 = config_get_layer_res();
+	let atlas_stride: i32 = atlas_w / item_w;
+	let atlas_step: i32 = 32767 / atlas_stride;
+	let item_x: i32 = (item_i % atlas_stride) * atlas_step;
+	let item_y: i32 = math_floor(item_i / atlas_stride) * atlas_step;
+	for (let i: i32 = 0; i < texa.length / 2; ++i) {
+		texa[i * 2] = texa[i * 2] / atlas_stride + item_x;
+		texa[i * 2 + 1] = texa[i * 2 + 1] / atlas_stride + item_y;
+	}
+}
+
+function util_mesh_get_unique(): mesh_object_t[] {
+	let ar: mesh_object_t[] = [];
+
+	for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
+		if (!project_paint_objects[i].base.visible) {
+			continue;
+		}
+		let found: bool = false;
+		for (let j: i32 = 0; j < i; ++j) {
+			if (project_paint_objects[i].data == project_paint_objects[j].data) {
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			array_push(ar, project_paint_objects[i]);
+		}
+	}
+
+	return ar;
+}

+ 0 - 41
paint/sources/util_mesh_ext.ts

@@ -1,41 +0,0 @@
-
-function _util_mesh_unique_data_count(): i32 {
-	return util_mesh_ext_get_unique().length;
-}
-
-function util_mesh_ext_pack_uvs(texa: i16_array_t) {
-    // Scale tex coords into global atlas
-	let atlas_w: i32 = config_get_scene_atlas_res();
-	let item_i: i32 = _util_mesh_unique_data_count() - 1; // Add the one being imported
-	let item_w: i32 = config_get_layer_res();
-	let atlas_stride: i32 = atlas_w / item_w;
-	let atlas_step: i32 = 32767 / atlas_stride;
-	let item_x: i32 = (item_i % atlas_stride) * atlas_step;
-	let item_y: i32 = math_floor(item_i / atlas_stride) * atlas_step;
-	for (let i: i32 = 0; i < texa.length / 2; ++i) {
-		texa[i * 2] = texa[i * 2] / atlas_stride + item_x;
-		texa[i * 2 + 1] = texa[i * 2 + 1] / atlas_stride + item_y;
-	}
-}
-
-function util_mesh_ext_get_unique(): mesh_object_t[] {
-	let ar: mesh_object_t[] = [];
-
-	for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
-		if (!project_paint_objects[i].base.visible) {
-			continue;
-		}
-		let found: bool = false;
-		for (let j: i32 = 0; j < i; ++j) {
-			if (project_paint_objects[i].data == project_paint_objects[j].data) {
-				found = true;
-				break;
-			}
-		}
-		if (!found) {
-			array_push(ar, project_paint_objects[i]);
-		}
-	}
-
-	return ar;
-}