Files
armorpaint/paint/sources/ui/ui_menubar.c
T
2026-06-11 20:01:25 +02:00

984 lines
32 KiB
C

#include "../global.h"
typedef enum {
MENUBAR_CATEGORY_FILE = 0,
MENUBAR_CATEGORY_EDIT = 1,
MENUBAR_CATEGORY_VIEWPORT = 2,
MENUBAR_CATEGORY_MODE = 3,
MENUBAR_CATEGORY_CAMERA = 4,
MENUBAR_CATEGORY_WORKSPACE = 5,
MENUBAR_CATEGORY_HELP = 6,
} menubar_category_t;
typedef struct update_info {
i32 version;
char *version_name;
} update_info_t;
i32 ui_menubar_category = 0;
int ui_menubar_capture_screenshot_frame = 0;
void ui_menubar_init() {
ui_menubar_hwnd->layout = UI_LAYOUT_HORIZONTAL;
ui_menubar_menu_handle->layout = UI_LAYOUT_HORIZONTAL;
}
i32 ui_menu_panel_x() {
if (!base_view3d_show && ui_view2d_show) {
return 0;
}
i32 panel_x = base_x();
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 1) {
i32 item_w = ui_toolbar_w(false);
panel_x = base_x() - item_w;
}
else {
panel_x += 5 * UI_SCALE();
}
return panel_x;
}
i32 ui_menu_top_y() {
#ifdef IRON_IOS
if (config_is_iphone()) {
return UI_ELEMENT_H() + UI_ELEMENT_OFFSET();
}
#endif
if (g_config->touch_ui) {
return 0;
}
return UI_ELEMENT_H();
}
i32 ui_menu_panel_y() {
#ifdef IRON_IOS
if (config_is_iphone()) {
return ui_header_h;
}
#endif
i32 panel_y = 0;
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 0) { // Floating
panel_y += 5 * UI_SCALE();
}
return panel_y;
}
void ui_menubar_render_ui_save_project_on_next_frame(void *_) {
project_save(false);
box_projects_show();
}
void ui_menubar_render_ui_save_project(void *_) {
sys_notify_on_end_frame(&ui_menubar_render_ui_save_project_on_next_frame, NULL);
}
void ui_menubar_draw_tab_header() {
i32 item_w = ui_toolbar_w(false);
i32 nodesw = (ui_nodes_show || ui_view2d_show) ? g_config->layout->buffer[LAYOUT_SIZE_NODES_W] : 0;
i32 ww = iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - ui_menubar_w - nodesw;
i32 panel_x = (base_x() - item_w) + ui_menubar_w;
if (!base_view3d_show) {
panel_x += 1;
}
if (!base_view3d_show && ui_view2d_show) {
panel_x = ui_menubar_w;
ww -= 100 * UI_SCALE();
}
if (ui_window(ui_menubar_hwnd, panel_x, 0, ww, ui_header_h, false)) {
if (g_config->touch_ui) {
ui_fill(0, 0, g_ui->_window_w, g_ui->_window_h + 4, g_theme->SEPARATOR_COL);
}
else {
bool a = ui_tab(ui_menubar_tab, tr("3D View"), false, -1, false);
bool b = ui_tab(ui_menubar_tab, base_view3d_show ? ">" : "<", false, -2, false);
if ((a && !base_view3d_show) || b) {
base_view3d_show = !base_view3d_show;
ui_menubar_tab->i = base_view3d_show ? 0 : -1;
base_resize();
}
}
}
}
void ui_menubar_draw_category_items_about_box() {
gpu_texture_t *img = data_get_image("badge.k");
ui_image(img, 0xffffffff, -1.0);
ui_end_element();
ui_handle_t *h = ui_handle(__ID__);
if (h->init) {
h->text = string_copy(_ui_menu_render_msg);
}
ui_text_area(h, UI_ALIGN_LEFT, false, "", false);
ui_row3();
#if defined(IRON_WINDOWS) || defined(IRON_LINUX) || defined(IRON_MACOS)
if (ui_icon_button(tr("Copy"), ICON_COPY, UI_ALIGN_CENTER)) {
iron_copy_to_clipboard(_ui_menu_render_msg);
}
#else
ui_end_element();
#endif
if (ui_icon_button(tr("Contributors"), ICON_LINK, UI_ALIGN_CENTER)) {
iron_load_url("https://github.com/armory3d/armorpaint/graphs/contributors");
}
if (ui_icon_button(tr("OK"), ICON_CHECK, UI_ALIGN_CENTER)) {
ui_box_hide();
}
}
void ui_menubar_draw_category_items_on_check_for_updates_downloaded(char *url, buffer_t *buffer) {
if (buffer != NULL) {
// Compare versions
update_info_t *update = json_parse(sys_buffer_to_string(buffer));
i32 update_version = math_floor(update->version);
if (update_version > 0) {
char *date = config_get_date(); // 2019 -> 19
date = string_copy(substring(date, 2, string_length(date)));
i32 date_int = parse_int(string_replace_all(date, "-", ""));
if (update_version > date_int) {
any_map_t *vars = any_map_create();
any_map_set(vars, "url", manifest_url);
ui_box_show_message(tr("Update"), vtr("Update is available!\nPlease visit {url}.", vars), false);
}
else {
ui_box_show_message(tr("Update"), tr("You are up to date!"), false);
}
}
}
else {
any_map_t *vars = any_map_create();
any_map_set(vars, "url", manifest_url);
ui_box_show_message(tr("Update"), vtr("Unable to check for updates.\nPlease visit {url}.", vars), false);
}
}
void ui_menubar_capture_screenshot(void *_) {
ui_menubar_capture_screenshot_frame++;
if (ui_menubar_capture_screenshot_frame == 2) {
viewport_capture_screenshot();
sys_remove_update(ui_menubar_capture_screenshot);
}
}
void ui_menubar_import_envmap(char *path) {
if (!ends_with(path, ".hdr")) {
console_error(tr("Error: .hdr file expected"));
return;
}
import_asset_run(path, -1.0, -1.0, true, true, NULL);
}
viewport_mode_t ui_menubar_string_to_viewport_mode(char *s) {
if (string_equals(s, tr("Lit")))
return VIEWPORT_MODE_LIT;
if (string_equals(s, tr("Base Color")))
return VIEWPORT_MODE_BASE_COLOR;
if (string_equals(s, tr("Normal")))
return VIEWPORT_MODE_NORMAL_MAP;
if (string_equals(s, tr("Occlusion")))
return VIEWPORT_MODE_OCCLUSION;
if (string_equals(s, tr("Roughness")))
return VIEWPORT_MODE_ROUGHNESS;
if (string_equals(s, tr("Metallic")))
return VIEWPORT_MODE_METALLIC;
if (string_equals(s, tr("Opacity")))
return VIEWPORT_MODE_OPACITY;
if (string_equals(s, tr("Height")))
return VIEWPORT_MODE_HEIGHT;
if (string_equals(s, tr("Emission")))
return VIEWPORT_MODE_EMISSION;
if (string_equals(s, tr("Subsurface")))
return VIEWPORT_MODE_SUBSURFACE;
if (string_equals(s, tr("TexCoord")))
return VIEWPORT_MODE_TEXCOORD;
if (string_equals(s, tr("Object Normal")))
return VIEWPORT_MODE_OBJECT_NORMAL;
if (string_equals(s, tr("Material ID")))
return VIEWPORT_MODE_MATERIAL_ID;
if (string_equals(s, tr("Object ID")))
return VIEWPORT_MODE_OBJECT_ID;
if (string_equals(s, tr("Mask")))
return VIEWPORT_MODE_MASK;
if (string_equals(s, tr("Path Traced")))
return VIEWPORT_MODE_PATH_TRACE;
return VIEWPORT_MODE_LIT;
}
char *ui_menubar_viewport_mode_to_string(viewport_mode_t m) {
if (m == VIEWPORT_MODE_LIT)
return "Lit";
if (m == VIEWPORT_MODE_BASE_COLOR)
return "Base Color";
if (m == VIEWPORT_MODE_NORMAL_MAP)
return "Normal";
if (m == VIEWPORT_MODE_OCCLUSION)
return "Occlusion";
if (m == VIEWPORT_MODE_ROUGHNESS)
return "Roughness";
if (m == VIEWPORT_MODE_METALLIC)
return "Metallic";
if (m == VIEWPORT_MODE_OPACITY)
return "Opacity";
if (m == VIEWPORT_MODE_HEIGHT)
return "Height";
if (m == VIEWPORT_MODE_EMISSION)
return "Emission";
if (m == VIEWPORT_MODE_SUBSURFACE)
return "Subsurface";
if (m == VIEWPORT_MODE_TEXCOORD)
return "TexCoord";
if (m == VIEWPORT_MODE_OBJECT_NORMAL)
return "Object Normal";
if (m == VIEWPORT_MODE_MATERIAL_ID)
return "Material ID";
if (m == VIEWPORT_MODE_OBJECT_ID)
return "Object ID";
if (m == VIEWPORT_MODE_MASK)
return "Mask";
if (m == VIEWPORT_MODE_PATH_TRACE)
return "Path Traced";
return "Lit";
}
void ui_menubar_draw_category_items() {
if (ui_menubar_category == MENUBAR_CATEGORY_FILE) {
if (ui_menu_button(tr("New Project..."), any_map_get(g_keymap, "file_new"), ICON_FILE_NEW)) {
project_new_box();
}
if (ui_menu_button(tr("Open..."), any_map_get(g_keymap, "file_open"), ICON_FOLDER_OPEN)) {
project_open();
}
if (ui_menu_button(tr("Open Recent..."), any_map_get(g_keymap, "file_open_recent"), ICON_REPLAY)) {
box_projects_show();
}
if (ui_menu_button(tr("Save"), any_map_get(g_keymap, "file_save"), ICON_SAVE)) {
project_save(false);
}
if (ui_menu_button(tr("Save As..."), any_map_get(g_keymap, "file_save_as"), ICON_SAVE_AS)) {
project_save_as(false);
}
g_ui->changed = false;
ui_handle_t *h_pack_assets = ui_handle(__ID__);
h_pack_assets->b = g_context->pack_assets_on_save;
g_context->pack_assets_on_save = ui_check(h_pack_assets, tr("Pack Assets"), "");
if (g_ui->changed) {
ui_menu_keep_open = true;
}
ui_menu_separator();
if (ui_menu_sub_button(ui_handle(__ID__), tr("Import"))) {
ui_menu_sub_begin(7);
if (ui_menu_button(tr("Texture..."), any_map_get(g_keymap, "file_import_assets"), ICON_IMAGE)) {
project_import_asset(string_array_join(path_texture_formats(), ","), false);
}
if (ui_menu_button(tr("Envmap..."), "", ICON_LANDSCAPE)) {
ui_files_show("hdr", false, false, &ui_menubar_import_envmap);
}
if (ui_menu_button(tr("Font..."), "", ICON_FONT)) {
project_import_asset("ttf,ttc,otf", true);
}
if (ui_menu_button(tr("Material..."), "", ICON_SPHERE)) {
project_import_material();
}
if (ui_menu_button(tr("Brush..."), "", ICON_PAINT)) {
project_import_brush();
}
if (ui_menu_button(tr("Swatches..."), "", ICON_PALETTE)) {
project_import_swatches(false);
}
if (ui_menu_button(tr("Mesh..."), "", ICON_CUBE)) {
project_import_mesh(true, NULL);
}
ui_menu_sub_end();
}
if (ui_menu_button(tr("Reimport Mesh"), any_map_get(g_keymap, "file_reimport_mesh"), ICON_SYNC)) {
project_reimport_mesh();
}
if (ui_menu_button(tr("Reimport Textures"), any_map_get(g_keymap, "file_reimport_textures"), ICON_SYNC)) {
project_reimport_textures();
}
ui_menu_separator();
if (ui_menu_sub_button(ui_handle(__ID__), tr("Export"))) {
ui_menu_sub_begin(g_config->experimental ? 4 : 3);
if (ui_menu_button(tr("Textures..."), any_map_get(g_keymap, "file_export_textures_as"), ICON_IMAGE)) {
g_context->layers_export = EXPORT_MODE_VISIBLE;
box_export_show_textures();
}
if (ui_menu_button(tr("Swatches..."), "", ICON_PALETTE)) {
project_export_swatches();
}
if (ui_menu_button(tr("Mesh..."), "", ICON_CUBE)) {
g_context->export_mesh_index = 0; // All
box_export_show_mesh();
}
if (g_config->experimental && ui_menu_button(tr("Player..."), "", ICON_PLAY)) {
box_export_show_player();
}
ui_menu_sub_end();
}
if (ui_menu_button(tr("Bake Material..."), "", ICON_BAKE)) {
box_export_show_bake_material();
}
ui_menu_separator();
if (ui_menu_button(tr("Exit"), "", ICON_EXIT)) {
iron_stop();
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_EDIT) {
char *step_undo = "";
char *step_redo = "";
if (history_undos > 0) {
step_undo = string_copy(history_steps->buffer[history_steps->length - 1 - history_redos]->name);
}
if (history_redos > 0) {
step_redo = string_copy(history_steps->buffer[history_steps->length - history_redos]->name);
}
g_ui->enabled = history_undos > 0;
any_map_t *vars_undo = any_map_create();
any_map_set(vars_undo, "step", step_undo);
if (ui_menu_button(vtr("Undo {step}", vars_undo), any_map_get(g_keymap, "edit_undo"), ICON_UNDO)) {
history_undo();
ui_menu_keep_open = true;
}
g_ui->enabled = history_redos > 0;
any_map_t *vars_redo = any_map_create();
any_map_set(vars_redo, "step", step_redo);
if (ui_menu_button(vtr("Redo {step}", vars_redo), any_map_get(g_keymap, "edit_redo"), ICON_REDO)) {
history_redo();
ui_menu_keep_open = true;
}
g_ui->enabled = true;
ui_menu_separator();
// History steps
i32 history_max = g_config->undo_steps + 1;
ui_handle_t *history_handle = ui_handle(__ID__);
history_handle->i = history_steps->length - 1 - history_redos;
for (i32 i = 0; i < history_max; ++i) {
if (i < history_steps->length) {
ui_radio(history_handle, i, history_steps->buffer[i]->name, "");
}
else {
g_ui->enabled = false;
ui_radio(history_handle, i, tr("History"), "");
g_ui->enabled = true;
}
}
if (history_handle->changed) {
i32 active = history_steps->length - 1 - history_redos;
i32 diff = history_handle->i - active;
while (diff > 0) {
diff--;
history_redo();
}
while (diff < 0) {
diff++;
history_undo();
}
ui_menu_keep_open = true;
}
ui_menu_separator();
if (ui_menu_button(tr("Preferences..."), any_map_get(g_keymap, "edit_prefs"), ICON_COG)) {
box_preferences_show();
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_VIEWPORT) {
if (ui_menu_button(tr("Distract Free"), any_map_get(g_keymap, "view_distract_free"), ICON_NONE)) {
ui_base_toggle_distract_free();
g_ui->is_hovered = false;
}
#if defined(IRON_WINDOWS) || defined(IRON_LINUX) || defined(IRON_MACOS) || defined(IRON_WASM)
if (ui_menu_button(tr("Toggle Fullscreen"), "alt+enter", ICON_FULLSCREEN)) {
base_toggle_fullscreen();
}
#endif
g_ui->changed = false;
world_data_t *p = scene_world;
ui_handle_t *env_handle = ui_handle(__ID__);
env_handle->f = p->strength;
ui_menu_align();
p->strength = ui_slider(env_handle, tr("Environment"), 0.0, 6.0, true, 100.0, true, UI_ALIGN_RIGHT, true);
if (env_handle->changed) {
g_context->ddirty = 2;
}
ui_handle_t *enva_handle = ui_handle(__ID__);
enva_handle->f = g_context->envmap_angle / (float)math_pi() * 180.0;
if (enva_handle->f < 0) {
enva_handle->f += (math_floor(-enva_handle->f / 360.0) + 1) * 360;
}
else if (enva_handle->f > 360) {
enva_handle->f -= math_floor(enva_handle->f / 360.0) * 360;
}
ui_menu_align();
g_context->envmap_angle = ui_slider(enva_handle, tr("Environment Angle"), 0.0, 360.0, true, 1, true, UI_ALIGN_RIGHT, true) / 180.0 * math_pi();
if (g_ui->is_hovered) {
any_map_t *vars = any_map_create();
any_map_set(vars, "shortcut", any_map_get(g_keymap, "rotate_envmap"));
ui_tooltip(vtr("{shortcut} and move mouse", vars));
}
if (enva_handle->changed) {
g_context->ddirty = 2;
}
ui_handle_t *split_view_handle = ui_handle(__ID__);
split_view_handle->b = g_context->split_view;
g_context->split_view = ui_check(split_view_handle, string(" %s", tr("Split View")), "");
if (split_view_handle->changed) {
base_resize();
}
ui_handle_t *cull_handle = ui_handle(__ID__);
cull_handle->b = g_context->cull_backfaces;
g_context->cull_backfaces = ui_check(cull_handle, string(" %s", tr("Cull Backfaces")), "");
if (cull_handle->changed) {
make_material_parse_mesh_material();
}
ui_handle_t *wireframe_handle = ui_handle(__ID__);
wireframe_handle->b = g_context->draw_wireframe;
g_context->draw_wireframe = ui_check(wireframe_handle, string(" %s", tr("Wireframe")), "");
if (wireframe_handle->changed) {
gpu_texture_t *current = _draw_current;
draw_end();
util_uv_cache_uv_map();
draw_begin(current, false, 0);
make_material_parse_mesh_material();
}
ui_handle_t *texels_handle = ui_handle(__ID__);
texels_handle->b = g_context->draw_texels;
g_context->draw_texels = ui_check(texels_handle, string(" %s", tr("Texels")), "");
if (texels_handle->changed) {
make_material_parse_mesh_material();
}
ui_handle_t *compass_handle = ui_handle(__ID__);
compass_handle->b = g_context->show_compass;
g_context->show_compass = ui_check(compass_handle, string(" %s", tr("Compass")), "");
if (compass_handle->changed) {
g_context->ddirty = 2;
}
ui_handle_t *show_envmap_handle = ui_handle(__ID__);
show_envmap_handle->b = g_context->show_envmap;
g_context->show_envmap = ui_check(show_envmap_handle, string(" %s", tr("Envmap")), "");
if (show_envmap_handle->changed) {
context_load_envmap();
g_context->ddirty = 2;
}
ui_handle_t *show_envmap_blur_handle = ui_handle(__ID__);
show_envmap_blur_handle->b = g_context->show_envmap_blur;
g_context->show_envmap_blur = ui_check(show_envmap_blur_handle, string(" %s", tr("Blur Envmap")), "");
if (show_envmap_blur_handle->changed) {
g_context->ddirty = 2;
}
ui_handle_t *show_envmap_spheres_handle = ui_handle(__ID__);
show_envmap_spheres_handle->b = g_context->show_envmap_spheres;
g_context->show_envmap_spheres = ui_check(show_envmap_spheres_handle, string(" %s", tr("Envmap Spheres")), "");
if (show_envmap_spheres_handle->changed) {
g_context->ddirty = 2;
}
if (ui_menu_button(tr("Reset Envmap"), "", ICON_NONE)) {
project_set_default_envmap();
}
if (ui_menu_button(tr("Capture Screenshot"), "", ICON_PHOTO)) {
g_context->capturing_screenshot = true;
ui_menubar_capture_screenshot_frame = 0;
sys_notify_on_update(&ui_menubar_capture_screenshot, NULL);
g_ui->changed = false; // Close menu
}
// if (g_config->experimental) {
// ui_handle_t *h = ui_handle(__ID__);
// h->b = g_context->capture_background;
// g_context->capture_background = ui_check(h, tr("Capture Background"), "");
// }
if (g_config->experimental && !viewport_recording && ui_menu_button(tr("Capture Video"), "", ICON_MOVIE)) {
viewport_capture_video_begin();
g_ui->changed = false; // Close menu
}
if (g_config->experimental && viewport_recording && ui_menu_button(tr("Stop Capture"), "", ICON_STOP)) {
viewport_capture_video_end();
g_ui->changed = false; // Close menu
}
if (g_config->experimental && ui_menu_button(tr("Run in Player"), "f5", ICON_PLAY)) {
base_run_in_player();
g_ui->changed = false; // Close menu
}
context_update_envmap();
if (g_ui->changed) {
ui_menu_keep_open = true;
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_MODE) {
ui_handle_t *mode_handle = ui_handle(__ID__);
mode_handle->i = g_context->viewport_mode;
string_array_t *modes = base_get_viewport_modes();
string_array_t *shortcuts = base_get_viewport_mode_shortcuts();
if (g_config->workflow == WORKFLOW_BASE) {
array_splice(modes, 9, 1);
array_splice(modes, 8, 1);
array_splice(modes, 7, 1);
array_splice(modes, 5, 1);
array_splice(modes, 4, 1);
array_splice(modes, 3, 1);
array_splice(modes, 2, 1);
array_splice(shortcuts, 9, 1);
array_splice(shortcuts, 8, 1);
array_splice(shortcuts, 7, 1);
array_splice(shortcuts, 5, 1);
array_splice(shortcuts, 4, 1);
array_splice(shortcuts, 3, 1);
array_splice(shortcuts, 2, 1);
mode_handle->i = string_array_index_of(modes, ui_menubar_viewport_mode_to_string(mode_handle->i));
}
for (i32 i = 0; i < modes->length; ++i) {
char *shortcut = g_config->touch_ui ? "" : string("%s, %s", any_map_get(g_keymap, "viewport_mode"), shortcuts->buffer[i]);
ui_radio(mode_handle, i, modes->buffer[i], shortcut);
}
if (mode_handle->changed) {
viewport_mode_t m = ui_menubar_string_to_viewport_mode(modes->buffer[mode_handle->i]);
context_set_viewport_mode(m);
ui_menu_keep_open = true;
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_CAMERA) {
if (ui_menu_button(tr("Reset"), any_map_get(g_keymap, "view_reset"), ICON_NONE)) {
viewport_reset();
viewport_scale_to_bounds(2.0);
}
ui_menu_separator();
bool full = true;
#ifdef IRON_IOS
if (config_is_iphone()) {
full = false;
}
#endif
if (full) {
if (ui_menu_sub_button(ui_handle(__ID__), tr("View"))) {
ui_menu_sub_begin(6);
if (ui_menu_button(tr("Front"), any_map_get(g_keymap, "view_front"), ICON_NONE)) {
viewport_set_view(0, -1, 0, math_pi() / 2.0, 0, 0);
}
if (ui_menu_button(tr("Back"), any_map_get(g_keymap, "view_back"), ICON_NONE)) {
viewport_set_view(0, 1, 0, math_pi() / 2.0, 0, math_pi());
}
if (ui_menu_button(tr("Right"), any_map_get(g_keymap, "view_right"), ICON_NONE)) {
viewport_set_view(1, 0, 0, math_pi() / 2.0, 0, math_pi() / 2.0);
}
if (ui_menu_button(tr("Left"), any_map_get(g_keymap, "view_left"), ICON_NONE)) {
viewport_set_view(-1, 0, 0, math_pi() / 2.0, 0, -math_pi() / 2.0);
}
if (ui_menu_button(tr("Top"), any_map_get(g_keymap, "view_top"), ICON_NONE)) {
viewport_set_view(0, 0, 1, 0, 0, 0);
}
if (ui_menu_button(tr("Bottom"), any_map_get(g_keymap, "view_bottom"), ICON_NONE)) {
viewport_set_view(0, 0, -1, math_pi(), 0, math_pi());
}
ui_menu_sub_end();
}
g_ui->changed = false;
if (ui_menu_sub_button(ui_handle(__ID__), tr("Orbit"))) {
ui_menu_sub_begin(5);
if (ui_menu_button(tr("Left"), any_map_get(g_keymap, "view_orbit_left"), ICON_ARROW_LEFT)) {
viewport_orbit(-math_pi() / 12.0, 0);
}
if (ui_menu_button(tr("Right"), any_map_get(g_keymap, "view_orbit_right"), ICON_ARROW_RIGHT)) {
viewport_orbit(math_pi() / 12.0, 0);
}
if (ui_menu_button(tr("Up"), any_map_get(g_keymap, "view_orbit_up"), ICON_ARROW_UP)) {
viewport_orbit(0, -math_pi() / 12.0);
}
if (ui_menu_button(tr("Down"), any_map_get(g_keymap, "view_orbit_down"), ICON_ARROW_DOWN)) {
viewport_orbit(0, math_pi() / 12.0);
}
if (ui_menu_button(tr("Opposite"), any_map_get(g_keymap, "view_orbit_opposite"), ICON_NONE)) {
viewport_orbit_opposite();
}
ui_menu_sub_end();
}
}
if (ui_menu_button(tr("Zoom In"), any_map_get(g_keymap, "view_zoom_in"), ICON_ZOOM_IN)) {
viewport_zoom(0.2);
}
if (ui_menu_button(tr("Zoom Out"), any_map_get(g_keymap, "view_zoom_out"), ICON_ZOOM_OUT)) {
viewport_zoom(-0.2);
}
ui_menu_align();
ui_handle_t *fov_handle = ui_handle(__ID__);
fov_handle->f = math_floor(scene_camera->data->fov * 100) / 100.0;
scene_camera->data->fov = ui_slider(fov_handle, tr("FoV"), 0.3, 1.4, true, 100.0, true, UI_ALIGN_RIGHT, true);
if (fov_handle->changed) {
viewport_update_camera_type(g_context->camera_type);
}
ui_menu_separator();
ui_menu_align();
ui_menu_label(tr("Pivot"), any_map_get(g_keymap, "view_pivot_center"));
ui_menu_align();
ui_handle_t *camera_pivot_handle = ui_handle(__ID__);
camera_pivot_handle->i = g_context->camera_pivot;
string_array_t *pivot_center_items = any_array_create_from_raw(
(void *[]){
tr("Cursor"),
tr("Center"),
},
2);
g_context->camera_pivot = ui_inline_radio(camera_pivot_handle, pivot_center_items, UI_ALIGN_LEFT);
ui_menu_separator();
ui_menu_align();
ui_menu_label(tr("Mode"), NULL);
ui_menu_align();
ui_handle_t *camera_controls_handle = ui_handle(__ID__);
camera_controls_handle->i = g_context->camera_controls;
string_array_t *camera_controls_items = any_array_create_from_raw(
(void *[]){
tr("Orbit"),
tr("Rotate"),
tr("Fly"),
},
3);
g_context->camera_controls = ui_inline_radio(camera_controls_handle, camera_controls_items, UI_ALIGN_LEFT);
any_map_t *vars = any_map_create();
any_map_set(vars, "rotate_shortcut", any_map_get(g_keymap, "action_rotate"));
any_map_set(vars, "zoom_shortcut", any_map_get(g_keymap, "action_zoom"));
any_map_set(vars, "pan_shortcut", any_map_get(g_keymap, "action_pan"));
char *orbit_and_rotate_tooltip = vtr("Orbit and Rotate mode:\n{rotate_shortcut} or move right mouse button to rotate.\n{zoom_shortcut} or scroll to "
"zoom.\n{pan_shortcut} or move middle mouse to pan.",
vars);
char *fly_tooltip = tr("Fly mode:\nHold the right mouse button and one of the following commands:\nmove mouse to rotate.\nw, up or scroll up to "
"move forward.\ns, down or scroll down to move backward.\na or left to move left.\nd or right to move right.\ne to move "
"up.\nq to move down.\nHold shift to move faster or alt to move slower.");
if (g_ui->is_hovered) {
ui_tooltip(string("%s\n\n%s", orbit_and_rotate_tooltip, fly_tooltip));
}
ui_menu_separator();
ui_menu_align();
ui_menu_label(tr("Type"), any_map_get(g_keymap, "view_camera_type"));
ui_menu_align();
string_array_t *camera_type_items = any_array_create_from_raw(
(void *[]){
tr("Perspective"),
tr("Orthographic"),
},
2);
ui_handle_t *cam_handle = ui_handle(__ID__);
cam_handle->i = g_context->camera_type;
g_context->camera_type = ui_inline_radio(cam_handle, camera_type_items, UI_ALIGN_LEFT);
if (cam_handle->changed) {
viewport_update_camera_type(g_context->camera_type);
}
if (g_ui->changed) {
ui_menu_keep_open = true;
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_HELP) {
if (ui_menu_button(tr("Manual"), "", ICON_HELP)) {
iron_load_url(string("%s/manual", manifest_url));
}
if (ui_menu_button(tr("How To"), "", ICON_HELP)) {
iron_load_url(string("%s/howto", manifest_url));
}
if (ui_menu_button(tr("What's New"), "", ICON_LINK)) {
iron_load_url(string("%s/notes", manifest_url));
}
if (ui_menu_button(tr("Issue Tracker"), "", ICON_LINK)) {
iron_load_url("https://github.com/armory3d/armorpaint/issues");
}
if (ui_menu_button(tr("Report Bug"), "", ICON_LINK)) {
#if defined(IRON_MACOS) || defined(IRON_IOS) // Limited url length
iron_load_url(string("https://github.com/armory3d/armorpaint/issues/new?labels=bug&template=bug_report.md&body=*%s%%20%s-%s,%%20%s", manifest_title,
manifest_version, config_get_sha(), iron_system_id()));
#else
iron_load_url(string("https://github.com/armory3d/armorpaint/issues/new?labels=bug&template=bug_report.md&body=*%s%%20%s-%s,%%20%s*%%0A%%0A**Issue "
"description:**%%0A%%0A**Steps to reproduce:**%%0A%%0A",
manifest_title, manifest_version, config_get_sha(), iron_system_id()));
#endif
}
if (ui_menu_button(tr("Request Feature"), "", ICON_LINK)) {
#if defined(IRON_MACOS) || defined(IRON_IOS) // Limited url length
iron_load_url(
string("https://github.com/armory3d/armorpaint/issues/new?labels=feature%%20request&template=feature_request.md&body=*%s%%20%s-%s,%%20%s",
manifest_title, manifest_version, config_get_sha(), iron_system_id()));
#else
iron_load_url(string("https://github.com/armory3d/armorpaint/issues/"
"new?labels=feature%%20request&template=feature_request.md&body=*%s%%20%s-%s,%%20%s*%%0A%%0A**Feature description:**%%0A%%0A",
manifest_title, manifest_version, config_get_sha(), iron_system_id()));
#endif
}
ui_menu_separator();
if (ui_menu_button(tr("Check for Updates..."), "", ICON_NONE)) {
#ifdef IRON_ANDROID
iron_load_url(manifest_url_android);
#elif defined(IRON_IOS)
iron_load_url(manifest_url_ios);
#else
// Retrieve latest version number
iron_file_download("https://check-for-updates.armory3d.workers.dev", &ui_menubar_draw_category_items_on_check_for_updates_downloaded, 0, NULL);
#endif
}
if (ui_menu_button(tr("About..."), "", ICON_INFO)) {
char *msg = string("%s.org - v%s (%s) - %s\n", manifest_title, manifest_version, config_get_date(), config_get_sha());
msg = string("%s%s - %s", msg, iron_system_id(), strings_graphics_api());
char *gpu = gpu_device_name();
msg = string("%s\n%s", msg, gpu);
gc_unroot(_ui_menu_render_msg);
_ui_menu_render_msg = string_copy(msg);
gc_root(_ui_menu_render_msg);
ui_box_show_custom(&ui_menubar_draw_category_items_about_box, 400, 320, NULL, true, tr("About"));
}
}
else if (ui_menubar_category == MENUBAR_CATEGORY_WORKSPACE) {
ui_handle_t *workspace_handle = ui_handle(__ID__);
workspace_handle->i = g_config->workspace;
string_array_t *modes = any_array_create_from_raw(
(void *[]){
tr("Paint 3D"),
tr("Paint 2D"),
tr("Nodes"),
tr("Script"),
},
4);
if (g_config->experimental) {
any_array_push(modes, tr("Player"));
}
for (i32 i = 0; i < modes->length; ++i) {
ui_radio(workspace_handle, i, modes->buffer[i], "");
}
if (workspace_handle->changed) {
g_config->workspace = workspace_handle->i;
config_save();
base_update_workspace();
}
ui_menu_separator();
ui_menu_align();
ui_menu_label(tr("Workflow"), NULL);
ui_menu_align();
ui_handle_t *workflow_handle = ui_handle(__ID__);
workflow_handle->i = g_config->workflow;
string_array_t *workflow_items = any_array_create_from_raw(
(void *[]){
tr("PBR"),
tr("Base"),
tr("Sculpt"),
},
3);
g_config->workflow = ui_inline_radio(workflow_handle, workflow_items, UI_ALIGN_LEFT);
if (workflow_handle->changed) {
g_config->workflow = workflow_handle->i;
config_save();
base_update_workflow();
}
}
}
void ui_menubar_show_menu(i32 category) {
if (ui_menu_show && ui_menubar_category == category) {
return;
}
ui_menu_show = true;
ui_menu_show_first = true;
gc_unroot(ui_menu_commands);
ui_menu_commands = ui_menubar_draw_category_items;
gc_root(ui_menu_commands);
ui_menubar_category = category;
i32 panel_x = ui_menu_panel_x();
i32 panel_y = ui_menu_panel_y();
ui_menu_x = math_floor(g_ui->_x - g_ui->_w) + panel_x;
ui_menu_y = math_floor(ui_MENUBAR_H(g_ui)) + panel_y + 2;
if (g_config->touch_ui) {
i32 menu_w = math_floor(base_default_element_w * UI_SCALE() * 2.0);
ui_menu_x -= math_floor((menu_w - g_ui->_w) / 2.0) + math_floor(ui_header_h / 2.0);
ui_menu_x += math_floor(2 * UI_SCALE());
ui_menu_y -= math_floor(2 * UI_SCALE());
ui_menu_keep_open = true;
}
}
void ui_menubar_render_ui() {
if (g_config->touch_ui && !base_view3d_show) {
return;
}
i32 item_w = ui_toolbar_w(false);
i32 panel_x = ui_menu_panel_x();
i32 panel_y = ui_menu_panel_y();
if (ui_window(ui_menubar_menu_handle, panel_x, panel_y, ui_menubar_w, ui_header_h, false)) {
if (!base_view3d_show) {
g_ui->_x += 1;
}
ui_begin_menu();
if (g_config->touch_ui) {
g_ui->_w = item_w;
if (ui_menubar_icon_button(ICON_MENU)) {
box_preferences_show();
}
if (ui_menubar_icon_button(ICON_PROJECTS)) {
// Save project icon in lit mode
context_set_viewport_mode(VIEWPORT_MODE_LIT);
console_toast(tr("Saving project"));
sys_notify_on_next_frame(&ui_menubar_render_ui_save_project, NULL);
}
if (ui_menubar_icon_button(ICON_IMPORT)) {
project_import_asset(NULL, true);
}
if (ui_menubar_icon_button(ICON_EXPORT)) {
box_export_show_textures();
}
i32 size = math_floor(g_ui->_w / (float)UI_SCALE());
if (ui_menu_show && ui_menubar_category == MENUBAR_CATEGORY_VIEWPORT) {
ui_fill(0, -6, size, size - 4, g_theme->HIGHLIGHT_COL);
}
if (ui_menubar_icon_button(ICON_IMAGE)) {
ui_menubar_show_menu(MENUBAR_CATEGORY_VIEWPORT);
}
if (ui_menu_show && ui_menubar_category == MENUBAR_CATEGORY_MODE) {
ui_fill(0, -6, size, size - 4, g_theme->HIGHLIGHT_COL);
}
if (ui_menubar_icon_button(ICON_SUN)) {
ui_menubar_show_menu(MENUBAR_CATEGORY_MODE);
}
if (ui_menu_show && ui_menubar_category == MENUBAR_CATEGORY_CAMERA) {
ui_fill(0, -6, size, size - 4, g_theme->HIGHLIGHT_COL);
}
if (ui_menubar_icon_button(ICON_CAMERA)) {
ui_menubar_show_menu(MENUBAR_CATEGORY_CAMERA);
}
// if (ui_menubar_icon_button(ICON_WINDOW)) {
// ui_menubar_show_menu(MENUBAR_CATEGORY_WORKSPACE);
// }
if (ui_menu_show && ui_menubar_category == MENUBAR_CATEGORY_HELP) {
ui_fill(0, -6, size, size - 4, g_theme->HIGHLIGHT_COL);
}
bool full = true;
#ifdef IRON_IOS
if (config_is_iphone()) {
full = false;
}
#endif
if (full && ui_menubar_icon_button(ICON_HELP)) {
ui_menubar_show_menu(MENUBAR_CATEGORY_HELP);
}
g_ui->enabled = history_undos > 0;
if (ui_menubar_icon_button(ICON_UNDO)) {
history_undo();
}
g_ui->enabled = history_redos > 0;
if (full && ui_menubar_icon_button(ICON_REDO)) {
history_redo();
}
g_ui->enabled = true;
}
else {
string_array_t *categories = any_array_create_from_raw(
(void *[]){
tr("File"),
tr("Edit"),
tr("Viewport"),
tr("Mode"),
tr("Camera"),
tr("Workspace"),
tr("Help"),
},
7);
for (i32 i = 0; i < categories->length; ++i) {
if (ui_menubar_button(categories->buffer[i]) || (ui_menu_show && ui_menu_commands == ui_menubar_draw_category_items && g_ui->is_hovered)) {
ui_menubar_show_menu(i);
}
}
}
// Store real menubar w
if (ui_menubar_w < g_ui->_x + 10) {
ui_menubar_w = math_floor(g_ui->_x + 10);
ui_toolbar_handle->redraws = 2;
}
// Crop menubar if sidebar + nodes are overlapping
i32 nodesw = (ui_nodes_show || ui_view2d_show) ? g_config->layout->buffer[LAYOUT_SIZE_NODES_W] : 0;
if (ui_menubar_w > iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - nodesw) {
ui_menubar_w = iron_window_width() - g_config->layout->buffer[LAYOUT_SIZE_SIDEBAR_W] - nodesw;
ui_toolbar_handle->redraws = 2;
}
ui_end_menu();
}
if (g_config->layout->buffer[LAYOUT_SIZE_HEADER] == 1) {
// Non-floating header
ui_menubar_draw_tab_header();
}
}
bool ui_menubar_icon_button(i32 i) {
u32 col = g_theme->WINDOW_BG_COL;
bool light = col > 0xff666666;
i32 icon_accent = light ? 0xff666666 : 0xffaaaaaa;
gpu_texture_t *img = resource_get("icons.k");
rect_t *rect = resource_tile50(img, i);
return ui_sub_image(img, icon_accent, -1.0, rect->x, rect->y, rect->w, rect->h) == UI_STATE_RELEASED;
}