Files
armorpaint/paint/sources/base.c
T
2026-05-20 19:52:36 +02:00

2451 lines
82 KiB
C

#include "global.h"
typedef enum {
BORDER_SIDE_LEFT = 0,
BORDER_SIDE_RIGHT = 1,
BORDER_SIDE_TOP = 2,
BORDER_SIDE_BOTTOm = 3,
} border_side_t;
i32 base_drag_tint = 0xffffffff;
i32 base_drag_size = -1;
rect_t *base_drag_rect = NULL;
f32 base_drag_start = 0.0;
f32 base_drop_x = 0.0;
f32 base_drop_y = 0.0;
gpu_texture_t *base_color_wheel;
gpu_texture_t *base_color_wheel_gradient;
i32 base_appx = 0;
i32 base_appy = 0;
i32 base_last_window_width = 0;
i32 base_last_window_height = 0;
bool base_start_arm_found = false;
i32 _base_material_count;
i32 ui_base_border_started = 0;
ui_handle_t *ui_base_border_handle = NULL;
char *ui_base_action_paint_remap = "";
i32 ui_base_operator_search_offset = 0;
f32 ui_base_undo_tap_time = 0.0;
f32 ui_base_redo_tap_time = 0.0;
bool _ui_base_operator_search_first;
void base_on_shutdown() {
#if defined(IRON_ANDROID) || defined(IRON_IOS)
project_save(false);
#endif
config_save();
}
void base_on_background() {
// Release keys after alt-tab / win-tab
_key_up(KEY_CODE_ALT, NULL);
_key_up(KEY_CODE_WIN, NULL);
_key_up(KEY_CODE_CONTROL, NULL);
_key_up(KEY_CODE_SHIFT, NULL);
}
void base_on_pause() {}
void base_on_resume() {}
void base_on_foreground() {
g_context->foreground_event = true;
g_context->last_paint_x = -1;
g_context->last_paint_y = -1;
}
void base_on_drop_files(char *drop_path) {
drop_path = string_copy(trim_end(drop_path));
any_array_push(base_drop_paths, drop_path);
}
void base_init_on_start_arm(void *_) {
if (base_start_arm_found) {
import_arm_run_project(project_filepath);
}
g_context->tool = TOOL_TYPE_CURSOR;
// Auto-run script
if (g_project->script_datas != NULL && g_project->script_datas->length > 0) {
minic_ctx_t *ctx = minic_eval(g_project->script_datas->buffer[0]);
}
}
void base_save_window_rect() {
g_config->window_w = iron_window_width();
g_config->window_h = iron_window_height();
g_config->window_x = iron_window_x();
g_config->window_y = iron_window_y();
config_save();
}
void base_save_and_quit_callback(bool save) {
base_save_window_rect();
if (save) {
project_save(true);
}
else {
iron_stop();
}
}
void base_material_dropped() {
// Material drag and dropped onto viewport or layers tab
if (context_in_3d_view()) {
uv_type_t uv_type = keyboard_down("control") ? UV_TYPE_PROJECT : UV_TYPE_UVMAP;
mat4_t decal_mat = uv_type == UV_TYPE_PROJECT ? util_render_get_decal_mat() : mat4_nan();
layers_create_fill_layer(uv_type, decal_mat, -1);
}
if (context_in_layers() && tab_layers_can_drop_new_layer(g_context->drag_dest)) {
uv_type_t uv_type = keyboard_down("control") ? UV_TYPE_PROJECT : UV_TYPE_UVMAP;
mat4_t decal_mat = uv_type == UV_TYPE_PROJECT ? util_render_get_decal_mat() : mat4_nan();
layers_create_fill_layer(uv_type, decal_mat, g_context->drag_dest);
}
else if (context_in_nodes()) {
ui_nodes_accept_material_drop(array_index_of(project_materials, base_drag_material));
}
else if (context_in_materials()) {
tab_materials_accept_material_drop(base_drag_material);
}
gc_unroot(base_drag_material);
base_drag_material = NULL;
}
void base_update_import_asset_done() {
// Asset was material
if (project_materials->length > _base_material_count) {
gc_unroot(base_drag_material);
base_drag_material = g_context->material;
gc_root(base_drag_material);
base_material_dropped();
}
}
void base_handle_drop_paths() {
if (base_drop_paths->length > 0) {
bool wait = false;
#if defined(IRON_LINUX) || defined(IRON_MACOS)
wait = !mouse_moved; // Mouse coords not updated during drag
#endif
if (!wait) {
base_drop_x = mouse_x;
base_drop_y = mouse_y;
char *drop_path = array_shift(base_drop_paths);
import_asset_run(drop_path, base_drop_x, base_drop_y, true, true, NULL);
}
}
}
void base_update(void *_) {
if (mouse_movement_x != 0 || mouse_movement_y != 0) {
iron_mouse_set_cursor(IRON_CURSOR_ARROW);
}
bool has_drag = base_drag_asset != NULL || base_drag_material != NULL || base_drag_layer != NULL || base_drag_file != NULL || base_drag_swatch != NULL ||
base_drag_brush != NULL || base_drag_font != NULL;
if (g_config->touch_ui) {
// Touch and hold to activate dragging
if (base_drag_start < 0.2) {
if (has_drag && mouse_down("left")) {
base_drag_start += sys_real_delta();
}
else {
base_drag_start = 0;
}
has_drag = false;
}
if (mouse_released("left")) {
base_drag_start = 0;
}
bool moved = math_abs(mouse_movement_x) > 1 && math_abs(mouse_movement_y) > 1;
if ((mouse_released("left") || moved) && !has_drag) {
gc_unroot(base_drag_asset);
base_drag_asset = NULL;
gc_unroot(base_drag_swatch);
base_drag_swatch = NULL;
gc_unroot(base_drag_file);
base_drag_file = NULL;
gc_unroot(base_drag_file_icon);
base_drag_file_icon = NULL;
base_is_dragging = false;
gc_unroot(base_drag_material);
base_drag_material = NULL;
gc_unroot(base_drag_layer);
base_drag_layer = NULL;
gc_unroot(base_drag_brush);
base_drag_brush = NULL;
gc_unroot(base_drag_font);
base_drag_font = NULL;
}
// Disable touch scrolling while dragging is active
ui_touch_control = !base_is_dragging;
}
if (has_drag && (mouse_movement_x != 0 || mouse_movement_y != 0)) {
base_is_dragging = true;
}
if (mouse_released("left") && has_drag) {
if (base_drag_asset != NULL) {
// Create image texture
if (context_in_nodes()) {
ui_nodes_accept_asset_drop(array_index_of(project_assets, base_drag_asset));
}
else if (context_in_3d_view()) {
if (ends_with(to_lower_case(base_drag_asset->file), ".hdr")) {
gpu_texture_t *image = project_get_image(base_drag_asset);
import_envmap_run(base_drag_asset->file, image);
}
}
// Create mask
else if (context_in_layers() || context_in_2d_view(VIEW_2D_TYPE_LAYER)) {
layers_create_image_mask(base_drag_asset);
}
else if (context_in_textures()) {
tab_textures_accept_asset_drop(base_drag_asset);
}
gc_unroot(base_drag_asset);
base_drag_asset = NULL;
}
else if (base_drag_swatch != NULL) {
// Create RGB node
if (context_in_nodes()) {
ui_nodes_accept_swatch_drop(base_drag_swatch);
}
else if (context_in_swatches()) {
tab_swatches_accept_swatch_drop(base_drag_swatch);
}
else if (context_in_materials()) {
tab_materials_accept_swatch_drop(base_drag_swatch);
}
else if (context_in_3d_view()) {
i32 color = base_drag_swatch->base;
color = color_set_ab(color, base_drag_swatch->opacity * 255);
layers_create_color_layer(color, base_drag_swatch->occlusion, base_drag_swatch->roughness, base_drag_swatch->metallic, -1);
}
else if (context_in_layers() && tab_layers_can_drop_new_layer(g_context->drag_dest)) {
i32 color = base_drag_swatch->base;
color = color_set_ab(color, base_drag_swatch->opacity * 255);
layers_create_color_layer(color, base_drag_swatch->occlusion, base_drag_swatch->roughness, base_drag_swatch->metallic, g_context->drag_dest);
}
gc_unroot(base_drag_swatch);
base_drag_swatch = NULL;
}
else if (base_drag_file != NULL) {
if (!context_in_browser()) {
base_drop_x = mouse_x;
base_drop_y = mouse_y;
_base_material_count = project_materials->length;
import_asset_run(base_drag_file, base_drop_x, base_drop_y, true, true, &base_update_import_asset_done);
}
gc_unroot(base_drag_file);
base_drag_file = NULL;
gc_unroot(base_drag_file_icon);
base_drag_file_icon = NULL;
}
else if (base_drag_material != NULL) {
base_material_dropped();
}
else if (base_drag_layer != NULL) {
if (context_in_nodes()) {
ui_nodes_accept_layer_drop(array_index_of(project_layers, base_drag_layer));
}
else if (context_in_layers() && base_is_dragging) {
slot_layer_move(base_drag_layer, g_context->drag_dest);
make_material_parse_mesh_material();
}
gc_unroot(base_drag_layer);
base_drag_layer = NULL;
}
else if (base_drag_brush != NULL) {
if (context_in_brushes()) {
tab_brushes_accept_brush_drop(base_drag_brush);
}
gc_unroot(base_drag_brush);
base_drag_brush = NULL;
}
else if (base_drag_font != NULL) {
if (context_in_fonts()) {
tab_fonts_accept_font_drop(base_drag_font);
}
gc_unroot(base_drag_font);
base_drag_font = NULL;
}
iron_mouse_set_cursor(IRON_CURSOR_ARROW);
base_is_dragging = false;
}
if (g_context->color_picker_callback != NULL && (mouse_released("left") || mouse_released("right"))) {
g_context->color_picker_callback = NULL;
context_select_tool(g_context->color_picker_previous_tool);
}
base_handle_drop_paths();
if (g_context->ddirty < 0) {
g_context->ddirty = 0;
}
if (g_context->tool == TOOL_TYPE_CURSOR) {
if (keyboard_down("control") && keyboard_started("d")) {
sim_duplicate();
}
if (keyboard_started("delete")) {
sim_delete();
}
}
// Live material when using sys_time() script node
if (g_context->tool == TOOL_TYPE_MATERIAL) {
bool has_script_node = false;
ui_node_canvas_t *canvas = g_context->material->canvas;
for (i32 i = 0; i < canvas->nodes->length; ++i) {
ui_node_t *n = canvas->nodes->buffer[i];
if (string_equals(n->type, "SCRIPT_CPU")) {
has_script_node = true;
break;
}
}
if (has_script_node) {
layers_update_fill_layers();
iron_delay_idle_sleep();
}
}
render_compass_update();
if (g_config->workspace == WORKSPACE_PLAYER) {
player_update();
}
}
gpu_texture_t *base_get_drag_image() {
base_drag_tint = 0xffffffff;
base_drag_size = -1;
gc_unroot(base_drag_rect);
base_drag_rect = NULL;
if (base_drag_asset != NULL) {
return project_get_image(base_drag_asset);
}
if (base_drag_swatch != NULL) {
base_drag_tint = base_drag_swatch->base;
base_drag_size = 26;
return tab_swatches_empty_get();
}
if (base_drag_file != NULL) {
if (base_drag_file_icon != NULL) {
return base_drag_file_icon;
}
gpu_texture_t *icons = resource_get("icons.k");
gc_unroot(base_drag_rect);
base_drag_rect = string_index_of(base_drag_file, ".") > 0 ? resource_tile50(icons, ICON_FILE) : resource_tile50(icons, ICON_FOLDER_FULL);
gc_root(base_drag_rect);
base_drag_tint = ui->ops->theme->HIGHLIGHT_COL;
return icons;
}
if (base_drag_material != NULL) {
return base_drag_material->image_icon;
}
if (base_drag_brush != NULL) {
return base_drag_brush->image_icon;
}
if (base_drag_font != NULL) {
return base_drag_font->image;
}
if (base_drag_layer != NULL && slot_layer_is_group(base_drag_layer)) {
gpu_texture_t *icons = resource_get("icons.k");
rect_t *folder_closed = resource_tile50(icons, ICON_FOLDER_FULL);
rect_t *folder_open = resource_tile50(icons, ICON_FOLDER_OPEN);
gc_unroot(base_drag_rect);
base_drag_rect = base_drag_layer->show_panel ? folder_open : folder_closed;
gc_root(base_drag_rect);
base_drag_tint = base_darker(ui->ops->theme->LABEL_COL, 0x00202020);
return icons;
}
if (base_drag_layer != NULL && slot_layer_is_mask(base_drag_layer) && base_drag_layer->fill_material == NULL) {
tab_layers_make_mask_preview_rgba32(base_drag_layer);
return g_context->mask_preview_rgba32;
}
if (base_drag_layer != NULL) {
return base_drag_layer->fill_material != NULL ? base_drag_layer->fill_material->image_icon : base_drag_layer->texpaint_preview;
}
return NULL;
}
rect_t *base_get_drag_background() {
gpu_texture_t *icons = resource_get("icons.k");
if (base_drag_layer != NULL && !slot_layer_is_group(base_drag_layer) && base_drag_layer->fill_material == NULL) {
return resource_tile50(icons, ICON_CHECKER);
}
return NULL;
}
void base_init_undo_layers() {
if (history_undo_layers == NULL) {
gc_unroot(history_undo_layers);
history_undo_layers = any_array_create_from_raw((void *[]){}, 0);
gc_root(history_undo_layers);
for (i32 i = 0; i < g_config->undo_steps; ++i) {
i32 len = history_undo_layers->length;
char *ext = string("_undo%s", i32_to_string(len));
slot_layer_t *l = slot_layer_create(ext, LAYER_SLOT_TYPE_LAYER, NULL);
any_array_push(history_undo_layers, l);
}
}
}
void base_render(void *_) {
if (g_context->tool == TOOL_TYPE_CURSOR) {
sim_init();
sim_update();
}
if (g_context->frame == 2) {
util_render_make_material_preview();
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR1]->redraws = 2;
base_init_undo_layers();
make_material_parse_mesh_material();
make_material_parse_paint_material(true);
g_context->ddirty = 0;
}
else if (g_context->frame == 3) {
g_context->ddirty = 3;
}
g_context->frame++;
if (base_is_dragging) {
iron_mouse_set_cursor(IRON_CURSOR_HAND);
gpu_texture_t *img = base_get_drag_image();
f32 scale_factor = UI_SCALE();
f32 size = (base_drag_size == -1 ? 50 : base_drag_size) * scale_factor;
f32 ratio = size / (float)img->width;
f32 h = img->height * ratio;
i32 inv = 0;
draw_begin(NULL, false, 0);
draw_set_color(base_drag_tint);
rect_t *bg_rect = base_get_drag_background();
if (bg_rect != NULL) {
draw_scaled_sub_image(resource_get("icons.k"), bg_rect->x, bg_rect->y, bg_rect->w, bg_rect->h, mouse_x + base_drag_off_x,
mouse_y + base_drag_off_y + inv, size, h - inv * 2);
}
base_drag_rect == NULL ? draw_scaled_image(img, mouse_x + base_drag_off_x, mouse_y + base_drag_off_y + inv, size, h - inv * 2)
: draw_scaled_sub_image(img, base_drag_rect->x, base_drag_rect->y, base_drag_rect->w, base_drag_rect->h,
mouse_x + base_drag_off_x, mouse_y + base_drag_off_y + inv, size, h - inv * 2);
draw_set_color(0xffffffff);
draw_end();
}
bool using_menu = ui_menu_show && mouse_y > ui_header_h;
base_ui_enabled = !ui_box_show && !using_menu && ui->combo_selected_handle == NULL;
if (ui_box_show) {
ui_box_render();
}
if (ui_menu_show) {
ui_menu_render();
}
// Save last pos for continuos paint
g_context->last_paint_vec_x = g_context->paint_vec.x;
g_context->last_paint_vec_y = g_context->paint_vec.y;
#if defined(IRON_ANDROID) || defined(IRON_IOS)
// No mouse move events for touch, re-init last paint position on touch start
if (!mouse_down("left")) {
g_context->last_paint_x = -1;
g_context->last_paint_y = -1;
}
#endif
}
void ui_base_init_on_next_frame(void *_) {
layers_init();
}
void ui_base_view_top() {
bool is_typing = 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);
}
}
}
void ui_base_on_border_hover(ui_handle_t *handle, i32 side) {
if (!base_ui_enabled) {
return;
}
if (handle != ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0] && handle != ui_base_hwnds->buffer[TAB_AREA_SIDEBAR1] &&
handle != ui_base_hwnds->buffer[TAB_AREA_STATUS] && handle != ui_nodes_hwnd && handle != ui_view2d_hwnd) {
return; // Scalable handles
}
if (handle == ui_view2d_hwnd && side != BORDER_SIDE_LEFT) {
return;
}
if (handle == ui_nodes_hwnd && side == BORDER_SIDE_TOP && !ui_view2d_show) {
return;
}
if (handle == ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0] && side == BORDER_SIDE_TOP) {
return;
}
if (handle == ui_nodes_hwnd && side != BORDER_SIDE_LEFT && side != BORDER_SIDE_TOP) {
return;
}
if (handle == ui_base_hwnds->buffer[TAB_AREA_STATUS] && side != BORDER_SIDE_TOP) {
return;
}
if (side == BORDER_SIDE_RIGHT) {
return; // UI is snapped to the right side
}
side == BORDER_SIDE_LEFT || side == BORDER_SIDE_RIGHT ? iron_mouse_set_cursor(IRON_CURSOR_SIZEWE) : iron_mouse_set_cursor(IRON_CURSOR_SIZENS);
if (ui->input_started) {
ui_base_border_started = side;
gc_unroot(ui_base_border_handle);
ui_base_border_handle = handle;
gc_root(ui_base_border_handle);
base_is_resizing = true;
}
}
void ui_base_on_tab_drop(ui_handle_t *to, i32 to_position, ui_handle_t *from, i32 from_position) {
i32 i = -1;
i32 j = -1;
for (i32 k = 0; k < ui_base_htabs->length; ++k) {
if (ui_base_htabs->buffer[k] == to) {
i = k;
}
if (ui_base_htabs->buffer[k] == from) {
j = k;
}
}
if (i == j && to_position == from_position) {
return;
}
if (i > -1 && j > -1) {
tab_draw_t_array_t *tabsi = ui_base_hwnd_tabs->buffer[i];
tab_draw_t_array_t *tabsj = ui_base_hwnd_tabs->buffer[j];
if (tabsj->length == 1) {
return; // Keep at least one tab in place
}
tab_draw_t *element = tabsj->buffer[from_position];
array_splice(tabsj, from_position, 1);
array_insert(tabsi, to_position, element);
ui_base_hwnds->buffer[i]->redraws = 2;
ui_base_hwnds->buffer[j]->redraws = 2;
}
}
bool ui_base_picker_button() {
return ui_icon_button("", ICON_PICKER, UI_ALIGN_CENTER);
}
void ui_base_make_empty_envmap(i32 col) {
ui_base_viewport_col = col;
u8_array_t *b = u8_array_create(4);
b->buffer[0] = color_get_rb(col);
b->buffer[1] = color_get_gb(col);
b->buffer[2] = color_get_bb(col);
b->buffer[3] = 255;
g_context->empty_envmap = gpu_create_texture_from_bytes(b, 1, 1, GPU_TEXTURE_FORMAT_RGBA32);
}
void ui_base_init() {
ui_toolbar_init();
g_context->text_tool_text = string_copy(tr("Text"));
ui_header_init();
ui_statusbar_init();
ui_menubar_init();
ui_header_h = math_floor(ui_header_default_h * g_config->window_scale);
ui_menubar_w = math_floor(ui_menubar_default_w * g_config->window_scale);
if (g_context->empty_envmap == NULL) {
ui_base_make_empty_envmap(base_theme->VIEWPORT_COL);
}
if (g_context->preview_envmap == NULL) {
u8_array_t *b = u8_array_create(4);
b->buffer[0] = 0;
b->buffer[1] = 0;
b->buffer[2] = 0;
b->buffer[3] = 255;
g_context->preview_envmap = gpu_create_texture_from_bytes(b, 1, 1, GPU_TEXTURE_FORMAT_RGBA32);
}
if (g_context->saved_envmap == NULL) {
// raw.saved_envmap = scene_world._envmap;
g_context->default_irradiance = scene_world->_->irradiance;
g_context->default_radiance = scene_world->_->radiance;
g_context->default_radiance_mipmaps = scene_world->_->radiance_mipmaps;
}
scene_world->_->envmap = g_context->show_envmap ? g_context->saved_envmap : g_context->empty_envmap;
g_context->ddirty = 1;
string_array_t *resources = any_array_create_from_raw(
(void *[]){
"cursor.k",
"icons.k",
"icons05x.k",
},
3);
resource_load(resources);
f32 scale = g_config->window_scale;
ui_options_t *ops = GC_ALLOC_INIT(
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);
gc_root(ui);
ui_on_border_hover = ui_base_on_border_hover;
gc_root(ui_on_border_hover);
ui_on_tab_drop = ui_base_on_tab_drop;
gc_root(ui_on_tab_drop);
if (UI_SCALE() > 1) {
ui_base_set_icon_scale();
}
ui_picker_button = ui_base_picker_button;
g_context->gizmo = scene_get_child(".Gizmo");
g_context->gizmo_translate_x = object_get_child(g_context->gizmo, ".TranslateX");
g_context->gizmo_translate_y = object_get_child(g_context->gizmo, ".TranslateY");
g_context->gizmo_translate_z = object_get_child(g_context->gizmo, ".TranslateZ");
g_context->gizmo_scale_x = object_get_child(g_context->gizmo, ".ScaleX");
g_context->gizmo_scale_y = object_get_child(g_context->gizmo, ".ScaleY");
g_context->gizmo_scale_z = object_get_child(g_context->gizmo, ".ScaleZ");
g_context->gizmo_rotate_x = object_get_child(g_context->gizmo, ".RotateX");
g_context->gizmo_rotate_y = object_get_child(g_context->gizmo, ".RotateY");
g_context->gizmo_rotate_z = object_get_child(g_context->gizmo, ".RotateZ");
project_new(false);
if (string_equals(project_filepath, "")) {
sys_notify_on_next_frame(&ui_base_init_on_next_frame, NULL);
}
operator_register("view_top", ui_base_view_top);
}
void ui_base_menu_draw_viewport_mode() {
ui_handle_t *mode_handle = ui_handle(__ID__);
mode_handle->i = g_context->viewport_mode;
ui_text(tr("Viewport Mode"), UI_ALIGN_RIGHT, 0x00000000);
string_array_t *modes = base_get_viewport_modes();
string_array_t *shortcuts = base_get_viewport_mode_shortcuts();
if (gpu_raytrace_supported()) {
any_array_push(modes, tr("Path Traced"));
any_array_push(shortcuts, "p");
}
for (i32 i = 0; i < modes->length; ++i) {
ui_radio(mode_handle, i, modes->buffer[i], shortcuts->buffer[i]);
}
i32 index = string_array_index_of(shortcuts, keyboard_key_code(ui->key_code));
if (ui->is_key_pressed && index != -1) {
mode_handle->i = index;
ui->changed = true;
context_set_viewport_mode(mode_handle->i);
}
else if (mode_handle->changed) {
context_set_viewport_mode(mode_handle->i);
ui->changed = true;
}
}
void ui_base_update_next_frame(void *_) {
export_texture_run(g_context->texture_export_path, false);
}
void ui_base_operator_search_menu_draw() {
ui_menu_h = UI_ELEMENT_H() * 8;
ui_handle_t *search_handle = ui_handle(__ID__);
char *search = ui_text_input(search_handle, "", UI_ALIGN_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, UI_ALIGN_LEFT); // 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_DOWN && ui_base_operator_search_offset < 6) {
ui_base_operator_search_offset++;
}
if (ui->key_code == KEY_CODE_UP && ui_base_operator_search_offset > 0) {
ui_base_operator_search_offset--;
}
}
bool enter = keyboard_down("enter");
i32 count = 0;
i32 BUTTON_COL = ui->ops->theme->BUTTON_COL;
string_array_t *keys = map_keys(config_keymap);
for (i32 i = 0; i < keys->length; ++i) {
char *n = keys->buffer[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_LEFT, any_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;
}
void ui_base_operator_search() {
_ui_base_operator_search_first = true;
ui_menu_draw(&ui_base_operator_search_menu_draw, -1, -1);
}
f32 ui_base_get_radius_increment() {
return 0.1;
}
void ui_base_update_ui_on_next_frame(void *_) {
layers_update_fill_layer(true);
make_material_parse_paint_material(false);
}
rect_t *ui_base_get_brush_stencil_rect() {
i32 w = math_floor(g_context->brush_stencil_image->width * (base_h() / (float)g_context->brush_stencil_image->height) * g_context->brush_stencil_scale);
i32 h = math_floor(base_h() * g_context->brush_stencil_scale);
i32 x = math_floor(base_x() + g_context->brush_stencil_x * base_w());
i32 y = math_floor(base_y() + g_context->brush_stencil_y * base_h());
rect_t *r = GC_ALLOC_INIT(rect_t, {.w = w, .h = h, .x = x, .y = y});
return r;
}
bool ui_base_hit_rect(f32 mx, f32 my, i32 x, i32 y, i32 w, i32 h) {
return mx > x && mx < x + w && my > y && my < y + h;
}
void ui_base_update_ui() {
if (console_message_timer > 0) {
console_message_timer -= sys_delta();
if (console_message_timer <= 0) {
ui_base_hwnds->buffer[TAB_AREA_STATUS]->redraws = 2;
}
}
ui_sidebar_w_mini = math_floor(ui_sidebar_default_w_mini * UI_SCALE());
if (!base_ui_enabled) {
return;
}
render_gizmo_update();
util_layer_update_path();
// Same mapping for paint and rotate (predefined in touch keymap)
if (context_in_3d_view()) {
char *paint_key = any_map_get(config_keymap, "action_paint");
char *rotate_key = any_map_get(config_keymap, "action_rotate");
if (mouse_started("left") && string_equals(paint_key, rotate_key)) {
gc_unroot(ui_base_action_paint_remap);
ui_base_action_paint_remap = string_copy(paint_key);
gc_root(ui_base_action_paint_remap);
util_render_pick_pos_nor_tex();
bool is_mesh = math_abs(g_context->posx_picked) < 50 && math_abs(g_context->posy_picked) < 50 && math_abs(g_context->posz_picked) < 50;
#ifdef IRON_ANDROID
// Allow rotating with both pen and touch, because hovering a pen prevents touch input on android
bool pen_only = false;
#else
bool pen_only = g_context->pen_painting_only;
#endif
bool is_pen = pen_only && pen_down("tip");
// Mesh picked - disable rotate
// Pen painting only - rotate with touch, paint with pen
if ((is_mesh && !pen_only) || is_pen) {
any_map_set(config_keymap, "action_rotate", "");
any_map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
}
// World sphere picked - disable paint
else {
any_map_set(config_keymap, "action_paint", "");
any_map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
}
}
else if (!mouse_down("left") && !string_equals(ui_base_action_paint_remap, "")) {
any_map_set(config_keymap, "action_rotate", ui_base_action_paint_remap);
any_map_set(config_keymap, "action_paint", ui_base_action_paint_remap);
gc_unroot(ui_base_action_paint_remap);
ui_base_action_paint_remap = "";
gc_root(ui_base_action_paint_remap);
}
}
if (g_context->brush_stencil_image != NULL && operator_shortcut(any_map_get(config_keymap, "stencil_transform"), SHORTCUT_TYPE_DOWN)) {
rect_t *r = ui_base_get_brush_stencil_rect();
if (mouse_started("left")) {
g_context->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);
f32 cosa = math_cos(-g_context->brush_stencil_angle);
f32 sina = math_sin(-g_context->brush_stencil_angle);
f32 ox = 0;
f32 oy = -r->h / 2.0;
f32 x = ox * cosa - oy * sina;
f32 y = ox * sina + oy * cosa;
x += r->x + r->w / 2.0;
y += r->y + r->h / 2.0;
g_context->brush_stencil_rotating = ui_base_hit_rect(mouse_x, mouse_y, math_floor(x - 16), math_floor(y - 16), 32, 32);
}
f32 _scale = g_context->brush_stencil_scale;
if (mouse_down("left")) {
if (g_context->brush_stencil_scaling) {
i32 mult = mouse_x > r->x + r->w / 2 ? 1 : -1;
g_context->brush_stencil_scale += mouse_movement_x / 400.0 * mult;
}
else if (g_context->brush_stencil_rotating) {
f32 gizmo_x = r->x + r->w / 2.0;
f32 gizmo_y = r->y + r->h / 2.0;
g_context->brush_stencil_angle = -math_atan2(mouse_y - gizmo_y, mouse_x - gizmo_x) - math_pi() / 2.0;
}
else {
g_context->brush_stencil_x += mouse_movement_x / (float)base_w();
g_context->brush_stencil_y += mouse_movement_y / (float)base_h();
}
}
else {
g_context->brush_stencil_scaling = false;
}
if (mouse_wheel_delta != 0) {
g_context->brush_stencil_scale -= mouse_wheel_delta / 10.0;
}
// Center after scale
f32 ratio = base_h() / (float)g_context->brush_stencil_image->height;
f32 old_w = _scale * g_context->brush_stencil_image->width * ratio;
f32 new_w = g_context->brush_stencil_scale * g_context->brush_stencil_image->width * ratio;
f32 old_h = _scale * base_h();
f32 new_h = g_context->brush_stencil_scale * base_h();
g_context->brush_stencil_x += (old_w - new_w) / (float)base_w() / 2.0;
g_context->brush_stencil_y += (old_h - new_h) / (float)base_h() / 2.0;
}
bool set_clone_source =
g_context->tool == TOOL_TYPE_CLONE &&
operator_shortcut(string("%s+%s", any_map_get(config_keymap, "set_clone_source"), any_map_get(config_keymap, "action_paint")), SHORTCUT_TYPE_DOWN);
bool decal_mask = context_is_decal_mask_paint();
bool down = operator_shortcut(any_map_get(config_keymap, "action_paint"), SHORTCUT_TYPE_DOWN) || decal_mask || set_clone_source ||
operator_shortcut(string("%s+%s", any_map_get(config_keymap, "brush_ruler"), any_map_get(config_keymap, "action_paint")), SHORTCUT_TYPE_DOWN) ||
(pen_down("tip") && !keyboard_down("alt"));
if (g_config->touch_ui) {
if (pen_down("tip")) {
g_context->pen_painting_only = true;
}
else if (g_context->pen_painting_only) {
down = false;
}
}
if (g_context->tool == TOOL_TYPE_PARTICLE) {
down = false;
}
if (down) {
i32 mx = mouse_view_x();
i32 my = mouse_view_y();
i32 ww = sys_w();
if (g_context->paint2d) {
mx -= sys_w();
ww = ui_view2d_ww;
}
if (mx < ww && mx > sys_x() && my < sys_h() && my > sys_y()) {
if (set_clone_source) {
g_context->clone_start_x = mx;
g_context->clone_start_y = my;
}
else {
if (g_context->brush_time == 0 && !base_is_dragging && !base_is_resizing && ui->combo_selected_handle == NULL) { // Paint started
// Draw line
if (operator_shortcut(string("%s+%s", any_map_get(config_keymap, "brush_ruler"), any_map_get(config_keymap, "action_paint")),
SHORTCUT_TYPE_DOWN)) {
g_context->last_paint_vec_x = g_context->last_paint_x;
g_context->last_paint_vec_y = g_context->last_paint_y;
}
history_push_undo = true;
if (g_context->tool == TOOL_TYPE_CLONE && g_context->clone_start_x >= 0.0) { // Clone delta
g_context->clone_delta_x = (g_context->clone_start_x - mx) / (float)ww;
g_context->clone_delta_y = (g_context->clone_start_y - my) / (float)sys_h();
g_context->clone_start_x = -1;
}
else if (g_context->tool == TOOL_TYPE_FILL && g_context->fill_type_handle->i == FILL_TYPE_UV_ISLAND) {
util_uv_uvislandmap_cached = false;
}
}
g_context->brush_time += sys_delta();
brush_output_node_run();
}
}
}
else if (g_context->brush_time > 0) { // Brush released
g_context->brush_time = 0;
g_context->prev_paint_vec_x = -1;
g_context->prev_paint_vec_y = -1;
g_context->brush_blend_dirty = true; // Update brush mask
g_context->layer_preview_dirty = true; // Update layer preview
// New color id picked, update fill layer
if (g_context->tool == TOOL_TYPE_COLORID && g_context->layer->fill_material != NULL) {
sys_notify_on_next_frame(&ui_base_update_ui_on_next_frame, NULL);
}
}
if (g_context->layers_preview_dirty) {
g_context->layers_preview_dirty = false;
g_context->layer_preview_dirty = false;
g_context->mask_preview_last = NULL;
// Update all layer previews
for (i32 i = 0; i < project_layers->length; ++i) {
slot_layer_t *l = project_layers->buffer[i];
if (slot_layer_is_group(l)) {
continue;
}
gpu_texture_t *target = l->texpaint_preview;
if (target == NULL) {
continue;
}
gpu_texture_t *source = 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->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
}
if (g_context->layer != NULL && g_context->layer_preview_dirty && !slot_layer_is_group(g_context->layer)) {
g_context->layer_preview_dirty = false;
g_context->mask_preview_last = NULL;
// Update layer preview
slot_layer_t *l = g_context->layer;
gpu_texture_t *target = l->texpaint_preview;
if (target != NULL) {
gpu_texture_t *source = 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->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
}
}
bool undo_pressed = operator_shortcut(any_map_get(config_keymap, "edit_undo"), SHORTCUT_TYPE_STARTED);
bool redo_pressed =
operator_shortcut(any_map_get(config_keymap, "edit_redo"), SHORTCUT_TYPE_STARTED) || (keyboard_down("control") && keyboard_started("y"));
// Two-finger tap to undo, three-finger tap to redo
if (context_in_3d_view() && g_config->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();
}
}
void ui_base_update(void *_) {
ui_base_update_ui();
operator_update();
string_array_t *keys = map_keys(plugin_map);
for (i32 i = 0; i < keys->length; ++i) {
plugin_t *p = any_map_get(plugin_map, keys->buffer[i]);
if (p->on_update != NULL) {
minic_ctx_call_fn(p->ctx, p->on_update, NULL, 0);
}
}
if (!base_ui_enabled) {
return;
}
if (!ui->is_typing) {
if (operator_shortcut(any_map_get(config_keymap, "toggle_node_editor"), SHORTCUT_TYPE_STARTED)) {
ui_nodes_canvas_type == CANVAS_TYPE_MATERIAL ? ui_base_show_material_nodes() : ui_base_show_brush_nodes();
}
else if (operator_shortcut(any_map_get(config_keymap, "toggle_browser"), SHORTCUT_TYPE_STARTED)) {
ui_base_toggle_browser();
}
else if (operator_shortcut(any_map_get(config_keymap, "toggle_2d_view"), SHORTCUT_TYPE_STARTED)) {
ui_base_show_2d_view(VIEW_2D_TYPE_LAYER);
}
}
if (operator_shortcut(any_map_get(config_keymap, "file_save_as"), SHORTCUT_TYPE_STARTED)) {
project_save_as(false);
}
else if (operator_shortcut(any_map_get(config_keymap, "file_save"), SHORTCUT_TYPE_STARTED)) {
project_save(false);
}
else if (operator_shortcut(any_map_get(config_keymap, "file_open"), SHORTCUT_TYPE_STARTED)) {
project_open();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_open_recent"), SHORTCUT_TYPE_STARTED)) {
box_projects_show();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_reimport_mesh"), SHORTCUT_TYPE_STARTED)) {
project_reimport_mesh();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_reimport_textures"), SHORTCUT_TYPE_STARTED)) {
project_reimport_textures();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_new"), SHORTCUT_TYPE_STARTED)) {
project_new_box();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_export_textures"), SHORTCUT_TYPE_STARTED)) {
if (string_equals(g_context->texture_export_path, "")) { // First export, ask for path
g_context->layers_export = EXPORT_MODE_VISIBLE;
box_export_show_textures();
}
else {
sys_notify_on_next_frame(&ui_base_update_next_frame, NULL);
}
}
else if (operator_shortcut(any_map_get(config_keymap, "file_export_textures_as"), SHORTCUT_TYPE_STARTED)) {
g_context->layers_export = EXPORT_MODE_VISIBLE;
box_export_show_textures();
}
else if (operator_shortcut(any_map_get(config_keymap, "file_import_assets"), SHORTCUT_TYPE_STARTED)) {
project_import_asset(NULL, true);
}
else if (operator_shortcut(any_map_get(config_keymap, "edit_prefs"), SHORTCUT_TYPE_STARTED)) {
box_preferences_show();
}
if (keyboard_started(any_map_get(config_keymap, "view_distract_free")) || (keyboard_started("escape") && !ui_base_show && !ui_box_show)) {
ui_base_toggle_distract_free();
}
if (g_config->experimental && keyboard_started("f5") && g_config->workspace != WORKSPACE_PLAYER) {
// g_config->workspace = WORKSPACE_PLAYER;
// base_update_workspace();
base_run_in_player();
}
#ifdef IRON_LINUX
if (operator_shortcut("alt+enter", SHORTCUT_TYPE_STARTED)) {
base_toggle_fullscreen();
}
#endif
bool decal_mask = context_is_decal_mask();
if (g_context->brush_locked && mouse_moved) {
if (operator_shortcut(any_map_get(config_keymap, "brush_radius"), SHORTCUT_TYPE_DOWN) ||
operator_shortcut(any_map_get(config_keymap, "brush_opacity"), SHORTCUT_TYPE_DOWN) ||
operator_shortcut(any_map_get(config_keymap, "brush_angle"), SHORTCUT_TYPE_DOWN) ||
(decal_mask &&
operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius")), SHORTCUT_TYPE_DOWN))) {
if (g_context->brush_locked) {
if (operator_shortcut(any_map_get(config_keymap, "brush_opacity"), SHORTCUT_TYPE_DOWN)) {
g_context->brush_opacity += mouse_movement_x / 500.0;
g_context->brush_opacity = math_max(0.0, math_min(1.0, g_context->brush_opacity));
g_context->brush_opacity = math_round(g_context->brush_opacity * 100) / 100.0;
g_context->brush_opacity_handle->f = g_context->brush_opacity;
}
else if (operator_shortcut(any_map_get(config_keymap, "brush_angle"), SHORTCUT_TYPE_DOWN)) {
g_context->brush_angle += mouse_movement_x / 5.0;
i32 i = math_floor(g_context->brush_angle);
g_context->brush_angle = i % 360;
if (g_context->brush_angle < 0)
g_context->brush_angle += 360;
g_context->brush_angle_handle->f = g_context->brush_angle;
make_material_parse_paint_material(true);
}
else if (decal_mask && operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius")),
SHORTCUT_TYPE_DOWN)) {
g_context->brush_decal_mask_radius += mouse_movement_x / 150.0;
g_context->brush_decal_mask_radius = math_max(0.01, math_min(4.0, g_context->brush_decal_mask_radius));
g_context->brush_decal_mask_radius = math_round(g_context->brush_decal_mask_radius * 100) / 100.0;
g_context->brush_decal_mask_radius_handle->f = g_context->brush_decal_mask_radius;
}
else {
g_context->brush_radius += mouse_movement_x / 150.0;
g_context->brush_radius = math_max(0.01, math_min(4.0, g_context->brush_radius));
g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0;
}
ui_header_handle->redraws = 2;
}
}
}
bool is_typing = ui->is_typing;
if (!is_typing) {
if (operator_shortcut(any_map_get(config_keymap, "select_material"), SHORTCUT_TYPE_DOWN)) {
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR1]->redraws = 2;
for (i32 i = 1; i < 10; ++i) {
if (keyboard_started(i32_to_string(i))) {
context_select_material(i - 1);
}
}
}
else if (operator_shortcut(any_map_get(config_keymap, "select_layer"), SHORTCUT_TYPE_DOWN)) {
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
for (i32 i = 1; i < 10; ++i) {
if (keyboard_started(i32_to_string(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(any_map_get(config_keymap, "tool_brush"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_BRUSH);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_eraser"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_ERASER);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_fill"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_FILL);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_colorid"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_COLORID);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_decal"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_DECAL);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_text"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_TEXT);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_clone"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_CLONE);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_blur"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_BLUR);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_particle"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_PARTICLE);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_picker"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_PICKER);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_bake"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_BAKE);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_material"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_MATERIAL);
}
else if (operator_shortcut(any_map_get(config_keymap, "tool_cursor"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(TOOL_TYPE_CURSOR);
}
else if (operator_shortcut(any_map_get(config_keymap, "swap_brush_eraser"), SHORTCUT_TYPE_STARTED)) {
context_select_tool(g_context->tool == TOOL_TYPE_BRUSH ? TOOL_TYPE_ERASER : TOOL_TYPE_BRUSH);
}
}
// Radius
if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_DECAL ||
g_context->tool == TOOL_TYPE_TEXT || g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR ||
g_context->tool == TOOL_TYPE_PARTICLE) {
if (operator_shortcut(any_map_get(config_keymap, "brush_radius"), SHORTCUT_TYPE_STARTED) ||
operator_shortcut(any_map_get(config_keymap, "brush_opacity"), SHORTCUT_TYPE_STARTED) ||
operator_shortcut(any_map_get(config_keymap, "brush_angle"), SHORTCUT_TYPE_STARTED) ||
(decal_mask && operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius")),
SHORTCUT_TYPE_STARTED))) {
g_context->brush_locked = true;
if (!pen_connected) {
iron_mouse_lock();
}
}
else if (operator_shortcut(any_map_get(config_keymap, "brush_radius_decrease"), SHORTCUT_TYPE_REPEAT)) {
g_context->brush_radius -= ui_base_get_radius_increment();
g_context->brush_radius = math_max(math_round(g_context->brush_radius * 100) / 100.0, 0.01);
ui_header_handle->redraws = 2;
}
else if (operator_shortcut(any_map_get(config_keymap, "brush_radius_increase"), SHORTCUT_TYPE_REPEAT)) {
g_context->brush_radius += ui_base_get_radius_increment();
g_context->brush_radius = math_round(g_context->brush_radius * 100) / 100.0;
ui_header_handle->redraws = 2;
}
else if (decal_mask) {
if (operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius_decrease")),
SHORTCUT_TYPE_REPEAT)) {
g_context->brush_decal_mask_radius -= ui_base_get_radius_increment();
g_context->brush_decal_mask_radius = math_max(math_round(g_context->brush_decal_mask_radius * 100) / 100.0, 0.01);
g_context->brush_decal_mask_radius_handle->f = g_context->brush_decal_mask_radius;
ui_header_handle->redraws = 2;
}
else if (operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius_increase")),
SHORTCUT_TYPE_REPEAT)) {
g_context->brush_decal_mask_radius += ui_base_get_radius_increment();
g_context->brush_decal_mask_radius = math_round(g_context->brush_decal_mask_radius * 100) / 100.0;
g_context->brush_decal_mask_radius_handle->f = g_context->brush_decal_mask_radius;
ui_header_handle->redraws = 2;
}
}
}
if (decal_mask && (operator_shortcut(any_map_get(config_keymap, "decal_mask"), SHORTCUT_TYPE_STARTED) ||
operator_shortcut(any_map_get(config_keymap, "decal_mask"), SHORTCUT_TYPE_RELEASED))) {
ui_header_handle->redraws = 2;
}
// Viewpoint
if (mouse_view_x() < sys_w()) {
if (operator_shortcut(any_map_get(config_keymap, "view_reset"), SHORTCUT_TYPE_STARTED)) {
viewport_reset();
viewport_scale_to_bounds(2.0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_back"), SHORTCUT_TYPE_STARTED)) {
viewport_set_view(0, 1, 0, math_pi() / 2.0, 0, math_pi());
}
else if (operator_shortcut(any_map_get(config_keymap, "view_front"), SHORTCUT_TYPE_STARTED)) {
viewport_set_view(0, -1, 0, math_pi() / 2.0, 0, 0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_left"), SHORTCUT_TYPE_STARTED)) {
viewport_set_view(-1, 0, 0, math_pi() / 2.0, 0, -math_pi() / 2.0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_right"), SHORTCUT_TYPE_STARTED)) {
viewport_set_view(1, 0, 0, math_pi() / 2.0, 0, math_pi() / 2.0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_bottom"), SHORTCUT_TYPE_STARTED)) {
viewport_set_view(0, 0, -1, math_pi(), 0, math_pi());
}
else if (operator_shortcut(any_map_get(config_keymap, "view_camera_type"), SHORTCUT_TYPE_STARTED)) {
g_context->camera_type = g_context->camera_type == CAMERA_TYPE_PERSPECTIVE ? CAMERA_TYPE_ORTHOGRAPHIC : CAMERA_TYPE_PERSPECTIVE;
g_context->cam_handle->i = g_context->camera_type;
viewport_update_camera_type(g_context->camera_type);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_orbit_left"), SHORTCUT_TYPE_REPEAT)) {
viewport_orbit(-math_pi() / 12.0, 0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_orbit_right"), SHORTCUT_TYPE_REPEAT)) {
viewport_orbit(math_pi() / 12.0, 0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_orbit_up"), SHORTCUT_TYPE_REPEAT)) {
viewport_orbit(0, -math_pi() / 12.0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_orbit_down"), SHORTCUT_TYPE_REPEAT)) {
viewport_orbit(0, math_pi() / 12.0);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_orbit_opposite"), SHORTCUT_TYPE_STARTED)) {
viewport_orbit_opposite();
}
else if (operator_shortcut(any_map_get(config_keymap, "view_zoom_in"), SHORTCUT_TYPE_REPEAT)) {
viewport_zoom(0.2);
}
else if (operator_shortcut(any_map_get(config_keymap, "view_zoom_out"), SHORTCUT_TYPE_REPEAT)) {
viewport_zoom(-0.2);
}
else if (operator_shortcut(any_map_get(config_keymap, "viewport_mode"), SHORTCUT_TYPE_STARTED)) {
ui->is_key_pressed = false;
ui_menu_draw(&ui_base_menu_draw_viewport_mode, -1, -1);
}
}
if (operator_shortcut(any_map_get(config_keymap, "operator_search"), SHORTCUT_TYPE_STARTED)) {
ui_base_operator_search();
}
}
if (g_context->brush_locked) {
bool b = g_context->brush_locked && !operator_shortcut(any_map_get(config_keymap, "brush_radius"), SHORTCUT_TYPE_DOWN) &&
!operator_shortcut(any_map_get(config_keymap, "brush_opacity"), SHORTCUT_TYPE_DOWN) &&
!operator_shortcut(any_map_get(config_keymap, "brush_angle"), SHORTCUT_TYPE_DOWN) &&
!(decal_mask && operator_shortcut(string("%s+%s", any_map_get(config_keymap, "decal_mask"), any_map_get(config_keymap, "brush_radius")),
SHORTCUT_TYPE_DOWN));
if (b) {
iron_mouse_unlock();
g_context->last_paint_x = -1;
g_context->last_paint_y = -1;
g_context->brush_locked = false;
}
}
// 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_LEFT) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_W] -= math_floor(mouse_movement_x);
if (g_config->layout->buffer[LAYOUT_SIZE_NODES_W] < 32) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_W] = 32;
}
else if (g_config->layout->buffer[LAYOUT_SIZE_NODES_W] > iron_window_width() * 0.7) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_W] = math_floor(iron_window_width() * 0.7);
}
}
else { // UINodes / UIView2D ratio
g_config->layout->buffer[LAYOUT_SIZE_NODES_H] -= math_floor(mouse_movement_y);
if (g_config->layout->buffer[LAYOUT_SIZE_NODES_H] < 32) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_H] = 32;
}
else if (g_config->layout->buffer[LAYOUT_SIZE_NODES_H] > sys_h() * 0.95) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_H] = math_floor(sys_h() * 0.95);
}
}
}
else if (ui_base_border_handle == ui_base_hwnds->buffer[TAB_AREA_STATUS]) {
i32 my = math_floor(mouse_movement_y);
if (g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] - my >= ui_statusbar_default_h * g_config->window_scale &&
g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] - my < iron_window_height() * 0.7) {
g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] -= my;
}
}
else { // Sidebar
if (ui_base_border_started == BORDER_SIDE_LEFT) {
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] -= math_floor(mouse_movement_x);
if (g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] < ui_sidebar_w_mini) {
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] = ui_sidebar_w_mini;
}
else if (g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] > iron_window_width() - ui_sidebar_w_mini * 2) {
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] = iron_window_width() - ui_sidebar_w_mini * 2;
}
if (ui_nodes_show || ui_base_show) {
// Scale down node view if viewport is already at minimal size
if (g_config->layout->buffer[LAYOUT_SIZE_NODES_W] + g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] + ui_sidebar_w_mini >
iron_window_width()) {
g_config->layout->buffer[LAYOUT_SIZE_NODES_W] =
iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - ui_sidebar_w_mini;
}
}
}
else {
i32 my = math_floor(mouse_movement_y);
if (ui_base_border_handle == ui_base_hwnds->buffer[TAB_AREA_SIDEBAR1] && ui_base_border_started == BORDER_SIDE_TOP) {
if (g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0] + my > 32 && g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H1] - my > 32) {
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0] += my;
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H1] -= my;
}
}
}
}
base_resize();
}
if (!mouse_down("left")) {
gc_unroot(ui_base_border_handle);
ui_base_border_handle = NULL;
base_is_resizing = false;
}
if (g_context->tool == TOOL_TYPE_PARTICLE) {
util_particle_init_physics();
physics_world_t *world = physics_world_active;
physics_world_update(world);
for (i32 i = 0; i < 32; ++i) {
if (g_context->particles[i].timer != NULL && g_context->particles[i].timer->delay <= 0) {
physics_body_remove(g_context->particles[i].body);
mesh_object_remove((mesh_object_t *)g_context->particles[i].bullet->ext);
memset(&g_context->particles[i], 0, sizeof(g_context->particles[i]));
}
}
bool any_active = false;
for (i32 i = 0; i < 32; ++i) {
if (g_context->particles[i].timer != NULL) {
any_active = true;
break;
}
}
if (any_active) {
g_context->ddirty = 2;
g_context->rdirty = 2;
iron_delay_idle_sleep();
}
static f64 particle_last_spawn_time = 0.0;
bool particle_just_fired = false;
if (mouse_down("left") && context_in_paint_area() && !g_context->paint2d && (mouse_started("left") || sys_time() - particle_last_spawn_time >= 0.2)) {
particle_last_spawn_time = sys_time();
i32 slot = -1;
for (i32 i = 0; i < 32; ++i) {
if (g_context->particles[i].timer == NULL) {
slot = i;
break;
}
}
if (slot >= 0) {
if (mouse_started("left")) {
history_push_undo = true;
g_context->brush_blend_dirty = true;
}
object_t *o = scene_spawn_object(".Sphere", NULL, true);
mesh_object_t *mo = o->ext;
mo->base->name = ".Bullet";
mo->base->visible = true;
util_render_pick_pos_nor_tex();
f32 nx = g_context->norx_picked;
f32 ny = g_context->nory_picked;
f32 nz = g_context->norz_picked;
f32 sphere_radius = g_context->brush_radius * 0.1f;
f32 spawn_h = -(sphere_radius + g_context->particle_spawn_distance) + 0.01;
mo->base->transform->loc =
(vec4_t){g_context->posx_picked + nx * spawn_h, g_context->posy_picked + ny * spawn_h, g_context->posz_picked + nz * spawn_h, 1.0};
mo->base->transform->scale = (vec4_t){g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, g_context->brush_radius * 0.2, 1.0};
transform_build_matrix(mo->base->transform);
physics_body_t *body = physics_body_create();
body->shape = PHYSICS_SHAPE_SPHERE;
body->mass = g_context->particle_mass;
physics_body_init(body, mo->base);
// Random direction
f32 rx = math_random() * 2.0f - 1.0f;
f32 ry = math_random() * 2.0f - 1.0f;
f32 rz = math_random() * 2.0f - 1.0f;
f32 rlen = sqrtf(rx * rx + ry * ry + rz * rz);
if (rlen < 0.0001f)
rlen = 0.0001f;
rx /= rlen;
ry /= rlen;
rz /= rlen;
if (rx * nx + ry * ny + rz * nz < 0.0f) {
rx = -rx;
ry = -ry;
rz = -rz;
}
f32 r = g_context->particle_random;
f32 dx = nx + (rx - nx) * r;
f32 dy = ny + (ry - ny) * r;
f32 dz = nz + (rz - nz) * r;
f32 impulse = g_context->particle_spawn_distance * 30.0f;
physics_body_apply_impulse(body, (vec4_t){dx * impulse, dy * impulse, dz * impulse, 0.0});
g_context->particles[slot].body = body;
g_context->particles[slot].bullet = mo->base;
g_context->particles[slot].timer = tween_timer(g_context->particle_lifetime, NULL, NULL);
particle_just_fired = true;
}
}
if (mouse_released("left")) {
g_context->layer_preview_dirty = true;
}
#ifdef arm_physics
if (!particle_just_fired) {
for (i32 i = 0; i < 32; ++i) {
if (g_context->particles[i].timer == NULL) {
continue;
}
physics_pair_t_array_t *pairs = physics_world_get_contact_pairs(world, g_context->particles[i].body);
if (pairs != NULL && pairs->length > 0) {
physics_pair_t *p = pairs->buffer[0];
g_context->particles[i].hit_last_x = g_context->particles[i].hit_x != 0 ? g_context->particles[i].hit_x : p->pos_a_x;
g_context->particles[i].hit_last_y = g_context->particles[i].hit_y != 0 ? g_context->particles[i].hit_y : p->pos_a_y;
g_context->particles[i].hit_last_z = g_context->particles[i].hit_z != 0 ? g_context->particles[i].hit_z : p->pos_a_z;
g_context->particles[i].hit_x = p->pos_a_x;
g_context->particles[i].hit_y = p->pos_a_y;
g_context->particles[i].hit_z = p->pos_a_z;
g_context->particles[i].hit_nor_x = p->nor_x;
g_context->particles[i].hit_nor_y = p->nor_y;
g_context->particles[i].hit_nor_z = p->nor_z;
g_context->particles[i].contact_time += sys_delta();
g_context->pdirty = 1;
}
else {
g_context->particles[i].hit_x = 0.0;
}
}
}
#endif
}
}
void ui_base_render(void *_) {
if (!ui_base_show && g_config->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), false)) {
if (ui_button(tr("Close"), UI_ALIGN_CENTER, "")) {
ui_base_toggle_distract_free();
}
}
ui_end();
}
if (!ui_base_show) {
return;
}
ui->input_enabled = base_ui_enabled;
// Remember last tab positions
for (i32 i = 0; i < ui_base_htabs->length; ++i) {
if (ui_base_htabs->buffer[i]->changed) {
g_config->layout_tabs->buffer[i] = ui_base_htabs->buffer[i]->i;
config_save();
}
}
// Set tab positions
for (i32 i = 0; i < ui_base_htabs->length; ++i) {
ui_base_htabs->buffer[i]->i = g_config->layout_tabs->buffer[i];
}
// Nothing to display in the main area
if (!base_view3d_show && !ui_nodes_show && !ui_view2d_show) {
draw_begin(NULL, true, base_theme->SEPARATOR_COL);
gpu_texture_t *img = data_get_image("badge_bw.k");
draw_set_color(0x22ffffff);
draw_image(img, base_view3d_w() / 2.0 - img->width / 2.0, base_h() / 2.0 - img->height / 2.0);
draw_end();
}
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;
}
void ui_base_render_cursor(void *_) {
if (!base_ui_enabled) {
return;
}
if (g_context->tool == TOOL_TYPE_MATERIAL || g_context->tool == TOOL_TYPE_BAKE) {
return;
}
draw_begin(NULL, false, 0);
draw_set_color(0xffffffff);
g_context->view_index = g_context->view_index_last;
i32 mx = base_x() + g_context->paint_vec.x * base_w();
i32 my = base_y() + g_context->paint_vec.y * base_h();
g_context->view_index = -1;
if (g_context->brush_stencil_image != NULL && g_context->tool != TOOL_TYPE_PICKER && g_context->tool != TOOL_TYPE_COLORID) {
rect_t *r = ui_base_get_brush_stencil_rect();
if (!operator_shortcut(any_map_get(config_keymap, "stencil_hide"), SHORTCUT_TYPE_DOWN)) {
draw_set_color(0x88ffffff);
f32 angle = g_context->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(g_context->brush_stencil_image, r->x, r->y, r->w, r->h);
draw_set_transform(mat3_nan());
draw_set_color(0xffffffff);
}
bool transform = operator_shortcut(any_map_get(config_keymap, "stencil_transform"), SHORTCUT_TYPE_DOWN);
if (transform) {
// Outline
draw_rect(r->x, r->y, r->w, r->h, 1.0);
// Scale
draw_rect(r->x - 8, r->y - 8, 16, 16, 1.0);
draw_rect(r->x - 8 + r->w, r->y - 8, 16, 16, 1.0);
draw_rect(r->x - 8, r->y - 8 + r->h, 16, 16, 1.0);
draw_rect(r->x - 8 + r->w, r->y - 8 + r->h, 16, 16, 1.0);
// Rotate
f32 cosa = math_cos(-g_context->brush_stencil_angle);
f32 sina = math_sin(-g_context->brush_stencil_angle);
f32 ox = 0;
f32 oy = -r->h / 2.0;
f32 x = ox * cosa - oy * sina;
f32 y = ox * sina + oy * cosa;
x += r->x + r->w / 2.0;
y += r->y + r->h / 2.0;
draw_filled_circle(x, y, 8, 0);
}
}
if (g_context->tool == TOOL_TYPE_PICKER) {
// Show picker icon when picking
if (g_context->color_picker_callback != NULL) {
gpu_texture_t *img = resource_get("icons.k");
rect_t *rect = resource_tile50(img, TOOL_TYPE_PICKER);
draw_sub_image(img, rect->x, rect->y, rect->w, rect->h, mx + 10, my + 10);
}
// Show picked material next to cursor
else if (g_context->picker_select_material) {
gpu_texture_t *img = g_context->material->image_icon;
draw_image(img, mx + 10, my + 10);
}
}
gpu_texture_t *cursor_img = resource_get("cursor.k");
i32 psize = math_floor(182 * (g_context->brush_radius * 0.5 * g_context->brush_nodes_radius) * UI_SCALE());
// Clone source cursor
if (g_context->tool == TOOL_TYPE_CLONE && !keyboard_down("alt") && (mouse_down("left") || pen_down("tip"))) {
draw_set_color(0x66ffffff);
draw_scaled_image(cursor_img, mx + g_context->clone_delta_x * sys_w() - psize / 2.0, my + g_context->clone_delta_y * sys_h() - psize / 2.0, psize,
psize);
draw_set_color(0xffffffff);
}
bool decal = context_is_decal();
if (context_in_2d_view(VIEW_2D_TYPE_LAYER) || decal) {
bool decal_mask = context_is_decal_mask();
if (decal && !context_in_nodes()) {
f32 decal_alpha = 0.5;
if (!decal_mask) {
g_context->decal_x = g_context->paint_vec.x;
g_context->decal_y = g_context->paint_vec.y;
decal_alpha = g_context->brush_opacity;
}
if (!g_config->brush_live && (!context_is_decal() || context_in_2d_view(VIEW_2D_TYPE_LAYER))) {
i32 psizex = math_floor(182 * 0.5 * 0.92 * UI_SCALE() * (g_context->brush_radius * g_context->brush_nodes_radius * g_context->brush_scale_x) *
ui_view2d_pan_scale);
i32 psizey = math_floor(182 * 0.5 * 0.92 * UI_SCALE() * (g_context->brush_radius * g_context->brush_nodes_radius) * ui_view2d_pan_scale);
g_context->view_index = g_context->view_index_last;
f32 decalx = base_x() + g_context->decal_x * base_w() - psizex / 2.0;
f32 decaly = base_y() + g_context->decal_y * base_h() - psizey / 2.0;
g_context->view_index = -1;
draw_set_color(color_from_floats(1, 1, 1, decal_alpha));
f32 angle = (g_context->brush_angle + g_context->brush_nodes_angle) * (math_pi() / 180.0);
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(g_context->decal_image, decalx, decaly, psizex, psizey);
draw_set_transform(mat3_nan());
draw_set_color(0xffffffff);
}
}
if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE ||
g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE || (decal_mask && context_in_2d_view(VIEW_2D_TYPE_LAYER))) {
if (decal_mask) {
psize = math_floor(cursor_img->width * (g_context->brush_decal_mask_radius * g_context->brush_nodes_radius) * UI_SCALE());
}
if (context_in_2d_view(VIEW_2D_TYPE_LAYER)) {
psize = math_floor(psize * ui_view2d_pan_scale);
}
draw_scaled_image(cursor_img, mx - psize / 2.0, my - psize / 2.0, psize, psize);
}
}
if (g_context->brush_lazy_radius > 0 && !g_context->brush_locked && !slot_layer_is_path(g_context->layer) &&
(g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_DECAL || g_context->tool == TOOL_TYPE_TEXT ||
g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE)) {
draw_filled_rect(mx - 1, my - 1, 2, 2);
mx = g_context->brush_lazy_x * base_w() + base_x();
my = g_context->brush_lazy_y * base_h() + base_y();
f32 radius = g_context->brush_lazy_radius * 180;
draw_set_color(0xff666666);
draw_scaled_image(cursor_img, mx - radius / 2.0, my - radius / 2.0, radius, radius);
draw_set_color(0xffffffff);
}
draw_end();
}
void base_init() {
base_last_window_width = iron_window_width();
base_last_window_height = iron_window_height();
sys_notify_on_drop_files(&base_on_drop_files);
sys_notify_on_app_state(&base_on_foreground, &base_on_resume, &base_on_pause, &base_on_background, &base_on_shutdown);
iron_set_save_and_quit_callback(base_save_and_quit_callback);
base_font = data_get_font("font.ttf");
gc_root(base_font);
base_color_wheel = data_get_image("color_wheel.k");
gc_root(base_color_wheel);
base_color_wheel_gradient = data_get_image("color_wheel_gradient.k");
gc_root(base_color_wheel_gradient);
config_load_theme(g_config->theme, false);
base_default_element_w = base_theme->ELEMENT_W;
base_default_element_h = base_theme->ELEMENT_H;
base_default_font_size = base_theme->FONT_SIZE;
translator_load_translations(g_config->locale);
ui_files_filename = string_copy(tr("untitled"));
gc_root(ui_files_filename);
#if defined(IRON_ANDROID) || defined(IRON_IOS)
sys_title_set(tr("untitled"));
#endif
// Baked font for fast startup
if (string_equals(g_config->locale, "en")) {
draw_font_13(base_font);
}
else {
draw_font_init(base_font);
}
ui_nodes_enum_texts = base_combo_enum_texts;
ui_nodes_enum_images = base_combo_enum_images;
gc_root(ui_nodes_enum_texts);
// Init plugins
if (g_config->plugins != NULL) {
for (i32 i = 0; i < g_config->plugins->length; ++i) {
char *plugin = g_config->plugins->buffer[i];
plugin_start(plugin);
}
}
args_parse();
camera_init();
ui_base_init();
ui_viewnodes_init();
ui_view2d_init();
sys_notify_on_update(base_update, NULL);
sys_notify_on_update(ui_view2d_update, NULL);
sys_notify_on_update(ui_nodes_update, NULL);
sys_notify_on_update(ui_base_update, NULL);
sys_notify_on_update(camera_update, NULL);
sys_notify_on_render(ui_view2d_render, NULL);
sys_notify_on_render(ui_base_render_cursor, NULL);
sys_notify_on_render(ui_nodes_render, NULL);
sys_notify_on_render(ui_base_render, NULL);
sys_notify_on_render(base_render, NULL);
base_appx = ui_toolbar_w(true);
base_appy = 0;
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 1) {
base_appy = ui_header_h * 2;
}
scene_camera->data->fov = math_floor(scene_camera->data->fov * 100) / 100.0;
camera_object_build_proj(scene_camera, -1.0);
args_run();
if (g_config->workspace != WORKSPACE_PAINT_3D) {
base_update_workspace();
}
if (g_config->workflow != WORKFLOW_PBR) {
base_update_workflow();
}
g_context->camera_pivot = g_config->camera_pivot;
g_context->camera_controls = g_config->camera_controls;
bool has_projects = g_config->recent_projects->length > 0;
if (g_config->splash_screen && has_projects) {
box_projects_show();
}
// Startup project
char *start_arm = string("%s/start.arm", iron_internal_files_location());
if (iron_file_exists(start_arm)) {
gc_unroot(project_filepath);
project_filepath = start_arm;
gc_root(project_filepath);
base_start_arm_found = true;
args_player = true;
}
if (args_player) {
// base_player_lock = true;
g_config->workspace = WORKSPACE_PLAYER;
base_update_workspace();
make_material_parse_paint_material(true);
sys_notify_on_next_frame(&base_init_on_start_arm, NULL);
}
}
i32 base_w() {
// Drawing material preview
if (g_context->material_preview) {
return util_render_material_preview_size;
}
// Drawing decal preview
if (g_context->decal_preview) {
return util_render_decal_preview_size;
}
if (g_context->paint2d_view) {
return ui_view2d_ww;
}
// 3D view is hidden
if (!base_view3d_show) {
return 1;
}
i32 res = base_view3d_w();
return res > 0 ? res : 1; // App was minimized, force render path resize
}
i32 base_view3d_w() {
i32 res = 0;
if (g_config->layout == NULL) {
i32 sidebarw = ui_sidebar_default_w;
res = iron_window_width() - sidebarw - ui_toolbar_default_w;
}
else if (ui_nodes_show || ui_view2d_show) {
res = iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - g_config->layout->buffer[LAYOUT_SIZE_NODES_W] - ui_toolbar_w(true);
}
else if (ui_base_show) {
res = iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - ui_toolbar_w(true);
}
else { // Distract free
res = iron_window_width();
}
if (g_context->view_index > -1) {
res = math_ceil(res / 2.0);
}
if (g_context->paint2d_view) {
res = ui_view2d_ww;
}
return res;
}
i32 base_h() {
// Drawing material preview
if (g_context->material_preview) {
return util_render_material_preview_size;
}
// Drawing decal preview
if (g_context->decal_preview) {
return util_render_decal_preview_size;
}
i32 res = iron_window_height();
if (g_config->layout == NULL) {
res -= ui_header_default_h * 2 + ui_statusbar_default_h;
#if defined(IRON_ANDROID) || defined(IRON_IOS)
res += ui_header_h;
#endif
}
else if (ui_base_show && res > 0) {
i32 statush = g_config->layout->buffer[LAYOUT_SIZE_STATUS_H];
res -= math_floor(ui_header_default_h * 2 * g_config->window_scale) + statush;
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 0) {
res += ui_header_h * 2;
}
if (!base_view3d_show) {
res += ui_header_h * 4;
}
}
return res > 0 ? res : 1; // App was minimized, force render path resize
}
i32 base_x() {
return g_context->view_index == 1 ? base_appx + base_w() : base_appx;
}
i32 base_y() {
return base_appy;
}
void base_on_resize() {
if (iron_window_width() == 0 || iron_window_height() == 0) {
return;
}
f32 ratio_w = iron_window_width() / (float)base_last_window_width;
base_last_window_width = iron_window_width();
f32 ratio_h = iron_window_height() / (float)base_last_window_height;
base_last_window_height = iron_window_height();
g_config->layout->buffer[LAYOUT_SIZE_NODES_W] = math_floor(g_config->layout->buffer[LAYOUT_SIZE_NODES_W] * ratio_w);
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0] = math_floor(g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0] * ratio_h);
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H1] = iron_window_height() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0];
base_resize();
base_save_window_rect();
}
void base_resize() {
if (iron_window_width() == 0 || iron_window_height() == 0) {
return;
}
camera_object_t *cam = scene_camera;
if (cam->data->ortho != NULL) {
cam->data->ortho->buffer[2] = -2 * (sys_h() / (float)sys_w());
cam->data->ortho->buffer[3] = 2 * (sys_h() / (float)sys_w());
}
camera_object_build_proj(cam, -1.0);
scene_camera->frame = 0;
if (g_context->camera_type == CAMERA_TYPE_ORTHOGRAPHIC) {
viewport_update_camera_type(g_context->camera_type);
}
g_context->ddirty = 2;
if (ui_base_show && base_view3d_show) {
base_appx = ui_toolbar_w(true);
base_appy = 0;
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 1) {
base_appy = ui_header_h * 2;
}
}
else {
base_appx = -1;
base_appy = 0;
}
ui_nodes_grid_redraw = true;
ui_view2d_grid_redraw = true;
base_redraw_ui();
}
string_array_t *base_combo_enum_texts(char *node_type) {
if (string_equals(node_type, "TEX_IMAGE")) {
if (project_asset_names->length > 0) {
return project_asset_names;
}
else {
string_array_t *empty = any_array_create_from_raw(
(void *[]){
"",
},
1);
return empty;
}
}
if (string_equals(node_type, "LAYER") || string_equals(node_type, "LAYER_MASK")) {
string_array_t *layer_names = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < project_layers->length; ++i) {
slot_layer_t *l = project_layers->buffer[i];
any_array_push(layer_names, l->name);
}
return layer_names;
}
if (string_equals(node_type, "MATERIAL")) {
string_array_t *material_names = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < project_materials->length; ++i) {
slot_material_t *m = project_materials->buffer[i];
any_array_push(material_names, m->canvas->name);
}
return material_names;
}
if (string_equals(node_type, "image_texture_node")) {
if (project_asset_names->length > 0) {
return project_asset_names;
}
else {
string_array_t *empty = any_array_create_from_raw(
(void *[]){
"",
},
1);
return empty;
}
}
return NULL;
}
any_array_t *base_combo_enum_images(char *node_type) {
if (string_equals(node_type, "TEX_IMAGE")) {
any_array_t *ar = any_array_create(0);
for (i32 i = 0; i < project_assets->length; ++i) {
asset_t *asset = project_assets->buffer[i];
gpu_texture_t *img = project_get_image(asset);
any_array_push(ar, img);
}
return ar;
}
if (string_equals(node_type, "LAYER") || string_equals(node_type, "LAYER_MASK")) {
any_array_t *ar = any_array_create(0);
for (i32 i = 0; i < project_layers->length; ++i) {
slot_layer_t *l = project_layers->buffer[i];
any_array_push(ar, l->texpaint_preview);
}
return ar;
}
if (string_equals(node_type, "MATERIAL")) {
any_array_t *ar = any_array_create(0);
for (i32 i = 0; i < project_materials->length; ++i) {
slot_material_t *m = project_materials->buffer[i];
any_array_push(ar, m->image_icon);
}
return ar;
}
if (string_equals(node_type, "image_texture_node")) {
any_array_t *ar = any_array_create(0);
for (i32 i = 0; i < project_assets->length; ++i) {
asset_t *asset = project_assets->buffer[i];
gpu_texture_t *img = project_get_image(asset);
any_array_push(ar, img);
}
return ar;
}
return NULL;
}
i32 base_get_asset_index(char *file_name) {
i32 i = string_array_index_of(project_asset_names, file_name);
return i >= 0 ? i : 0;
}
void base_toggle_fullscreen() {
if (iron_window_get_mode() == IRON_WINDOW_MODE_WINDOW) {
g_config->window_w = iron_window_width();
g_config->window_h = iron_window_height();
g_config->window_x = iron_window_x();
g_config->window_y = iron_window_y();
iron_window_change_mode(IRON_WINDOW_MODE_FULLSCREEN);
}
else {
iron_window_change_mode(IRON_WINDOW_MODE_WINDOW);
iron_window_resize(g_config->window_w, g_config->window_h);
iron_window_move(g_config->window_x, g_config->window_y);
}
g_context->ddirty = 3;
}
bool base_is_decal_layer() {
bool is_painting = g_context->tool != TOOL_TYPE_MATERIAL && g_context->tool != TOOL_TYPE_BAKE;
return is_painting && g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT;
}
void base_redraw_status() {
ui_base_hwnds->buffer[TAB_AREA_STATUS]->redraws = 2;
}
void base_redraw_console() {
ui_base_hwnds->buffer[TAB_AREA_STATUS]->redraws = 2;
}
ui_handle_t_array_t *ui_base_init_hwnds() {
ui_handle_t_array_t *hwnds = any_array_create_from_raw(
(void *[]){
ui_handle_create(),
ui_handle_create(),
ui_handle_create(),
},
3);
return hwnds;
}
ui_handle_t_array_t *ui_base_init_htabs() {
ui_handle_t_array_t *htabs = any_array_create_from_raw(
(void *[]){
ui_handle_create(),
ui_handle_create(),
ui_handle_create(),
},
3);
return htabs;
}
tab_draw_t *_draw_callback_create(void (*f)(ui_handle_t *)) {
tab_draw_t *cb = GC_ALLOC_INIT(tab_draw_t, {.f = f});
return cb;
}
tab_draw_array_t_array_t *ui_base_init_hwnd_tabs() {
tab_draw_array_t *a0 = any_array_create_from_raw(
(void *[]){
_draw_callback_create(tab_layers_draw),
_draw_callback_create(tab_history_draw),
_draw_callback_create(tab_scripts_draw),
},
3);
tab_draw_array_t *a1 = any_array_create_from_raw(
(void *[]){
_draw_callback_create(tab_materials_draw),
_draw_callback_create(tab_brushes_draw),
_draw_callback_create(tab_plugins_draw),
},
3);
tab_draw_array_t *a2 = any_array_create_from_raw(
(void *[]){
_draw_callback_create(tab_browser_draw),
_draw_callback_create(tab_meshes_draw),
_draw_callback_create(tab_textures_draw),
_draw_callback_create(tab_fonts_draw),
_draw_callback_create(tab_swatches_draw),
_draw_callback_create(tab_console_draw),
_draw_callback_create(ui_statusbar_draw_version_tab),
},
7);
#ifdef IRON_IOS
if (config_is_iphone()) {
array_splice(a2, 4, 1); // Swatches
}
#endif
#ifdef is_debug
any_array_push(a0, _draw_callback_create(tab_debug_draw));
#endif
tab_draw_array_t_array_t *r = any_array_create_from_raw((void *[]){}, 0);
any_array_push(r, a0);
any_array_push(r, a1);
any_array_push(r, a2);
return r;
}
void ui_base_toggle_distract_free() {
if (base_player_lock) {
return;
}
ui_base_show = !ui_base_show;
if (ui_base_show) {
g_config->workspace = WORKSPACE_PAINT_3D;
base_update_workspace();
}
base_resize();
}
void 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_MATERIAL;
ui_nodes_canvas_type = CANVAS_TYPE_MATERIAL;
if (g_config->touch_ui && ui_view2d_show && base_view3d_show) {
ui_view2d_show = false;
}
if (g_config->touch_ui && ui_nodes_show && iron_window_width() < iron_window_height()) {
ui_view2d_show = false;
base_view3d_show = false;
}
if (g_config->touch_ui && !ui_nodes_show && iron_window_width() < iron_window_height()) {
base_view3d_show = true;
}
base_resize();
}
void 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_BRUSH;
ui_nodes_canvas_type = CANVAS_TYPE_BRUSH;
if (g_config->touch_ui && ui_view2d_show && base_view3d_show) {
ui_view2d_show = false;
}
if (g_config->touch_ui && ui_nodes_show && iron_window_width() < iron_window_height()) {
ui_view2d_show = false;
base_view3d_show = false;
}
if (g_config->touch_ui && !ui_nodes_show && iron_window_width() < iron_window_height()) {
base_view3d_show = true;
}
base_resize();
}
void ui_base_show_2d_view(view_2d_type_t type) {
// 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 (g_config->touch_ui && ui_nodes_show && base_view3d_show) {
ui_nodes_show = false;
}
if (g_config->touch_ui && ui_view2d_show && iron_window_width() < iron_window_height()) {
ui_nodes_show = false;
base_view3d_show = false;
}
if (g_config->touch_ui && !ui_view2d_show && iron_window_width() < iron_window_height()) {
base_view3d_show = true;
}
base_resize();
}
void ui_base_show_3d_view() {
if (!base_view3d_show) {
if (g_config->touch_ui && ui_nodes_show && ui_view2d_show) {
ui_view2d_show = false;
}
if (g_config->touch_ui && (ui_nodes_show || ui_view2d_show) && iron_window_width() < iron_window_height()) {
ui_nodes_show = false;
ui_view2d_show = false;
}
}
base_view3d_show = !base_view3d_show;
base_resize();
}
void ui_base_toggle_browser() {
bool minimized = g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] <= (ui_statusbar_default_h * g_config->window_scale);
g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] = minimized ? 240 : ui_statusbar_default_h;
g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] = math_floor(g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] * g_config->window_scale);
base_resize();
}
void ui_base_set_icon_scale() {
if (UI_SCALE() > 1) {
string_array_t *res = any_array_create_from_raw(
(void *[]){
"icons.k",
"icons05x.k",
"icons2x.k",
},
3);
resource_load(res);
any_map_set(resource_bundled, "icons05x.k", resource_get("icons.k"));
any_map_set(resource_bundled, "icons.k", resource_get("icons2x.k"));
}
else {
string_array_t *res = any_array_create_from_raw(
(void *[]){
"icons.k",
"icons05x.k",
},
2);
resource_load(res);
}
}
void base_redraw_ui() {
ui_header_handle->redraws = 2;
ui_base_hwnds->buffer[TAB_AREA_STATUS]->redraws = 2;
ui_menubar_menu_handle->redraws = 2;
ui_menubar_hwnd->redraws = 2;
ui_nodes_hwnd->redraws = 2;
ui_box_hwnd->redraws = 2;
ui_view2d_hwnd->redraws = 2;
// Redraw viewport
if (g_context->ddirty < 0) {
g_context->ddirty = 0;
}
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR0]->redraws = 2;
ui_base_hwnds->buffer[TAB_AREA_SIDEBAR1]->redraws = 2;
ui_toolbar_handle->redraws = 2;
if (g_context->split_view) {
g_context->ddirty = 1;
}
}
void ui_base_set_viewport_col(i32 col) {
ui_base_make_empty_envmap(col);
g_context->ddirty = 2;
if (!g_context->show_envmap) {
scene_world->_->envmap = g_context->empty_envmap;
}
}
void base_update_workspace() {
config_init_layout();
if (g_config->workspace == WORKSPACE_PAINT_3D) {
base_view3d_show = true;
ui_menubar_tab->i = 0;
ui_view2d_show = false;
ui_nodes_show = false;
}
else if (g_config->workspace == WORKSPACE_PAINT_2D) {
base_view3d_show = false;
ui_menubar_tab->i = -1;
ui_view2d_show = true;
ui_nodes_show = false;
}
else if (g_config->workspace == WORKSPACE_NODES) {
base_view3d_show = false;
ui_menubar_tab->i = -1;
ui_view2d_show = false;
ui_nodes_show = true;
ui_sidebar_show(false);
}
else if (g_config->workspace == WORKSPACE_SCRIPT) {
base_view3d_show = true;
ui_menubar_tab->i = 0;
ui_view2d_show = false;
ui_nodes_show = false;
ui_base_htabs->buffer[TAB_AREA_STATUS]->i = 5; // Console
g_config->layout_tabs->buffer[TAB_AREA_STATUS] = 5;
ui_base_htabs->buffer[TAB_AREA_SIDEBAR0]->i = 2; // Script
g_config->layout_tabs->buffer[TAB_AREA_SIDEBAR0] = 2;
g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] = iron_window_height() * 0.3;
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] = iron_window_width() * 0.52;
float h = UI_ELEMENT_H() + UI_ELEMENT_OFFSET() + 2;
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H0] = iron_window_height() - h;
g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_H1] = h;
render_path_resize();
}
else if (g_config->workspace == WORKSPACE_PLAYER) {
ui_base_show = false;
render_path_resize();
}
base_resize();
}
void base_update_workflow() {
// Update Material Output nodes
for (i32 i = 0; i < project_materials->length; ++i) {
ui_node_array_t *nodes = project_materials->buffer[i]->canvas->nodes;
for (i32 j = 0; j < nodes->length; ++j) {
if (string_equals(nodes->buffer[j]->type, "OUTPUT_MATERIAL_PBR")) {
nodes->buffer[j]->inputs->length = g_config->workflow == WORKFLOW_PBR ? 9 : 2;
nodes->buffer[j]->inputs->buffer[0]->name = g_config->workflow == WORKFLOW_SCULPT ? tr("Displacement") : tr("Base Color");
}
}
}
}
void base_run_in_player() {
if (string_equals(project_filepath, "")) {
console_error(tr("Save project first"));
return;
}
export_arm_run_project();
char *bin = iron_get_arg(0);
iron_sys_command(string("%s %s --player", bin, project_filepath));
// iron_exec_async()
}
uint32_t base_darker(uint32_t x, uint32_t y) {
uint32_t r = ((x >> 16) & 0xff);
uint32_t g = ((x >> 8) & 0xff);
uint32_t b = ((x) & 0xff);
uint32_t ry = ((y >> 16) & 0xff);
uint32_t gy = ((y >> 8) & 0xff);
uint32_t by = ((y) & 0xff);
r = r > ry ? r - ry : 0;
g = g > gy ? g - gy : 0;
b = b > by ? b - by : 0;
return (x & 0xff000000) | (r << 16) | (g << 8) | b;
}
string_array_t *base_get_viewport_modes() {
string_array_t *modes = any_array_create_from_raw(
(void *[]){
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"),
},
15);
if (gpu_raytrace_supported()) {
any_array_push(modes, tr("Path Traced"));
}
return modes;
}
string_array_t *base_get_viewport_mode_shortcuts() {
string_array_t *shortcuts = any_array_create_from_raw(
(void *[]){
"l",
"b",
"n",
"o",
"r",
"m",
"a",
"h",
"e",
"s",
"t",
"1",
"2",
"3",
"4",
},
15);
if (gpu_raytrace_supported()) {
any_array_push(shortcuts, "p");
}
return shortcuts;
}