editor_resource_picker.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. /*************************************************************************/
  2. /* editor_resource_picker.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. #include "editor_resource_picker.h"
  31. #include "editor/audio_stream_preview.h"
  32. #include "editor/editor_file_dialog.h"
  33. #include "editor/editor_node.h"
  34. #include "editor/editor_quick_open.h"
  35. #include "editor/editor_resource_preview.h"
  36. #include "editor/editor_scale.h"
  37. #include "editor/editor_settings.h"
  38. #include "editor/filesystem_dock.h"
  39. #include "editor/plugins/editor_resource_conversion_plugin.h"
  40. #include "editor/plugins/script_editor_plugin.h"
  41. #include "editor/scene_tree_dock.h"
  42. HashMap<StringName, List<StringName>> EditorResourcePicker::allowed_types_cache;
  43. void EditorResourcePicker::clear_caches() {
  44. allowed_types_cache.clear();
  45. }
  46. void EditorResourcePicker::_update_resource() {
  47. String resource_path;
  48. if (edited_resource.is_valid() && edited_resource->get_path().is_resource_file()) {
  49. resource_path = edited_resource->get_path() + "\n";
  50. }
  51. if (preview_rect) {
  52. preview_rect->set_texture(Ref<Texture2D>());
  53. assign_button->set_custom_minimum_size(assign_button_min_size);
  54. if (edited_resource == Ref<Resource>()) {
  55. assign_button->set_icon(Ref<Texture2D>());
  56. assign_button->set_text(TTR("<empty>"));
  57. assign_button->set_tooltip_text("");
  58. } else {
  59. assign_button->set_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->(), "Object"));
  60. if (!edited_resource->get_name().is_empty()) {
  61. assign_button->set_text(edited_resource->get_name());
  62. } else if (edited_resource->get_path().is_resource_file()) {
  63. assign_button->set_text(edited_resource->get_path().get_file());
  64. } else {
  65. assign_button->set_text(edited_resource->get_class());
  66. }
  67. assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + edited_resource->get_class());
  68. // Preview will override the above, so called at the end.
  69. EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, this, "_update_resource_preview", edited_resource->get_instance_id());
  70. }
  71. } else if (edited_resource.is_valid()) {
  72. assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + edited_resource->get_class());
  73. }
  74. assign_button->set_disabled(!editable && !edited_resource.is_valid());
  75. }
  76. void EditorResourcePicker::_update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj) {
  77. if (!edited_resource.is_valid() || edited_resource->get_instance_id() != p_obj) {
  78. return;
  79. }
  80. if (preview_rect) {
  81. Ref<Script> script = edited_resource;
  82. if (script.is_valid()) {
  83. assign_button->set_text(script->get_path().get_file());
  84. return;
  85. }
  86. if (p_preview.is_valid()) {
  87. preview_rect->set_offset(SIDE_LEFT, assign_button->get_icon()->get_width() + assign_button->get_theme_stylebox(SNAME("normal"))->get_default_margin(SIDE_LEFT) + get_theme_constant(SNAME("h_separation"), SNAME("Button")));
  88. // Resource-specific stretching.
  89. if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) {
  90. preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE);
  91. assign_button->set_custom_minimum_size(assign_button_min_size);
  92. } else {
  93. preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
  94. int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size");
  95. thumbnail_size *= EDSCALE;
  96. assign_button->set_custom_minimum_size(Size2(MAX(1, assign_button_min_size.x), MAX(thumbnail_size, assign_button_min_size.y)));
  97. }
  98. preview_rect->set_texture(p_preview);
  99. assign_button->set_text("");
  100. }
  101. }
  102. }
  103. void EditorResourcePicker::_resource_selected() {
  104. if (edited_resource.is_null()) {
  105. edit_button->set_pressed(true);
  106. _update_menu();
  107. return;
  108. }
  109. emit_signal(SNAME("resource_selected"), edited_resource, false);
  110. }
  111. void EditorResourcePicker::_file_selected(const String &p_path) {
  112. Ref<Resource> loaded_resource = ResourceLoader::load(p_path);
  113. ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'.");
  114. if (!base_type.is_empty()) {
  115. bool any_type_matches = false;
  116. for (int i = 0; i < base_type.get_slice_count(","); i++) {
  117. String base = base_type.get_slice(",", i);
  118. if (loaded_resource->is_class(base)) {
  119. any_type_matches = true;
  120. break;
  121. }
  122. }
  123. if (!any_type_matches) {
  124. EditorNode::get_singleton()->show_warning(vformat(TTR("The selected resource (%s) does not match any type expected for this property (%s)."), loaded_resource->get_class(), base_type));
  125. return;
  126. }
  127. }
  128. edited_resource = loaded_resource;
  129. emit_signal(SNAME("resource_changed"), edited_resource);
  130. _update_resource();
  131. }
  132. void EditorResourcePicker::_file_quick_selected() {
  133. _file_selected(quick_open->get_selected());
  134. }
  135. void EditorResourcePicker::_update_menu() {
  136. _update_menu_items();
  137. Rect2 gt = edit_button->get_screen_rect();
  138. edit_menu->reset_size();
  139. int ms = edit_menu->get_contents_minimum_size().width;
  140. Vector2 popup_pos = gt.get_end() - Vector2(ms, 0);
  141. edit_menu->set_position(popup_pos);
  142. edit_menu->popup();
  143. }
  144. void EditorResourcePicker::_update_menu_items() {
  145. _ensure_resource_menu();
  146. edit_menu->clear();
  147. // Add options for creating specific subtypes of the base resource type.
  148. if (is_editable()) {
  149. set_create_options(edit_menu);
  150. // Add an option to load a resource from a file using the QuickOpen dialog.
  151. edit_menu->add_icon_item(get_theme_icon(SNAME("Load"), SNAME("EditorIcons")), TTR("Quick Load"), OBJ_MENU_QUICKLOAD);
  152. // Add an option to load a resource from a file using the regular file dialog.
  153. edit_menu->add_icon_item(get_theme_icon(SNAME("Load"), SNAME("EditorIcons")), TTR("Load"), OBJ_MENU_LOAD);
  154. }
  155. // Add options for changing existing value of the resource.
  156. if (edited_resource.is_valid()) {
  157. // Determine if the edited resource is part of another scene (foreign) which was imported
  158. bool is_edited_resource_foreign_import = EditorNode::get_singleton()->is_resource_read_only(edited_resource);
  159. // If the resource is determined to be foreign and imported, change the menu entry's description to 'inspect' rather than 'edit'
  160. // since will only be able to view its properties in read-only mode.
  161. if (is_edited_resource_foreign_import) {
  162. // The 'Search' icon is a magnifying glass, which seems appropriate, but maybe a bespoke icon is preferred here.
  163. edit_menu->add_icon_item(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")), TTR("Inspect"), OBJ_MENU_INSPECT);
  164. } else {
  165. edit_menu->add_icon_item(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), TTR("Edit"), OBJ_MENU_INSPECT);
  166. }
  167. if (is_editable()) {
  168. edit_menu->add_icon_item(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), TTR("Clear"), OBJ_MENU_CLEAR);
  169. edit_menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
  170. // Check whether the resource has subresources.
  171. List<PropertyInfo> property_list;
  172. edited_resource->get_property_list(&property_list);
  173. bool has_subresources = false;
  174. for (PropertyInfo &p : property_list) {
  175. if ((p.type == Variant::OBJECT) && (p.hint == PROPERTY_HINT_RESOURCE_TYPE) && (p.name != "script")) {
  176. has_subresources = true;
  177. break;
  178. }
  179. }
  180. if (has_subresources) {
  181. edit_menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Make Unique (Recursive)"), OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
  182. }
  183. edit_menu->add_icon_item(get_theme_icon(SNAME("Save"), SNAME("EditorIcons")), TTR("Save"), OBJ_MENU_SAVE);
  184. }
  185. if (edited_resource->get_path().is_resource_file()) {
  186. edit_menu->add_separator();
  187. edit_menu->add_item(TTR("Show in FileSystem"), OBJ_MENU_SHOW_IN_FILE_SYSTEM);
  188. }
  189. }
  190. // Add options to copy/paste resource.
  191. Ref<Resource> cb = EditorSettings::get_singleton()->get_resource_clipboard();
  192. bool paste_valid = false;
  193. if (is_editable()) {
  194. if (cb.is_valid()) {
  195. if (base_type.is_empty()) {
  196. paste_valid = true;
  197. } else {
  198. for (int i = 0; i < base_type.get_slice_count(","); i++) {
  199. if (ClassDB::is_parent_class(cb->get_class(), base_type.get_slice(",", i))) {
  200. paste_valid = true;
  201. break;
  202. }
  203. }
  204. }
  205. }
  206. }
  207. if (edited_resource.is_valid() || paste_valid) {
  208. edit_menu->add_separator();
  209. if (edited_resource.is_valid()) {
  210. edit_menu->add_item(TTR("Copy"), OBJ_MENU_COPY);
  211. }
  212. if (paste_valid) {
  213. edit_menu->add_item(TTR("Paste"), OBJ_MENU_PASTE);
  214. }
  215. }
  216. // Add options to convert existing resource to another type of resource.
  217. if (is_editable() && edited_resource.is_valid()) {
  218. Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(edited_resource);
  219. if (conversions.size()) {
  220. edit_menu->add_separator();
  221. }
  222. for (int i = 0; i < conversions.size(); i++) {
  223. String what = conversions[i]->converts_to();
  224. Ref<Texture2D> icon;
  225. if (has_theme_icon(what, SNAME("EditorIcons"))) {
  226. icon = get_theme_icon(what, SNAME("EditorIcons"));
  227. } else {
  228. icon = get_theme_icon(what, SNAME("Resource"));
  229. }
  230. edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + i);
  231. }
  232. }
  233. }
  234. void EditorResourcePicker::_edit_menu_cbk(int p_which) {
  235. switch (p_which) {
  236. case OBJ_MENU_LOAD: {
  237. List<String> extensions;
  238. for (int i = 0; i < base_type.get_slice_count(","); i++) {
  239. String base = base_type.get_slice(",", i);
  240. ResourceLoader::get_recognized_extensions_for_type(base, &extensions);
  241. }
  242. HashSet<String> valid_extensions;
  243. for (const String &E : extensions) {
  244. valid_extensions.insert(E);
  245. }
  246. if (!file_dialog) {
  247. file_dialog = memnew(EditorFileDialog);
  248. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
  249. add_child(file_dialog);
  250. file_dialog->connect("file_selected", callable_mp(this, &EditorResourcePicker::_file_selected));
  251. }
  252. file_dialog->clear_filters();
  253. for (const String &E : valid_extensions) {
  254. file_dialog->add_filter("*." + E, E.to_upper());
  255. }
  256. file_dialog->popup_file_dialog();
  257. } break;
  258. case OBJ_MENU_QUICKLOAD: {
  259. if (!quick_open) {
  260. quick_open = memnew(EditorQuickOpen);
  261. add_child(quick_open);
  262. quick_open->connect("quick_open", callable_mp(this, &EditorResourcePicker::_file_quick_selected));
  263. }
  264. quick_open->popup_dialog(base_type);
  265. quick_open->set_title(TTR("Resource"));
  266. } break;
  267. case OBJ_MENU_INSPECT: {
  268. if (edited_resource.is_valid()) {
  269. emit_signal(SNAME("resource_selected"), edited_resource, true);
  270. }
  271. } break;
  272. case OBJ_MENU_CLEAR: {
  273. edited_resource = Ref<Resource>();
  274. emit_signal(SNAME("resource_changed"), edited_resource);
  275. _update_resource();
  276. } break;
  277. case OBJ_MENU_MAKE_UNIQUE: {
  278. if (edited_resource.is_null()) {
  279. return;
  280. }
  281. Ref<Resource> unique_resource = edited_resource->duplicate();
  282. ERR_FAIL_COND(unique_resource.is_null());
  283. edited_resource = unique_resource;
  284. emit_signal(SNAME("resource_changed"), edited_resource);
  285. _update_resource();
  286. } break;
  287. case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {
  288. if (edited_resource.is_null()) {
  289. return;
  290. }
  291. Ref<Resource> unique_resource = edited_resource->duplicate(true);
  292. ERR_FAIL_COND(unique_resource.is_null());
  293. edited_resource = unique_resource;
  294. emit_signal(SNAME("resource_changed"), edited_resource);
  295. _update_resource();
  296. } break;
  297. case OBJ_MENU_SAVE: {
  298. if (edited_resource.is_null()) {
  299. return;
  300. }
  301. EditorNode::get_singleton()->save_resource(edited_resource);
  302. } break;
  303. case OBJ_MENU_COPY: {
  304. EditorSettings::get_singleton()->set_resource_clipboard(edited_resource);
  305. } break;
  306. case OBJ_MENU_PASTE: {
  307. edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();
  308. if (edited_resource->is_built_in() && EditorNode::get_singleton()->get_edited_scene() &&
  309. edited_resource->get_path().get_slice("::", 0) != EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path()) {
  310. // Automatically make resource unique if it belongs to another scene.
  311. _edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
  312. return;
  313. }
  314. emit_signal(SNAME("resource_changed"), edited_resource);
  315. _update_resource();
  316. } break;
  317. case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {
  318. FileSystemDock *file_system_dock = FileSystemDock::get_singleton();
  319. file_system_dock->navigate_to_path(edited_resource->get_path());
  320. // Ensure that the FileSystem dock is visible.
  321. TabContainer *tab_container = (TabContainer *)file_system_dock->get_parent_control();
  322. tab_container->set_current_tab(tab_container->get_tab_idx_from_control(file_system_dock));
  323. } break;
  324. default: {
  325. // Allow subclasses to handle their own options first, only then fallback on the default branch logic.
  326. if (handle_menu_selected(p_which)) {
  327. break;
  328. }
  329. if (p_which >= CONVERT_BASE_ID) {
  330. int to_type = p_which - CONVERT_BASE_ID;
  331. Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin(edited_resource);
  332. ERR_FAIL_INDEX(to_type, conversions.size());
  333. edited_resource = conversions[to_type]->convert(edited_resource);
  334. emit_signal(SNAME("resource_changed"), edited_resource);
  335. _update_resource();
  336. break;
  337. }
  338. ERR_FAIL_COND(inheritors_array.is_empty());
  339. String intype = inheritors_array[p_which - TYPE_BASE_ID];
  340. Variant obj;
  341. if (ScriptServer::is_global_class(intype)) {
  342. obj = ClassDB::instantiate(ScriptServer::get_global_class_native_base(intype));
  343. if (obj) {
  344. Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(intype));
  345. if (script.is_valid()) {
  346. ((Object *)obj)->set_script(script);
  347. }
  348. }
  349. } else {
  350. obj = ClassDB::instantiate(intype);
  351. }
  352. if (!obj) {
  353. obj = EditorNode::get_editor_data().instance_custom_type(intype, "Resource");
  354. }
  355. Resource *resp = Object::cast_to<Resource>(obj);
  356. ERR_BREAK(!resp);
  357. EditorNode::get_editor_data().instantiate_object_properties(obj);
  358. edited_resource = Ref<Resource>(resp);
  359. emit_signal(SNAME("resource_changed"), edited_resource);
  360. _update_resource();
  361. } break;
  362. }
  363. }
  364. void EditorResourcePicker::set_create_options(Object *p_menu_node) {
  365. _ensure_resource_menu();
  366. // If a subclass implements this method, use it to replace all create items.
  367. if (GDVIRTUAL_CALL(_set_create_options, p_menu_node)) {
  368. return;
  369. }
  370. // By default provide generic "New ..." options.
  371. if (!base_type.is_empty()) {
  372. int idx = 0;
  373. HashSet<String> allowed_types;
  374. _get_allowed_types(false, &allowed_types);
  375. Vector<EditorData::CustomType> custom_resources;
  376. if (EditorNode::get_editor_data().get_custom_types().has("Resource")) {
  377. custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"];
  378. }
  379. for (const String &E : allowed_types) {
  380. const String &t = E;
  381. bool is_custom_resource = false;
  382. Ref<Texture2D> icon;
  383. if (!custom_resources.is_empty()) {
  384. for (int j = 0; j < custom_resources.size(); j++) {
  385. if (custom_resources[j].name == t) {
  386. is_custom_resource = true;
  387. if (custom_resources[j].icon.is_valid()) {
  388. icon = custom_resources[j].icon;
  389. }
  390. break;
  391. }
  392. }
  393. }
  394. if (!is_custom_resource && !(ScriptServer::is_global_class(t) || ClassDB::can_instantiate(t))) {
  395. continue;
  396. }
  397. inheritors_array.push_back(t);
  398. if (!icon.is_valid()) {
  399. icon = get_theme_icon(has_theme_icon(t, SNAME("EditorIcons")) ? t : String("Object"), SNAME("EditorIcons"));
  400. }
  401. int id = TYPE_BASE_ID + idx;
  402. edit_menu->add_icon_item(icon, vformat(TTR("New %s"), t), id);
  403. idx++;
  404. }
  405. if (edit_menu->get_item_count()) {
  406. edit_menu->add_separator();
  407. }
  408. }
  409. }
  410. bool EditorResourcePicker::handle_menu_selected(int p_which) {
  411. bool success;
  412. if (GDVIRTUAL_CALL(_handle_menu_selected, p_which, success)) {
  413. return success;
  414. }
  415. return false;
  416. }
  417. void EditorResourcePicker::_button_draw() {
  418. if (dropping) {
  419. Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
  420. assign_button->draw_rect(Rect2(Point2(), assign_button->get_size()), color, false);
  421. }
  422. }
  423. void EditorResourcePicker::_button_input(const Ref<InputEvent> &p_event) {
  424. Ref<InputEventMouseButton> mb = p_event;
  425. if (mb.is_valid()) {
  426. if (mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
  427. // Only attempt to update and show the menu if we have
  428. // a valid resource or the Picker is editable, as
  429. // there will otherwise be nothing to display.
  430. if (edited_resource.is_valid() || is_editable()) {
  431. _update_menu_items();
  432. Vector2 pos = get_screen_position() + mb->get_position();
  433. edit_menu->reset_size();
  434. edit_menu->set_position(pos);
  435. edit_menu->popup();
  436. }
  437. }
  438. }
  439. }
  440. void EditorResourcePicker::_get_allowed_types(bool p_with_convert, HashSet<String> *p_vector) const {
  441. Vector<String> allowed_types = base_type.split(",");
  442. int size = allowed_types.size();
  443. List<StringName> global_classes;
  444. ScriptServer::get_global_class_list(&global_classes);
  445. for (int i = 0; i < size; i++) {
  446. String base = allowed_types[i].strip_edges();
  447. p_vector->insert(base);
  448. // If we hit a familiar base type, take all the data from cache.
  449. if (allowed_types_cache.has(base)) {
  450. List<StringName> allowed_subtypes = allowed_types_cache[base];
  451. for (const StringName &subtype_name : allowed_subtypes) {
  452. p_vector->insert(subtype_name);
  453. }
  454. } else {
  455. List<StringName> allowed_subtypes;
  456. List<StringName> inheriters;
  457. ClassDB::get_inheriters_from_class(base, &inheriters);
  458. for (const StringName &subtype_name : inheriters) {
  459. p_vector->insert(subtype_name);
  460. allowed_subtypes.push_back(subtype_name);
  461. }
  462. for (const StringName &subtype_name : global_classes) {
  463. if (EditorNode::get_editor_data().script_class_is_parent(subtype_name, base)) {
  464. p_vector->insert(subtype_name);
  465. allowed_subtypes.push_back(subtype_name);
  466. }
  467. }
  468. // Store the subtypes of the base type in the cache for future use.
  469. allowed_types_cache[base] = allowed_subtypes;
  470. }
  471. if (p_with_convert) {
  472. if (base == "BaseMaterial3D") {
  473. p_vector->insert("Texture2D");
  474. } else if (base == "ShaderMaterial") {
  475. p_vector->insert("Shader");
  476. } else if (base == "Texture2D") {
  477. p_vector->insert("Image");
  478. }
  479. }
  480. }
  481. if (EditorNode::get_editor_data().get_custom_types().has("Resource")) {
  482. Vector<EditorData::CustomType> custom_resources = EditorNode::get_editor_data().get_custom_types()["Resource"];
  483. for (int i = 0; i < custom_resources.size(); i++) {
  484. p_vector->insert(custom_resources[i].name);
  485. }
  486. }
  487. }
  488. bool EditorResourcePicker::_is_drop_valid(const Dictionary &p_drag_data) const {
  489. if (base_type.is_empty()) {
  490. return true;
  491. }
  492. Dictionary drag_data = p_drag_data;
  493. Ref<Resource> res;
  494. if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {
  495. ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);
  496. if (se) {
  497. res = se->get_edited_resource();
  498. }
  499. } else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
  500. res = drag_data["resource"];
  501. }
  502. HashSet<String> allowed_types;
  503. _get_allowed_types(true, &allowed_types);
  504. if (res.is_valid() && _is_type_valid(res->get_class(), allowed_types)) {
  505. return true;
  506. }
  507. if (res.is_valid() && res->get_script()) {
  508. StringName custom_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());
  509. if (_is_type_valid(custom_class, allowed_types)) {
  510. return true;
  511. }
  512. }
  513. if (drag_data.has("type") && String(drag_data["type"]) == "files") {
  514. Vector<String> files = drag_data["files"];
  515. if (files.size() == 1) {
  516. String file = files[0];
  517. String file_type = EditorFileSystem::get_singleton()->get_file_type(file);
  518. if (!file_type.is_empty() && _is_type_valid(file_type, allowed_types)) {
  519. return true;
  520. }
  521. }
  522. }
  523. return false;
  524. }
  525. bool EditorResourcePicker::_is_type_valid(const String p_type_name, HashSet<String> p_allowed_types) const {
  526. for (const String &E : p_allowed_types) {
  527. String at = E.strip_edges();
  528. if (p_type_name == at || ClassDB::is_parent_class(p_type_name, at) || EditorNode::get_editor_data().script_class_is_parent(p_type_name, at)) {
  529. return true;
  530. }
  531. }
  532. return false;
  533. }
  534. Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
  535. if (edited_resource.is_valid()) {
  536. return EditorNode::get_singleton()->drag_resource(edited_resource, p_from);
  537. }
  538. return Variant();
  539. }
  540. bool EditorResourcePicker::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
  541. return editable && _is_drop_valid(p_data);
  542. }
  543. void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
  544. ERR_FAIL_COND(!_is_drop_valid(p_data));
  545. Dictionary drag_data = p_data;
  546. Ref<Resource> dropped_resource;
  547. if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {
  548. ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);
  549. if (se) {
  550. dropped_resource = se->get_edited_resource();
  551. }
  552. } else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
  553. dropped_resource = drag_data["resource"];
  554. }
  555. if (!dropped_resource.is_valid() && drag_data.has("type") && String(drag_data["type"]) == "files") {
  556. Vector<String> files = drag_data["files"];
  557. if (files.size() == 1) {
  558. String file = files[0];
  559. dropped_resource = ResourceLoader::load(file);
  560. }
  561. }
  562. if (dropped_resource.is_valid()) {
  563. HashSet<String> allowed_types;
  564. _get_allowed_types(false, &allowed_types);
  565. // If the accepted dropped resource is from the extended list, it requires conversion.
  566. if (!_is_type_valid(dropped_resource->get_class(), allowed_types)) {
  567. for (const String &E : allowed_types) {
  568. String at = E.strip_edges();
  569. if (at == "BaseMaterial3D" && Ref<Texture2D>(dropped_resource).is_valid()) {
  570. // Use existing resource if possible and only replace its data.
  571. Ref<StandardMaterial3D> mat = edited_resource;
  572. if (!mat.is_valid()) {
  573. mat.instantiate();
  574. }
  575. mat->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, dropped_resource);
  576. dropped_resource = mat;
  577. break;
  578. }
  579. if (at == "ShaderMaterial" && Ref<Shader>(dropped_resource).is_valid()) {
  580. Ref<ShaderMaterial> mat = edited_resource;
  581. if (!mat.is_valid()) {
  582. mat.instantiate();
  583. }
  584. mat->set_shader(dropped_resource);
  585. dropped_resource = mat;
  586. break;
  587. }
  588. if (at == "Texture2D" && Ref<Image>(dropped_resource).is_valid()) {
  589. Ref<ImageTexture> texture = edited_resource;
  590. if (!texture.is_valid()) {
  591. texture.instantiate();
  592. }
  593. texture->set_image(dropped_resource);
  594. dropped_resource = texture;
  595. break;
  596. }
  597. }
  598. }
  599. edited_resource = dropped_resource;
  600. emit_signal(SNAME("resource_changed"), edited_resource);
  601. _update_resource();
  602. }
  603. }
  604. void EditorResourcePicker::_bind_methods() {
  605. ClassDB::bind_method(D_METHOD("_update_resource_preview"), &EditorResourcePicker::_update_resource_preview);
  606. ClassDB::bind_method(D_METHOD("_get_drag_data_fw", "position", "from"), &EditorResourcePicker::get_drag_data_fw);
  607. ClassDB::bind_method(D_METHOD("_can_drop_data_fw", "position", "data", "from"), &EditorResourcePicker::can_drop_data_fw);
  608. ClassDB::bind_method(D_METHOD("_drop_data_fw", "position", "data", "from"), &EditorResourcePicker::drop_data_fw);
  609. ClassDB::bind_method(D_METHOD("set_base_type", "base_type"), &EditorResourcePicker::set_base_type);
  610. ClassDB::bind_method(D_METHOD("get_base_type"), &EditorResourcePicker::get_base_type);
  611. ClassDB::bind_method(D_METHOD("get_allowed_types"), &EditorResourcePicker::get_allowed_types);
  612. ClassDB::bind_method(D_METHOD("set_edited_resource", "resource"), &EditorResourcePicker::set_edited_resource);
  613. ClassDB::bind_method(D_METHOD("get_edited_resource"), &EditorResourcePicker::get_edited_resource);
  614. ClassDB::bind_method(D_METHOD("set_toggle_mode", "enable"), &EditorResourcePicker::set_toggle_mode);
  615. ClassDB::bind_method(D_METHOD("is_toggle_mode"), &EditorResourcePicker::is_toggle_mode);
  616. ClassDB::bind_method(D_METHOD("set_toggle_pressed", "pressed"), &EditorResourcePicker::set_toggle_pressed);
  617. ClassDB::bind_method(D_METHOD("set_editable", "enable"), &EditorResourcePicker::set_editable);
  618. ClassDB::bind_method(D_METHOD("is_editable"), &EditorResourcePicker::is_editable);
  619. GDVIRTUAL_BIND(_set_create_options, "menu_node");
  620. GDVIRTUAL_BIND(_handle_menu_selected, "id");
  621. ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_type"), "set_base_type", "get_base_type");
  622. ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource", PROPERTY_USAGE_NONE), "set_edited_resource", "get_edited_resource");
  623. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
  624. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");
  625. ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::BOOL, "inspect")));
  626. ADD_SIGNAL(MethodInfo("resource_changed", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
  627. }
  628. void EditorResourcePicker::_notification(int p_what) {
  629. switch (p_what) {
  630. case NOTIFICATION_ENTER_TREE: {
  631. _update_resource();
  632. [[fallthrough]];
  633. }
  634. case NOTIFICATION_THEME_CHANGED: {
  635. edit_button->set_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree")));
  636. } break;
  637. case NOTIFICATION_DRAW: {
  638. draw_style_box(get_theme_stylebox(SNAME("bg"), SNAME("Tree")), Rect2(Point2(), get_size()));
  639. } break;
  640. case NOTIFICATION_DRAG_BEGIN: {
  641. if (editable && _is_drop_valid(get_viewport()->gui_get_drag_data())) {
  642. dropping = true;
  643. assign_button->queue_redraw();
  644. }
  645. } break;
  646. case NOTIFICATION_DRAG_END: {
  647. if (dropping) {
  648. dropping = false;
  649. assign_button->queue_redraw();
  650. }
  651. } break;
  652. }
  653. }
  654. void EditorResourcePicker::set_base_type(const String &p_base_type) {
  655. base_type = p_base_type;
  656. // There is a possibility that the new base type is conflicting with the existing value.
  657. // Keep the value, but warn the user that there is a potential mistake.
  658. if (!base_type.is_empty() && edited_resource.is_valid()) {
  659. HashSet<String> allowed_types;
  660. _get_allowed_types(true, &allowed_types);
  661. StringName custom_class;
  662. bool is_custom = false;
  663. if (edited_resource->get_script()) {
  664. custom_class = EditorNode::get_singleton()->get_object_custom_type_name(edited_resource->get_script());
  665. is_custom = _is_type_valid(custom_class, allowed_types);
  666. }
  667. if (!is_custom && !_is_type_valid(edited_resource->get_class(), allowed_types)) {
  668. String class_str = (custom_class == StringName() ? edited_resource->get_class() : vformat("%s (%s)", custom_class, edited_resource->get_class()));
  669. WARN_PRINT(vformat("Value mismatch between the new base type of this EditorResourcePicker, '%s', and the type of the value it already has, '%s'.", base_type, class_str));
  670. }
  671. } else {
  672. // Call the method to build the cache immediately.
  673. HashSet<String> allowed_types;
  674. _get_allowed_types(false, &allowed_types);
  675. }
  676. }
  677. String EditorResourcePicker::get_base_type() const {
  678. return base_type;
  679. }
  680. Vector<String> EditorResourcePicker::get_allowed_types() const {
  681. HashSet<String> allowed_types;
  682. _get_allowed_types(false, &allowed_types);
  683. Vector<String> types;
  684. types.resize(allowed_types.size());
  685. int i = 0;
  686. String *w = types.ptrw();
  687. for (const String &E : allowed_types) {
  688. w[i] = E;
  689. i++;
  690. }
  691. return types;
  692. }
  693. void EditorResourcePicker::set_edited_resource(Ref<Resource> p_resource) {
  694. if (!p_resource.is_valid()) {
  695. edited_resource = Ref<Resource>();
  696. _update_resource();
  697. return;
  698. }
  699. if (!base_type.is_empty()) {
  700. HashSet<String> allowed_types;
  701. _get_allowed_types(true, &allowed_types);
  702. StringName custom_class;
  703. bool is_custom = false;
  704. if (p_resource->get_script()) {
  705. custom_class = EditorNode::get_singleton()->get_object_custom_type_name(p_resource->get_script());
  706. is_custom = _is_type_valid(custom_class, allowed_types);
  707. }
  708. if (!is_custom && !_is_type_valid(p_resource->get_class(), allowed_types)) {
  709. String class_str = (custom_class == StringName() ? p_resource->get_class() : vformat("%s (%s)", custom_class, p_resource->get_class()));
  710. ERR_FAIL_MSG(vformat("Failed to set a resource of the type '%s' because this EditorResourcePicker only accepts '%s' and its derivatives.", class_str, base_type));
  711. }
  712. }
  713. edited_resource = p_resource;
  714. _update_resource();
  715. }
  716. Ref<Resource> EditorResourcePicker::get_edited_resource() {
  717. return edited_resource;
  718. }
  719. void EditorResourcePicker::set_toggle_mode(bool p_enable) {
  720. assign_button->set_toggle_mode(p_enable);
  721. }
  722. bool EditorResourcePicker::is_toggle_mode() const {
  723. return assign_button->is_toggle_mode();
  724. }
  725. void EditorResourcePicker::set_toggle_pressed(bool p_pressed) {
  726. if (!is_toggle_mode()) {
  727. return;
  728. }
  729. assign_button->set_pressed(p_pressed);
  730. }
  731. void EditorResourcePicker::set_editable(bool p_editable) {
  732. editable = p_editable;
  733. assign_button->set_disabled(!editable && !edited_resource.is_valid());
  734. edit_button->set_visible(editable);
  735. }
  736. bool EditorResourcePicker::is_editable() const {
  737. return editable;
  738. }
  739. void EditorResourcePicker::_ensure_resource_menu() {
  740. if (edit_menu) {
  741. return;
  742. }
  743. edit_menu = memnew(PopupMenu);
  744. add_child(edit_menu);
  745. edit_menu->connect("id_pressed", callable_mp(this, &EditorResourcePicker::_edit_menu_cbk));
  746. edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed).bind(false));
  747. }
  748. EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {
  749. assign_button = memnew(Button);
  750. assign_button->set_flat(true);
  751. assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
  752. assign_button->set_clip_text(true);
  753. assign_button->set_drag_forwarding(this);
  754. add_child(assign_button);
  755. assign_button->connect("pressed", callable_mp(this, &EditorResourcePicker::_resource_selected));
  756. assign_button->connect("draw", callable_mp(this, &EditorResourcePicker::_button_draw));
  757. assign_button->connect("gui_input", callable_mp(this, &EditorResourcePicker::_button_input));
  758. if (!p_hide_assign_button_controls) {
  759. preview_rect = memnew(TextureRect);
  760. preview_rect->set_ignore_texture_size(true);
  761. preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  762. preview_rect->set_offset(SIDE_TOP, 1);
  763. preview_rect->set_offset(SIDE_BOTTOM, -1);
  764. preview_rect->set_offset(SIDE_RIGHT, -1);
  765. assign_button->add_child(preview_rect);
  766. }
  767. edit_button = memnew(Button);
  768. edit_button->set_flat(true);
  769. edit_button->set_toggle_mode(true);
  770. edit_button->connect("pressed", callable_mp(this, &EditorResourcePicker::_update_menu));
  771. add_child(edit_button);
  772. edit_button->connect("gui_input", callable_mp(this, &EditorResourcePicker::_button_input));
  773. add_theme_constant_override("separation", 0);
  774. }
  775. // EditorScriptPicker
  776. void EditorScriptPicker::set_create_options(Object *p_menu_node) {
  777. PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
  778. if (!menu_node) {
  779. return;
  780. }
  781. menu_node->add_icon_item(get_theme_icon(SNAME("ScriptCreate"), SNAME("EditorIcons")), TTR("New Script"), OBJ_MENU_NEW_SCRIPT);
  782. if (script_owner) {
  783. Ref<Script> script = script_owner->get_script();
  784. if (script.is_valid()) {
  785. menu_node->add_icon_item(get_theme_icon(SNAME("ScriptExtend"), SNAME("EditorIcons")), TTR("Extend Script"), OBJ_MENU_EXTEND_SCRIPT);
  786. }
  787. }
  788. menu_node->add_separator();
  789. }
  790. bool EditorScriptPicker::handle_menu_selected(int p_which) {
  791. switch (p_which) {
  792. case OBJ_MENU_NEW_SCRIPT: {
  793. if (script_owner) {
  794. SceneTreeDock::get_singleton()->open_script_dialog(script_owner, false);
  795. }
  796. return true;
  797. }
  798. case OBJ_MENU_EXTEND_SCRIPT: {
  799. if (script_owner) {
  800. SceneTreeDock::get_singleton()->open_script_dialog(script_owner, true);
  801. }
  802. return true;
  803. }
  804. }
  805. return false;
  806. }
  807. void EditorScriptPicker::set_script_owner(Node *p_owner) {
  808. script_owner = p_owner;
  809. }
  810. Node *EditorScriptPicker::get_script_owner() const {
  811. return script_owner;
  812. }
  813. void EditorScriptPicker::_bind_methods() {
  814. ClassDB::bind_method(D_METHOD("set_script_owner", "owner_node"), &EditorScriptPicker::set_script_owner);
  815. ClassDB::bind_method(D_METHOD("get_script_owner"), &EditorScriptPicker::get_script_owner);
  816. ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "script_owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_script_owner", "get_script_owner");
  817. }
  818. EditorScriptPicker::EditorScriptPicker() {
  819. }
  820. // EditorShaderPicker
  821. void EditorShaderPicker::set_create_options(Object *p_menu_node) {
  822. PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
  823. if (!menu_node) {
  824. return;
  825. }
  826. menu_node->add_icon_item(get_theme_icon(SNAME("Shader"), SNAME("EditorIcons")), TTR("New Shader"), OBJ_MENU_NEW_SHADER);
  827. menu_node->add_separator();
  828. }
  829. bool EditorShaderPicker::handle_menu_selected(int p_which) {
  830. Ref<ShaderMaterial> material = Ref<ShaderMaterial>(get_edited_material());
  831. switch (p_which) {
  832. case OBJ_MENU_NEW_SHADER: {
  833. if (material.is_valid()) {
  834. SceneTreeDock::get_singleton()->open_shader_dialog(material, preferred_mode);
  835. return true;
  836. }
  837. } break;
  838. default:
  839. break;
  840. }
  841. return false;
  842. }
  843. void EditorShaderPicker::set_edited_material(ShaderMaterial *p_material) {
  844. edited_material = p_material;
  845. }
  846. ShaderMaterial *EditorShaderPicker::get_edited_material() const {
  847. return edited_material;
  848. }
  849. void EditorShaderPicker::set_preferred_mode(int p_mode) {
  850. preferred_mode = p_mode;
  851. }
  852. EditorShaderPicker::EditorShaderPicker() {
  853. }
  854. //////////////
  855. void EditorAudioStreamPicker::_notification(int p_what) {
  856. switch (p_what) {
  857. case NOTIFICATION_READY:
  858. case NOTIFICATION_THEME_CHANGED: {
  859. _update_resource();
  860. } break;
  861. case NOTIFICATION_INTERNAL_PROCESS: {
  862. Ref<AudioStream> audio_stream = get_edited_resource();
  863. if (audio_stream.is_valid()) {
  864. if (audio_stream->get_length() > 0) {
  865. Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
  866. if (preview.is_valid()) {
  867. if (preview->get_version() != last_preview_version) {
  868. stream_preview_rect->queue_redraw();
  869. last_preview_version = preview->get_version();
  870. }
  871. }
  872. }
  873. uint64_t tagged_frame = audio_stream->get_tagged_frame();
  874. uint64_t diff_frames = AudioServer::get_singleton()->get_mixed_frames() - tagged_frame;
  875. uint64_t diff_msec = diff_frames * 1000 / AudioServer::get_singleton()->get_mix_rate();
  876. if (diff_msec < 300) {
  877. uint32_t count = audio_stream->get_tagged_frame_count();
  878. bool differ = false;
  879. if (count != tagged_frame_offset_count) {
  880. differ = true;
  881. }
  882. float offsets[MAX_TAGGED_FRAMES];
  883. for (uint32_t i = 0; i < MIN(count, uint32_t(MAX_TAGGED_FRAMES)); i++) {
  884. offsets[i] = audio_stream->get_tagged_frame_offset(i);
  885. if (offsets[i] != tagged_frame_offsets[i]) {
  886. differ = true;
  887. }
  888. }
  889. if (differ) {
  890. tagged_frame_offset_count = count;
  891. for (uint32_t i = 0; i < count; i++) {
  892. tagged_frame_offsets[i] = offsets[i];
  893. }
  894. }
  895. stream_preview_rect->queue_redraw();
  896. } else {
  897. if (tagged_frame_offset_count != 0) {
  898. stream_preview_rect->queue_redraw();
  899. }
  900. tagged_frame_offset_count = 0;
  901. }
  902. }
  903. } break;
  904. }
  905. }
  906. void EditorAudioStreamPicker::_update_resource() {
  907. EditorResourcePicker::_update_resource();
  908. Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
  909. int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
  910. Ref<AudioStream> audio_stream = get_edited_resource();
  911. if (audio_stream.is_valid() && audio_stream->get_length() > 0.0) {
  912. set_assign_button_min_size(Size2(1, font->get_height(font_size) * 3));
  913. } else {
  914. set_assign_button_min_size(Size2(1, font->get_height(font_size) * 1.5));
  915. }
  916. stream_preview_rect->queue_redraw();
  917. }
  918. void EditorAudioStreamPicker::_preview_draw() {
  919. Ref<AudioStream> audio_stream = get_edited_resource();
  920. if (!audio_stream.is_valid()) {
  921. get_assign_button()->set_text(TTR("<empty>"));
  922. return;
  923. }
  924. int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
  925. get_assign_button()->set_text("");
  926. Size2i size = stream_preview_rect->get_size();
  927. Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
  928. Rect2 rect(Point2(), size);
  929. if (audio_stream->get_length() > 0) {
  930. rect.size.height *= 0.5;
  931. stream_preview_rect->draw_rect(rect, Color(0, 0, 0, 1));
  932. Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
  933. float preview_len = preview->get_length();
  934. Vector<Vector2> lines;
  935. lines.resize(size.width * 2);
  936. for (int i = 0; i < size.width; i++) {
  937. float ofs = i * preview_len / size.width;
  938. float ofs_n = (i + 1) * preview_len / size.width;
  939. float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
  940. float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
  941. int idx = i;
  942. lines.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y);
  943. lines.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y);
  944. }
  945. Vector<Color> color;
  946. color.push_back(get_theme_color(SNAME("contrast_color_2"), SNAME("Editor")));
  947. RS::get_singleton()->canvas_item_add_multiline(stream_preview_rect->get_canvas_item(), lines, color);
  948. if (tagged_frame_offset_count) {
  949. Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
  950. for (uint32_t i = 0; i < tagged_frame_offset_count; i++) {
  951. int x = CLAMP(tagged_frame_offsets[i] * size.width / preview_len, 0, size.width);
  952. if (x == 0) {
  953. continue; // Because some may always return 0, ignore offset 0.
  954. }
  955. stream_preview_rect->draw_rect(Rect2i(x, 0, 2, rect.size.height), accent);
  956. }
  957. }
  958. rect.position.y += rect.size.height;
  959. }
  960. Ref<Texture2D> icon;
  961. Color icon_modulate(1, 1, 1, 1);
  962. if (tagged_frame_offset_count > 0) {
  963. icon = get_theme_icon(SNAME("Play"), SNAME("EditorIcons"));
  964. if ((OS::get_singleton()->get_ticks_msec() % 500) > 250) {
  965. icon_modulate = Color(1, 0.5, 0.5, 1); // get_theme_color(SNAME("accent_color"), SNAME("Editor"));
  966. }
  967. } else {
  968. icon = EditorNode::get_singleton()->get_object_icon(audio_stream.operator->(), "Object");
  969. }
  970. String text;
  971. if (!audio_stream->get_name().is_empty()) {
  972. text = audio_stream->get_name();
  973. } else if (audio_stream->get_path().is_resource_file()) {
  974. text = audio_stream->get_path().get_file();
  975. } else {
  976. text = audio_stream->get_class().replace_first("AudioStream", "");
  977. }
  978. stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 2, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);
  979. stream_preview_rect->draw_string(font, Point2i(EDSCALE * 2 + icon->get_width(), rect.position.y + font->get_ascent(font_size) + (rect.size.height - font->get_height(font_size)) / 2), text, HORIZONTAL_ALIGNMENT_CENTER, size.width - 4 * EDSCALE - icon->get_width());
  980. }
  981. EditorAudioStreamPicker::EditorAudioStreamPicker() :
  982. EditorResourcePicker(true) {
  983. stream_preview_rect = memnew(Control);
  984. stream_preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
  985. stream_preview_rect->set_offset(SIDE_TOP, 1);
  986. stream_preview_rect->set_offset(SIDE_BOTTOM, -1);
  987. stream_preview_rect->set_offset(SIDE_RIGHT, -1);
  988. stream_preview_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
  989. stream_preview_rect->connect("draw", callable_mp(this, &EditorAudioStreamPicker::_preview_draw));
  990. get_assign_button()->add_child(stream_preview_rect);
  991. get_assign_button()->move_child(stream_preview_rect, 0);
  992. set_process_internal(true);
  993. }