ui_files.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. let ui_files_default_path: string =
  2. ///if krom_windows
  3. "C:\\Users"
  4. ///elseif krom_android
  5. "/storage/emulated/0/Download"
  6. ///elseif krom_darwin
  7. "/Users"
  8. ///else
  9. "/"
  10. ///end
  11. ;
  12. let ui_files_filename: string;
  13. let ui_files_path: string = ui_files_default_path;
  14. let ui_files_last_path: string = "";
  15. let ui_files_last_search: string = "";
  16. let ui_files_files: string[] = null;
  17. let ui_files_icon_map: map_t<string, image_t> = null;
  18. let ui_files_selected: i32 = -1;
  19. let ui_files_show_extensions: bool = false;
  20. let ui_files_offline: bool = false;
  21. function ui_files_show(filters: string, is_save: bool, open_multiple: bool, files_done: (s: string)=>void) {
  22. if (is_save) {
  23. ui_files_path = krom_save_dialog(filters, "");
  24. if (ui_files_path != null) {
  25. while (string_index_of(ui_files_path, path_sep + path_sep) >= 0) {
  26. ui_files_path = string_replace_all(ui_files_path, path_sep + path_sep, path_sep);
  27. }
  28. ui_files_path = string_replace_all(ui_files_path, "\r", "");
  29. ui_files_filename = substring(ui_files_path, string_last_index_of(ui_files_path, path_sep) + 1, ui_files_path.length);
  30. ui_files_path = substring(ui_files_path, 0, string_last_index_of(ui_files_path, path_sep));
  31. files_done(ui_files_path);
  32. }
  33. }
  34. else {
  35. let paths: string[] = krom_open_dialog(filters, "", open_multiple);
  36. if (paths != null) {
  37. for (let i: i32 = 0; i < paths.length; ++i) {
  38. let path: string = paths[i];
  39. while (string_index_of(path, path_sep + path_sep) >= 0) {
  40. path = string_replace_all(path, path_sep + path_sep, path_sep);
  41. }
  42. path = string_replace_all(path, "\r", "");
  43. ui_files_filename = substring(path, string_last_index_of(path, path_sep) + 1, path.length);
  44. files_done(path);
  45. }
  46. }
  47. }
  48. ui_files_release_keys();
  49. }
  50. function ui_files_release_keys() {
  51. // File dialog may prevent firing key up events
  52. keyboard_up_listener(key_code_t.SHIFT);
  53. keyboard_up_listener(key_code_t.CONTROL);
  54. ///if krom_darwin
  55. keyboard_up_listener(key_code_t.META);
  56. ///end
  57. }
  58. let _ui_files_file_browser_handle: zui_handle_t;
  59. let _ui_files_file_browser_f: string;
  60. let _ui_files_file_browser_shandle: string;
  61. function ui_files_file_browser(ui: zui_t, handle: zui_handle_t, folders_only: bool = false, drag_files: bool = false, search: string = "", refresh: bool = false, context_menu: (s: string)=>void = null): string {
  62. let icons: image_t = resource_get("icons.k");
  63. let folder: rect_t = resource_tile50(icons, 2, 1);
  64. let file: rect_t = resource_tile50(icons, 3, 1);
  65. let is_cloud: bool = starts_with(handle.text, "cloud");
  66. if (is_cloud && file_cloud == null) {
  67. file_init_cloud(function () {
  68. ui_base_hwnds[tab_area_t.STATUS].redraws = 3;
  69. });
  70. }
  71. if (is_cloud && file_read_directory("cloud", false).length == 0) {
  72. return handle.text;
  73. }
  74. ///if krom_ios
  75. let document_directory: string = krom_save_dialog("", "");
  76. document_directory = substring(document_directory, 0, document_directory.length - 8); // Strip /"untitled"
  77. ///end
  78. if (handle.text == "") handle.text = ui_files_default_path;
  79. if (handle.text != ui_files_last_path || search != ui_files_last_search || refresh) {
  80. ui_files_files = [];
  81. // Up directory
  82. let i1: i32 = string_index_of(handle.text, path_sep);
  83. let nested: bool = i1 > -1 && handle.text.length - 1 > i1;
  84. ///if krom_windows
  85. // Server addresses like \\server are not nested
  86. nested = nested && !(handle.text.length >= 2 && char_at(handle.text, 0) == path_sep && char_at(handle.text, 1) == path_sep && string_last_index_of(handle.text, path_sep) == 1);
  87. ///end
  88. if (nested) {
  89. array_push(ui_files_files, "..");
  90. }
  91. let dir_path: string = handle.text;
  92. ///if krom_ios
  93. if (!is_cloud) {
  94. dir_path = document_directory + dir_path;
  95. }
  96. ///end
  97. let files_all: string[] = file_read_directory(dir_path, folders_only);
  98. for (let i: i32 = 0; i < files_all.length; ++i) {
  99. let f: string = files_all[i];
  100. if (f == "" || char_at(f, 0) == ".") {
  101. continue; // Skip hidden
  102. }
  103. if (string_index_of(f, ".") > 0 && !path_is_known(f)) {
  104. continue; // Skip unknown extensions
  105. }
  106. if (is_cloud && string_index_of(f, "_icon.") >= 0) {
  107. continue; // Skip thumbnails
  108. }
  109. if (string_index_of(to_lower_case(f), to_lower_case(search)) < 0) {
  110. continue; // Search filter
  111. }
  112. array_push(ui_files_files, f);
  113. }
  114. }
  115. ui_files_last_path = handle.text;
  116. ui_files_last_search = search;
  117. handle.changed = false;
  118. let slotw: i32 = math_floor(70 * zui_SCALE(ui));
  119. let num: i32 = math_floor(ui._w / slotw);
  120. ui._y += 4; // Don't cut off the border around selected materials
  121. // Directory contents
  122. for (let row: i32 = 0; row < math_floor(math_ceil(ui_files_files.length / num)); ++row) {
  123. let ar: f32[] = [];
  124. for (let i: i32 = 0; i < num * 2; ++i) {
  125. array_push(ar, 1 / num);
  126. }
  127. zui_row(ar);
  128. if (row > 0) {
  129. ui._y += zui_ELEMENT_OFFSET(ui) * 14.0;
  130. }
  131. for (let j: i32 = 0; j < num; ++j) {
  132. let i: i32 = j + row * num;
  133. if (i >= ui_files_files.length) {
  134. zui_end_element(slotw);
  135. zui_end_element(slotw);
  136. continue;
  137. }
  138. let f: string = ui_files_files[i];
  139. let _x: f32 = ui._x;
  140. let rect: rect_t = string_index_of(f, ".") > 0 ? file : folder;
  141. let col: i32 = rect == file ? ui.ops.theme.LABEL_COL : ui.ops.theme.LABEL_COL - 0x00202020;
  142. if (ui_files_selected == i) col = ui.ops.theme.HIGHLIGHT_COL;
  143. let off: f32 = ui._w / 2 - 25 * zui_SCALE(ui);
  144. ui._x += off;
  145. let uix: f32 = ui._x;
  146. let uiy: f32 = ui._y;
  147. let state: zui_state_t = zui_state_t.IDLE;
  148. let generic: bool = true;
  149. let icon: image_t = null;
  150. if (is_cloud && f != ".." && !ui_files_offline) {
  151. if (ui_files_icon_map == null) {
  152. ui_files_icon_map = map_create();
  153. }
  154. icon = map_get(ui_files_icon_map, handle.text + path_sep + f);
  155. if (icon == null) {
  156. let files_all: string[] = file_read_directory(handle.text);
  157. let icon_file: string = substring(f, 0, string_last_index_of(f, ".")) + "_icon.jpg";
  158. if (array_index_of(files_all, icon_file) >= 0) {
  159. let empty: image_t = map_get(render_path_render_targets, "empty_black")._image;
  160. map_set(ui_files_icon_map, handle.text + path_sep + f, empty);
  161. _ui_files_file_browser_handle = handle;
  162. _ui_files_file_browser_f = f;
  163. file_cache_cloud(handle.text + path_sep + icon_file, function (abs: string) {
  164. if (abs != null) {
  165. let image: image_t = data_get_image(abs);
  166. app_notify_on_init(function (image: image_t) {
  167. if (base_pipe_copy_rgb == null) {
  168. base_make_pipe_copy_rgb();
  169. }
  170. let icon: image_t = image_create_render_target(image.width, image.height);
  171. if (ends_with(f, ".arm")) { // Used for material sphere alpha cutout
  172. g2_begin(icon);
  173. ///if (is_paint || is_sculpt)
  174. g2_draw_image(project_materials[0].image, 0, 0);
  175. ///end
  176. }
  177. else {
  178. g2_begin(icon);
  179. g2_clear(0xffffffff);
  180. }
  181. g2_set_pipeline(base_pipe_copy_rgb);
  182. g2_draw_image(image, 0, 0);
  183. g2_set_pipeline(null);
  184. g2_end();
  185. map_set(ui_files_icon_map, _ui_files_file_browser_handle.text + path_sep + _ui_files_file_browser_f, icon);
  186. ui_base_hwnds[tab_area_t.STATUS].redraws = 3;
  187. }, image);
  188. }
  189. else {
  190. ui_files_offline = true;
  191. }
  192. });
  193. }
  194. }
  195. if (icon != null) {
  196. let w: i32 = 50;
  197. if (i == ui_files_selected) {
  198. zui_fill(-2, -2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  199. zui_fill(-2, w + 2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  200. zui_fill(-2, 0, 2, w + 4, ui.ops.theme.HIGHLIGHT_COL);
  201. zui_fill(w + 2 , -2, 2, w + 6, ui.ops.theme.HIGHLIGHT_COL);
  202. }
  203. state = zui_image(icon, 0xffffffff, w * zui_SCALE(ui));
  204. if (ui.is_hovered) {
  205. zui_tooltip_image(icon);
  206. zui_tooltip(f);
  207. }
  208. generic = false;
  209. }
  210. }
  211. if (ends_with(f, ".arm") && !is_cloud) {
  212. if (ui_files_icon_map == null) {
  213. ui_files_icon_map = map_create();
  214. }
  215. let key: string = handle.text + path_sep + f;
  216. icon = map_get(ui_files_icon_map, key);
  217. if (map_get(ui_files_icon_map, key) == null) {
  218. let blob_path: string = key;
  219. ///if krom_ios
  220. blob_path = document_directory + blob_path;
  221. // TODO: implement native .arm parsing first
  222. ///else
  223. let buffer: buffer_t = krom_load_blob(blob_path);
  224. let raw: any = armpack_decode(buffer);
  225. if (raw.material_icons != null) {
  226. let bytes_icon: any = raw.material_icons[0];
  227. icon = image_from_bytes(lz4_decode(bytes_icon, 256 * 256 * 4), 256, 256);
  228. }
  229. ///if (is_paint || is_sculpt)
  230. else if (raw.mesh_icons != null) {
  231. let bytes_icon: any = raw.mesh_icons[0];
  232. icon = image_from_bytes(lz4_decode(bytes_icon, 256 * 256 * 4), 256, 256);
  233. }
  234. else if (raw.brush_icons != null) {
  235. let bytes_icon: any = raw.brush_icons[0];
  236. icon = image_from_bytes(lz4_decode(bytes_icon, 256 * 256 * 4), 256, 256);
  237. }
  238. ///end
  239. ///if is_lab
  240. if (raw.mesh_icon != null) {
  241. let bytes_icon: any = raw.mesh_icon;
  242. icon = image_from_bytes(lz4_decode(bytes_icon, 256 * 256 * 4), 256, 256);
  243. }
  244. ///end
  245. map_set(ui_files_icon_map, key, icon);
  246. ///end
  247. }
  248. if (icon != null) {
  249. let w: i32 = 50;
  250. if (i == ui_files_selected) {
  251. zui_fill(-2, -2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  252. zui_fill(-2, w + 2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  253. zui_fill(-2, 0, 2, w + 4, ui.ops.theme.HIGHLIGHT_COL);
  254. zui_fill(w + 2 , -2, 2, w + 6, ui.ops.theme.HIGHLIGHT_COL);
  255. }
  256. state = zui_image(icon, 0xffffffff, w * zui_SCALE(ui));
  257. if (ui.is_hovered) {
  258. zui_tooltip_image(icon);
  259. zui_tooltip(f);
  260. }
  261. generic = false;
  262. }
  263. }
  264. if (path_is_texture(f) && !is_cloud) {
  265. let w: i32 = 50;
  266. if (ui_files_icon_map == null) {
  267. ui_files_icon_map = map_create();
  268. }
  269. let shandle: string = handle.text + path_sep + f;
  270. icon = map_get(ui_files_icon_map, shandle);
  271. if (icon == null) {
  272. let empty: image_t = map_get(render_path_render_targets, "empty_black")._image;
  273. map_set(ui_files_icon_map, shandle, empty);
  274. let image: image_t = data_get_image(shandle);
  275. _ui_files_file_browser_shandle = shandle;
  276. app_notify_on_init(function (image: image_t) {
  277. if (base_pipe_copy_rgb == null) {
  278. base_make_pipe_copy_rgb();
  279. }
  280. let sw: i32 = image.width > image.height ? w : math_floor(1.0 * image.width / image.height * w);
  281. let sh: i32 = image.width > image.height ? math_floor(1.0 * image.height / image.width * w) : w;
  282. let icon: image_t = image_create_render_target(sw, sh);
  283. g2_begin(icon);
  284. g2_clear(0xffffffff);
  285. g2_set_pipeline(base_pipe_copy_rgb);
  286. g2_draw_scaled_image(image, 0, 0, sw, sh);
  287. g2_set_pipeline(null);
  288. g2_end();
  289. map_set(ui_files_icon_map, _ui_files_file_browser_shandle, icon);
  290. ui_base_hwnds[tab_area_t.STATUS].redraws = 3;
  291. data_delete_image(_ui_files_file_browser_shandle); // The big image is not needed anymore
  292. }, image);
  293. }
  294. if (icon != null) {
  295. if (i == ui_files_selected) {
  296. zui_fill(-2, -2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  297. zui_fill(-2, w + 2, w + 4, 2, ui.ops.theme.HIGHLIGHT_COL);
  298. zui_fill(-2, 0, 2, w + 4, ui.ops.theme.HIGHLIGHT_COL);
  299. zui_fill(w + 2 , -2, 2, w + 6, ui.ops.theme.HIGHLIGHT_COL);
  300. }
  301. state = zui_image(icon, 0xffffffff, icon.height * zui_SCALE(ui));
  302. generic = false;
  303. }
  304. }
  305. if (generic) {
  306. state = zui_image(icons, col, 50 * zui_SCALE(ui), rect.x, rect.y, rect.w, rect.h);
  307. }
  308. if (ui.is_hovered && ui.input_released_r && context_menu != null) {
  309. context_menu(handle.text + path_sep + f);
  310. }
  311. if (state == zui_state_t.STARTED) {
  312. if (f != ".." && drag_files) {
  313. base_drag_off_x = -(mouse_x - uix - ui._window_x - 3);
  314. base_drag_off_y = -(mouse_y - uiy - ui._window_y + 1);
  315. base_drag_file = handle.text;
  316. ///if krom_ios
  317. if (!is_cloud) {
  318. base_drag_file = document_directory + base_drag_file;
  319. }
  320. ///end
  321. if (char_at(base_drag_file, base_drag_file.length - 1) != path_sep) {
  322. base_drag_file += path_sep;
  323. }
  324. base_drag_file += f;
  325. base_drag_file_icon = icon;
  326. }
  327. ui_files_selected = i;
  328. if (time_time() - context_raw.select_time < 0.25) {
  329. base_drag_file = null;
  330. base_drag_file_icon = null;
  331. base_is_dragging = false;
  332. handle.changed = ui.changed = true;
  333. if (f == "..") { // Up
  334. handle.text = substring(handle.text, 0, string_last_index_of(handle.text, path_sep));
  335. // Drive root
  336. if (handle.text.length == 2 && char_at(handle.text, 1) == ":") {
  337. handle.text += path_sep;
  338. }
  339. }
  340. else {
  341. if (char_at(handle.text, handle.text.length - 1) != path_sep) {
  342. handle.text += path_sep;
  343. }
  344. handle.text += f;
  345. }
  346. ui_files_selected = -1;
  347. }
  348. context_raw.select_time = time_time();
  349. }
  350. // Label
  351. ui._x = _x;
  352. ui._y += slotw * 0.75;
  353. let label0: string = (ui_files_show_extensions || string_index_of(f, ".") <= 0) ? f : substring(f, 0, string_last_index_of(f, "."));
  354. let label1: string = "";
  355. while (label0.length > 0 && g2_font_width(ui.ops.font, ui.font_size, label0) > ui._w - 6) { // 2 line split
  356. label1 = char_at(label0, label0.length - 1) + label1;
  357. label0 = substring(label0, 0, label0.length - 1);
  358. }
  359. if (label1 != "") {
  360. ui.cur_ratio--;
  361. }
  362. zui_text(label0, zui_align_t.CENTER);
  363. if (ui.is_hovered) {
  364. zui_tooltip(label0 + label1);
  365. }
  366. if (label1 != "") { // Second line
  367. ui._x = _x;
  368. ui._y += g2_font_height(ui.ops.font, ui.font_size);
  369. zui_text(label1, zui_align_t.CENTER);
  370. if (ui.is_hovered) {
  371. zui_tooltip(label0 + label1);
  372. }
  373. ui._y -= g2_font_height(ui.ops.font, ui.font_size);
  374. }
  375. ui._y -= slotw * 0.75;
  376. if (handle.changed) {
  377. break;
  378. }
  379. }
  380. if (handle.changed) {
  381. break;
  382. }
  383. }
  384. ui._y += slotw * 0.8;
  385. return handle.text;
  386. }