initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/inspector/SCsub
Normal file
6
editor/inspector/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
114
editor/inspector/add_metadata_dialog.cpp
Normal file
114
editor/inspector/add_metadata_dialog.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/**************************************************************************/
|
||||
/* add_metadata_dialog.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "add_metadata_dialog.h"
|
||||
|
||||
#include "editor/gui/editor_validation_panel.h"
|
||||
#include "editor/gui/editor_variant_type_selectors.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
|
||||
AddMetadataDialog::AddMetadataDialog() {
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
vbc->add_child(hbc);
|
||||
hbc->add_child(memnew(Label(TTR("Name:"))));
|
||||
|
||||
add_meta_name = memnew(LineEdit);
|
||||
add_meta_name->set_accessibility_name(TTRC("Name:"));
|
||||
add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1));
|
||||
hbc->add_child(add_meta_name);
|
||||
hbc->add_child(memnew(Label(TTR("Type:"))));
|
||||
|
||||
add_meta_type = memnew(EditorVariantTypeOptionButton);
|
||||
add_meta_type->set_accessibility_name(TTRC("Type:"));
|
||||
|
||||
hbc->add_child(add_meta_type);
|
||||
|
||||
Control *spacing = memnew(Control);
|
||||
vbc->add_child(spacing);
|
||||
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
|
||||
|
||||
set_ok_button_text(TTR("Add"));
|
||||
register_text_enter(add_meta_name);
|
||||
|
||||
validation_panel = memnew(EditorValidationPanel);
|
||||
vbc->add_child(validation_panel);
|
||||
validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid."));
|
||||
validation_panel->set_update_callback(callable_mp(this, &AddMetadataDialog::_check_meta_name));
|
||||
validation_panel->set_accept_button(get_ok_button());
|
||||
|
||||
add_meta_name->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
|
||||
}
|
||||
|
||||
void AddMetadataDialog::_complete_init(const StringName &p_title) {
|
||||
add_meta_name->set_text("");
|
||||
validation_panel->update();
|
||||
|
||||
set_title(vformat(TTR("Add Metadata Property for \"%s\""), p_title));
|
||||
|
||||
if (add_meta_type->get_item_count() == 0) {
|
||||
add_meta_type->populate({ Variant::NIL }, { { Variant::OBJECT, "Resource" } });
|
||||
}
|
||||
}
|
||||
|
||||
void AddMetadataDialog::open(const StringName p_title, List<StringName> &p_existing_metas) {
|
||||
this->_existing_metas = p_existing_metas;
|
||||
_complete_init(p_title);
|
||||
popup_centered();
|
||||
add_meta_name->grab_focus();
|
||||
}
|
||||
|
||||
StringName AddMetadataDialog::get_meta_name() {
|
||||
return add_meta_name->get_text();
|
||||
}
|
||||
|
||||
Variant AddMetadataDialog::get_meta_defval() {
|
||||
Variant defval;
|
||||
Callable::CallError ce;
|
||||
Variant::construct(add_meta_type->get_selected_type(), defval, nullptr, 0, ce);
|
||||
return defval;
|
||||
}
|
||||
|
||||
void AddMetadataDialog::_check_meta_name() {
|
||||
const String meta_name = add_meta_name->get_text();
|
||||
|
||||
if (meta_name.is_empty()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (!meta_name.is_valid_ascii_identifier()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (_existing_metas.find(meta_name)) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (meta_name[0] == '_') {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR);
|
||||
}
|
||||
}
|
||||
58
editor/inspector/add_metadata_dialog.h
Normal file
58
editor/inspector/add_metadata_dialog.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**************************************************************************/
|
||||
/* add_metadata_dialog.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class EditorValidationPanel;
|
||||
class EditorVariantTypeOptionButton;
|
||||
class LineEdit;
|
||||
|
||||
class AddMetadataDialog : public ConfirmationDialog {
|
||||
GDCLASS(AddMetadataDialog, ConfirmationDialog);
|
||||
|
||||
public:
|
||||
AddMetadataDialog();
|
||||
void open(const StringName p_title, List<StringName> &p_existing_metas);
|
||||
|
||||
StringName get_meta_name();
|
||||
Variant get_meta_defval();
|
||||
|
||||
private:
|
||||
List<StringName> _existing_metas;
|
||||
|
||||
void _check_meta_name();
|
||||
void _complete_init(const StringName &p_label);
|
||||
|
||||
LineEdit *add_meta_name = nullptr;
|
||||
EditorVariantTypeOptionButton *add_meta_type = nullptr;
|
||||
EditorValidationPanel *validation_panel = nullptr;
|
||||
};
|
||||
212
editor/inspector/editor_context_menu_plugin.cpp
Normal file
212
editor/inspector/editor_context_menu_plugin.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/**************************************************************************/
|
||||
/* editor_context_menu_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_context_menu_plugin.h"
|
||||
|
||||
#include "core/input/shortcut.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
void EditorContextMenuPlugin::get_options(const Vector<String> &p_paths) {
|
||||
GDVIRTUAL_CALL(_popup_menu, p_paths);
|
||||
}
|
||||
|
||||
void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable) {
|
||||
context_menu_shortcuts.insert(p_shortcut, p_callable);
|
||||
}
|
||||
|
||||
void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture) {
|
||||
ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
|
||||
ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
|
||||
|
||||
ContextMenuItem item;
|
||||
item.item_name = p_name;
|
||||
item.callable = p_callable;
|
||||
item.icon = p_texture;
|
||||
context_menu_items.insert(p_name, item);
|
||||
}
|
||||
|
||||
void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture) {
|
||||
Callable *callback = context_menu_shortcuts.getptr(p_shortcut);
|
||||
ERR_FAIL_NULL_MSG(callback, "Shortcut not registered. Use add_menu_shortcut() first.");
|
||||
|
||||
ContextMenuItem item;
|
||||
item.item_name = p_name;
|
||||
item.callable = *callback;
|
||||
item.icon = p_texture;
|
||||
item.shortcut = p_shortcut;
|
||||
context_menu_items.insert(p_name, item);
|
||||
}
|
||||
|
||||
void EditorContextMenuPlugin::add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture) {
|
||||
ERR_FAIL_NULL(p_menu);
|
||||
|
||||
ContextMenuItem item;
|
||||
item.item_name = p_name;
|
||||
item.icon = p_texture;
|
||||
item.submenu = p_menu;
|
||||
context_menu_items.insert(p_name, item);
|
||||
}
|
||||
|
||||
void EditorContextMenuPlugin::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
|
||||
ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()));
|
||||
ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>()));
|
||||
ClassDB::bind_method(D_METHOD("add_context_submenu_item", "name", "menu", "icon"), &EditorContextMenuPlugin::add_context_submenu_item, DEFVAL(Ref<Texture2D>()));
|
||||
|
||||
GDVIRTUAL_BIND(_popup_menu, "paths");
|
||||
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM_CREATE);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR_CODE);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TABS);
|
||||
BIND_ENUM_CONSTANT(CONTEXT_SLOT_2D_EDITOR);
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::add_plugin(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
|
||||
ERR_FAIL_COND(p_plugin.is_null());
|
||||
ERR_FAIL_COND(plugin_list.has(p_plugin));
|
||||
|
||||
p_plugin->slot = p_slot;
|
||||
plugin_list.push_back(p_plugin);
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin) {
|
||||
ERR_FAIL_COND(p_plugin.is_null());
|
||||
ERR_FAIL_COND(!plugin_list.has(p_plugin));
|
||||
|
||||
plugin_list.erase(p_plugin);
|
||||
}
|
||||
|
||||
bool EditorContextMenuPluginManager::has_plugins_for_slot(ContextMenuSlot p_slot) {
|
||||
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
|
||||
if (plugin->slot == p_slot) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
|
||||
bool separator_added = false;
|
||||
const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
|
||||
int id = EditorContextMenuPlugin::BASE_ID;
|
||||
|
||||
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
|
||||
if (plugin->slot != p_slot) {
|
||||
continue;
|
||||
}
|
||||
plugin->context_menu_items.clear();
|
||||
plugin->get_options(p_paths);
|
||||
|
||||
HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
|
||||
if (items.size() > 0 && !separator_added) {
|
||||
separator_added = true;
|
||||
p_popup->add_separator();
|
||||
}
|
||||
|
||||
for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
|
||||
EditorContextMenuPlugin::ContextMenuItem &item = E.value;
|
||||
item.id = id;
|
||||
|
||||
if (item.submenu) {
|
||||
p_popup->add_submenu_node_item(item.item_name, item.submenu, id);
|
||||
} else {
|
||||
p_popup->add_item(item.item_name, id);
|
||||
}
|
||||
|
||||
if (item.icon.is_valid()) {
|
||||
p_popup->set_item_icon(-1, item.icon);
|
||||
p_popup->set_item_icon_max_width(-1, icon_size);
|
||||
}
|
||||
|
||||
if (item.shortcut.is_valid()) {
|
||||
p_popup->set_item_shortcut(-1, item.shortcut, true);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Callable EditorContextMenuPluginManager::match_custom_shortcut(EditorContextMenuPlugin::ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
|
||||
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
|
||||
if (plugin->slot != p_slot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
|
||||
if (E.key->matches_event(p_event)) {
|
||||
return E.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Callable();
|
||||
}
|
||||
|
||||
bool EditorContextMenuPluginManager::activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg) {
|
||||
for (Ref<EditorContextMenuPlugin> &plugin : plugin_list) {
|
||||
if (plugin->slot != p_slot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : plugin->context_menu_items) {
|
||||
if (E.value.id == p_option) {
|
||||
invoke_callback(E.value.callable, p_arg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::invoke_callback(const Callable &p_callback, const Variant &p_arg) {
|
||||
const Variant *argptr = &p_arg;
|
||||
Callable::CallError ce;
|
||||
Variant result;
|
||||
p_callback.callp(&argptr, 1, result, ce);
|
||||
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_FAIL_MSG("Failed to execute context menu callback: " + Variant::get_callable_error_text(p_callback, &argptr, 1, ce) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::create() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = memnew(EditorContextMenuPluginManager);
|
||||
}
|
||||
|
||||
void EditorContextMenuPluginManager::cleanup() {
|
||||
ERR_FAIL_NULL(singleton);
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
114
editor/inspector/editor_context_menu_plugin.h
Normal file
114
editor/inspector/editor_context_menu_plugin.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/**************************************************************************/
|
||||
/* editor_context_menu_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class InputEvent;
|
||||
class PopupMenu;
|
||||
class Shortcut;
|
||||
class Texture2D;
|
||||
|
||||
class EditorContextMenuPlugin : public RefCounted {
|
||||
GDCLASS(EditorContextMenuPlugin, RefCounted);
|
||||
|
||||
friend class EditorContextMenuPluginManager;
|
||||
|
||||
static constexpr int MAX_ITEMS = 100;
|
||||
|
||||
public:
|
||||
enum ContextMenuSlot {
|
||||
CONTEXT_SLOT_SCENE_TREE,
|
||||
CONTEXT_SLOT_FILESYSTEM,
|
||||
CONTEXT_SLOT_SCRIPT_EDITOR,
|
||||
CONTEXT_SLOT_FILESYSTEM_CREATE,
|
||||
CONTEXT_SLOT_SCRIPT_EDITOR_CODE,
|
||||
CONTEXT_SLOT_SCENE_TABS,
|
||||
CONTEXT_SLOT_2D_EDITOR,
|
||||
};
|
||||
static constexpr int BASE_ID = 2000;
|
||||
|
||||
private:
|
||||
int slot = -1;
|
||||
|
||||
public:
|
||||
struct ContextMenuItem {
|
||||
int id = 0;
|
||||
String item_name;
|
||||
Callable callable;
|
||||
Ref<Texture2D> icon;
|
||||
Ref<Shortcut> shortcut;
|
||||
PopupMenu *submenu = nullptr;
|
||||
};
|
||||
HashMap<String, ContextMenuItem> context_menu_items;
|
||||
HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL1(_popup_menu, Vector<String>);
|
||||
|
||||
public:
|
||||
virtual void get_options(const Vector<String> &p_paths);
|
||||
|
||||
void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
|
||||
void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture);
|
||||
void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture);
|
||||
void add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture);
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot);
|
||||
|
||||
class EditorContextMenuPluginManager : public Object {
|
||||
GDCLASS(EditorContextMenuPluginManager, Object);
|
||||
|
||||
using ContextMenuSlot = EditorContextMenuPlugin::ContextMenuSlot;
|
||||
static inline EditorContextMenuPluginManager *singleton = nullptr;
|
||||
|
||||
LocalVector<Ref<EditorContextMenuPlugin>> plugin_list;
|
||||
|
||||
public:
|
||||
static EditorContextMenuPluginManager *get_singleton() { return singleton; }
|
||||
|
||||
void add_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
|
||||
void remove_plugin(const Ref<EditorContextMenuPlugin> &p_plugin);
|
||||
|
||||
bool has_plugins_for_slot(ContextMenuSlot p_slot);
|
||||
void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
|
||||
Callable match_custom_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
|
||||
bool activate_custom_option(ContextMenuSlot p_slot, int p_option, const Variant &p_arg);
|
||||
|
||||
void invoke_callback(const Callable &p_callback, const Variant &p_arg);
|
||||
|
||||
static void create();
|
||||
static void cleanup();
|
||||
};
|
||||
41
editor/inspector/editor_inspector.compat.inc
Normal file
41
editor/inspector/editor_inspector.compat.inc
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* editor_inspector.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void EditorInspectorPlugin::_add_property_editor_bind_compat_92322(const String &p_for_property, Control *p_prop, bool p_add_to_end) {
|
||||
add_property_editor(p_for_property, p_prop, p_add_to_end, "");
|
||||
}
|
||||
|
||||
void EditorInspectorPlugin::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end"), &EditorInspectorPlugin::_add_property_editor_bind_compat_92322, DEFVAL(false));
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
5749
editor/inspector/editor_inspector.cpp
Normal file
5749
editor/inspector/editor_inspector.cpp
Normal file
File diff suppressed because it is too large
Load Diff
846
editor/inspector/editor_inspector.h
Normal file
846
editor/inspector/editor_inspector.h
Normal file
@@ -0,0 +1,846 @@
|
||||
/**************************************************************************/
|
||||
/* editor_inspector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_property_name_processor.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
|
||||
class AddMetadataDialog;
|
||||
class AcceptDialog;
|
||||
class ConfirmationDialog;
|
||||
class EditorInspector;
|
||||
class EditorValidationPanel;
|
||||
class HSeparator;
|
||||
class LineEdit;
|
||||
class MarginContainer;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
class PopupMenu;
|
||||
class SpinBox;
|
||||
class StyleBoxFlat;
|
||||
class TextureRect;
|
||||
|
||||
class EditorPropertyRevert {
|
||||
public:
|
||||
static Variant get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid);
|
||||
static bool can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value = nullptr);
|
||||
};
|
||||
|
||||
class EditorInspectorActionButton : public Button {
|
||||
GDCLASS(EditorInspectorActionButton, Button);
|
||||
|
||||
StringName icon_name;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
EditorInspectorActionButton(const String &p_text, const StringName &p_icon_name);
|
||||
};
|
||||
|
||||
class EditorProperty : public Container {
|
||||
GDCLASS(EditorProperty, Container);
|
||||
|
||||
public:
|
||||
enum MenuItems {
|
||||
MENU_COPY_VALUE,
|
||||
MENU_PASTE_VALUE,
|
||||
MENU_COPY_PROPERTY_PATH,
|
||||
MENU_OVERRIDE_FOR_PROJECT,
|
||||
MENU_FAVORITE_PROPERTY,
|
||||
MENU_PIN_VALUE,
|
||||
MENU_DELETE,
|
||||
MENU_REVERT_VALUE,
|
||||
MENU_OPEN_DOCUMENTATION,
|
||||
};
|
||||
|
||||
enum ColorationMode {
|
||||
COLORATION_CONTAINER_RESOURCE,
|
||||
COLORATION_RESOURCE,
|
||||
COLORATION_EXTERNAL,
|
||||
};
|
||||
|
||||
private:
|
||||
String label;
|
||||
int text_size;
|
||||
friend class EditorInspector;
|
||||
Object *object = nullptr;
|
||||
StringName property;
|
||||
String property_path;
|
||||
String doc_path;
|
||||
bool internal = false;
|
||||
bool has_doc_tooltip = false;
|
||||
|
||||
int property_usage;
|
||||
|
||||
bool draw_label = true;
|
||||
bool draw_background = true;
|
||||
bool read_only = false;
|
||||
bool checkable = false;
|
||||
bool checked = false;
|
||||
bool draw_warning = false;
|
||||
bool draw_prop_warning = false;
|
||||
bool keying = false;
|
||||
bool deletable = false;
|
||||
|
||||
Rect2 right_child_rect;
|
||||
Rect2 bottom_child_rect;
|
||||
|
||||
Rect2 keying_rect;
|
||||
bool keying_hover = false;
|
||||
Rect2 revert_rect;
|
||||
bool revert_hover = false;
|
||||
Rect2 check_rect;
|
||||
bool check_hover = false;
|
||||
Rect2 delete_rect;
|
||||
bool delete_hover = false;
|
||||
|
||||
bool can_revert = false;
|
||||
bool can_pin = false;
|
||||
bool pin_hidden = false;
|
||||
bool pinned = false;
|
||||
|
||||
bool can_favorite = false;
|
||||
bool favorited = false;
|
||||
|
||||
bool use_folding = false;
|
||||
bool draw_top_bg = true;
|
||||
|
||||
void _update_popup();
|
||||
void _focusable_focused(int p_index);
|
||||
|
||||
bool selectable = true;
|
||||
bool selected = false;
|
||||
int selected_focusable;
|
||||
|
||||
float split_ratio;
|
||||
|
||||
Vector<Control *> focusables;
|
||||
Control *label_reference = nullptr;
|
||||
Control *bottom_editor = nullptr;
|
||||
PopupMenu *menu = nullptr;
|
||||
|
||||
HashMap<StringName, Variant> cache;
|
||||
|
||||
GDVIRTUAL0(_update_property)
|
||||
GDVIRTUAL1(_set_read_only, bool)
|
||||
|
||||
void _update_flags();
|
||||
|
||||
protected:
|
||||
bool has_borders = false;
|
||||
bool can_override = false;
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
virtual void _set_read_only(bool p_read_only);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
const Color *_get_property_colors();
|
||||
|
||||
virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const;
|
||||
virtual StringName _get_revert_property() const;
|
||||
|
||||
void _update_property_bg();
|
||||
|
||||
void _accessibility_action_menu(const Variant &p_data);
|
||||
void _accessibility_action_click(const Variant &p_data);
|
||||
|
||||
public:
|
||||
void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false);
|
||||
|
||||
String get_tooltip_string(const String &p_string) const;
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
void set_label(const String &p_label);
|
||||
String get_label() const;
|
||||
|
||||
void set_read_only(bool p_read_only);
|
||||
bool is_read_only() const;
|
||||
|
||||
void set_draw_label(bool p_draw_label);
|
||||
bool is_draw_label() const;
|
||||
|
||||
void set_draw_background(bool p_draw_background);
|
||||
bool is_draw_background() const;
|
||||
|
||||
Object *get_edited_object();
|
||||
StringName get_edited_property() const;
|
||||
inline Variant get_edited_property_value() const {
|
||||
ERR_FAIL_NULL_V(object, Variant());
|
||||
return object->get(property);
|
||||
}
|
||||
Variant get_edited_property_display_value() const;
|
||||
EditorInspector *get_parent_inspector() const;
|
||||
|
||||
void set_doc_path(const String &p_doc_path);
|
||||
void set_internal(bool p_internal);
|
||||
|
||||
virtual void update_property();
|
||||
void update_editor_property_status();
|
||||
|
||||
virtual bool use_keying_next() const;
|
||||
|
||||
void set_checkable(bool p_checkable);
|
||||
bool is_checkable() const;
|
||||
|
||||
void set_checked(bool p_checked);
|
||||
bool is_checked() const;
|
||||
|
||||
void set_draw_warning(bool p_draw_warning);
|
||||
bool is_draw_warning() const;
|
||||
|
||||
void set_keying(bool p_keying);
|
||||
bool is_keying() const;
|
||||
|
||||
virtual bool is_colored(ColorationMode p_mode) { return false; }
|
||||
|
||||
void set_deletable(bool p_enable);
|
||||
bool is_deletable() const;
|
||||
void add_focusable(Control *p_control);
|
||||
void grab_focus(int p_focusable = -1);
|
||||
void select(int p_focusable = -1);
|
||||
void deselect();
|
||||
bool is_selected() const;
|
||||
|
||||
void set_label_reference(Control *p_control);
|
||||
void set_bottom_editor(Control *p_control);
|
||||
|
||||
void set_use_folding(bool p_use_folding);
|
||||
bool is_using_folding() const;
|
||||
|
||||
virtual void expand_all_folding();
|
||||
virtual void collapse_all_folding();
|
||||
virtual void expand_revertable();
|
||||
|
||||
virtual Variant get_drag_data(const Point2 &p_point) override;
|
||||
virtual void update_cache();
|
||||
virtual bool is_cache_valid() const;
|
||||
|
||||
void set_selectable(bool p_selectable);
|
||||
bool is_selectable() const;
|
||||
|
||||
void set_name_split_ratio(float p_ratio);
|
||||
float get_name_split_ratio() const;
|
||||
|
||||
void set_favoritable(bool p_favoritable);
|
||||
bool is_favoritable() const;
|
||||
|
||||
void set_object_and_property(Object *p_object, const StringName &p_property);
|
||||
virtual Control *make_custom_tooltip(const String &p_text) const override;
|
||||
|
||||
void set_draw_top_bg(bool p_draw) { draw_top_bg = p_draw; }
|
||||
|
||||
bool can_revert_to_default() const { return can_revert; }
|
||||
|
||||
void menu_option(int p_option);
|
||||
|
||||
EditorProperty();
|
||||
};
|
||||
|
||||
class EditorInspectorPlugin : public RefCounted {
|
||||
GDCLASS(EditorInspectorPlugin, RefCounted);
|
||||
|
||||
public:
|
||||
friend class EditorInspector;
|
||||
struct AddedEditor {
|
||||
Control *property_editor = nullptr;
|
||||
Vector<String> properties;
|
||||
String label;
|
||||
bool add_to_end = false;
|
||||
};
|
||||
|
||||
List<AddedEditor> added_editors;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL1RC(bool, _can_handle, Object *)
|
||||
GDVIRTUAL1(_parse_begin, Object *)
|
||||
GDVIRTUAL2(_parse_category, Object *, String)
|
||||
GDVIRTUAL2(_parse_group, Object *, String)
|
||||
GDVIRTUAL7R(bool, _parse_property, Object *, Variant::Type, String, PropertyHint, String, BitField<PropertyUsageFlags>, bool)
|
||||
GDVIRTUAL1(_parse_end, Object *)
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _add_property_editor_bind_compat_92322(const String &p_for_property, Control *p_prop, bool p_add_to_end);
|
||||
static void _bind_compatibility_methods();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
public:
|
||||
void add_custom_control(Control *control);
|
||||
void add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end = false, const String &p_label = String());
|
||||
void add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop);
|
||||
|
||||
virtual bool can_handle(Object *p_object);
|
||||
virtual void parse_begin(Object *p_object);
|
||||
virtual void parse_category(Object *p_object, const String &p_category);
|
||||
virtual void parse_group(Object *p_object, const String &p_group);
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false);
|
||||
virtual void parse_end(Object *p_object);
|
||||
};
|
||||
|
||||
class EditorInspectorCategory : public Control {
|
||||
GDCLASS(EditorInspectorCategory, Control);
|
||||
|
||||
friend class EditorInspector;
|
||||
|
||||
// Right-click context menu options.
|
||||
enum ClassMenuOption {
|
||||
MENU_OPEN_DOCS,
|
||||
MENU_UNFAVORITE_ALL,
|
||||
};
|
||||
|
||||
struct ThemeCache {
|
||||
int horizontal_separation = 0;
|
||||
int vertical_separation = 0;
|
||||
int class_icon_size = 0;
|
||||
|
||||
Color font_color;
|
||||
|
||||
Ref<Font> bold_font;
|
||||
int bold_font_size = 0;
|
||||
|
||||
Ref<Texture2D> icon_favorites;
|
||||
Ref<Texture2D> icon_unfavorite;
|
||||
Ref<Texture2D> icon_help;
|
||||
|
||||
Ref<StyleBox> background;
|
||||
} theme_cache;
|
||||
|
||||
PropertyInfo info;
|
||||
|
||||
Ref<Texture2D> icon;
|
||||
String label;
|
||||
String doc_class_name;
|
||||
PopupMenu *menu = nullptr;
|
||||
bool is_favorite = false;
|
||||
bool menu_icon_dirty = true;
|
||||
|
||||
void _handle_menu_option(int p_option);
|
||||
void _popup_context_menu(const Point2i &p_position);
|
||||
void _update_icon();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
void _notification(int p_what);
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
void _accessibility_action_menu(const Variant &p_data);
|
||||
|
||||
public:
|
||||
void set_as_favorite();
|
||||
void set_property_info(const PropertyInfo &p_info);
|
||||
void set_doc_class_name(const String &p_name);
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
virtual Control *make_custom_tooltip(const String &p_text) const override;
|
||||
|
||||
EditorInspectorCategory();
|
||||
};
|
||||
|
||||
class EditorInspectorSection : public Container {
|
||||
GDCLASS(EditorInspectorSection, Container);
|
||||
|
||||
friend class EditorInspector;
|
||||
|
||||
String label;
|
||||
String section;
|
||||
Color bg_color;
|
||||
bool vbox_added = false; // Optimization.
|
||||
bool foldable = false;
|
||||
bool checkable = false;
|
||||
bool checked = false;
|
||||
bool keying = false;
|
||||
int indent_depth = 0;
|
||||
int level = 1;
|
||||
String related_enable_property;
|
||||
|
||||
Timer *dropping_unfold_timer = nullptr;
|
||||
bool dropping_for_unfold = false;
|
||||
|
||||
Rect2 check_rect;
|
||||
bool check_hover = false;
|
||||
Rect2 keying_rect;
|
||||
bool keying_hover = false;
|
||||
bool header_hover = false;
|
||||
|
||||
bool checkbox_only = false;
|
||||
|
||||
HashSet<StringName> revertable_properties;
|
||||
|
||||
void _test_unfold();
|
||||
int _get_header_height();
|
||||
Ref<Texture2D> _get_arrow();
|
||||
Ref<Texture2D> _get_checkbox();
|
||||
|
||||
EditorInspector *_get_parent_inspector() const;
|
||||
|
||||
struct ThemeCache {
|
||||
int horizontal_separation = 0;
|
||||
int vertical_separation = 0;
|
||||
int inspector_margin = 0;
|
||||
int indent_size = 0;
|
||||
|
||||
Color warning_color;
|
||||
Color prop_subsection;
|
||||
Color font_color;
|
||||
Color font_disabled_color;
|
||||
Color font_hover_color;
|
||||
Color font_pressed_color;
|
||||
Color font_hover_pressed_color;
|
||||
|
||||
Ref<Font> font;
|
||||
int font_size = 0;
|
||||
Ref<Font> bold_font;
|
||||
int bold_font_size = 0;
|
||||
Ref<Font> light_font;
|
||||
int light_font_size = 0;
|
||||
|
||||
Ref<Texture2D> arrow;
|
||||
Ref<Texture2D> arrow_collapsed;
|
||||
Ref<Texture2D> arrow_collapsed_mirrored;
|
||||
Ref<Texture2D> icon_gui_checked;
|
||||
Ref<Texture2D> icon_gui_unchecked;
|
||||
Ref<Texture2D> icon_gui_animation_key;
|
||||
|
||||
Ref<StyleBoxFlat> indent_box;
|
||||
Ref<StyleBoxFlat> key_hover;
|
||||
} theme_cache;
|
||||
|
||||
protected:
|
||||
Object *object = nullptr;
|
||||
VBoxContainer *vbox = nullptr;
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
void _accessibility_action_collapse(const Variant &p_data);
|
||||
void _accessibility_action_expand(const Variant &p_data);
|
||||
|
||||
public:
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
virtual Control *make_custom_tooltip(const String &p_text) const override;
|
||||
|
||||
void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0, int p_level = 1);
|
||||
String get_section() const;
|
||||
String get_label() const { return label; }
|
||||
VBoxContainer *get_vbox();
|
||||
void unfold();
|
||||
void fold();
|
||||
void set_bg_color(const Color &p_bg_color);
|
||||
void reset_timer();
|
||||
void set_checkable(const String &p_related_check_property, bool p_checkbox_only, bool p_checked);
|
||||
inline bool is_checkable() const { return checkable; }
|
||||
void set_checked(bool p_checked);
|
||||
void set_keying(bool p_keying);
|
||||
|
||||
bool has_revertable_properties() const;
|
||||
void property_can_revert_changed(const String &p_path, bool p_can_revert);
|
||||
void _property_edited(const String &p_property);
|
||||
void update_property();
|
||||
|
||||
EditorInspectorSection();
|
||||
~EditorInspectorSection();
|
||||
};
|
||||
|
||||
class ArrayPanelContainer : public PanelContainer {
|
||||
GDCLASS(ArrayPanelContainer, PanelContainer);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
void _accessibility_action_menu(const Variant &p_data);
|
||||
|
||||
public:
|
||||
ArrayPanelContainer();
|
||||
};
|
||||
|
||||
class EditorInspectorArray : public EditorInspectorSection {
|
||||
GDCLASS(EditorInspectorArray, EditorInspectorSection);
|
||||
|
||||
enum Mode {
|
||||
MODE_NONE,
|
||||
MODE_USE_COUNT_PROPERTY,
|
||||
MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION,
|
||||
} mode = MODE_NONE;
|
||||
StringName count_property;
|
||||
StringName array_element_prefix;
|
||||
String swap_method;
|
||||
|
||||
int count = 0;
|
||||
int selected = -1;
|
||||
|
||||
VBoxContainer *elements_vbox = nullptr;
|
||||
|
||||
Control *control_dropping = nullptr;
|
||||
bool dropping = false;
|
||||
|
||||
Button *add_button = nullptr;
|
||||
|
||||
AcceptDialog *resize_dialog = nullptr;
|
||||
SpinBox *new_size_spin_box = nullptr;
|
||||
|
||||
// Pagination.
|
||||
int page_length = 5;
|
||||
int page = 0;
|
||||
int max_page = 0;
|
||||
int begin_array_index = 0;
|
||||
int end_array_index = 0;
|
||||
|
||||
bool read_only = false;
|
||||
bool movable = true;
|
||||
bool is_const = false;
|
||||
bool numbered = false;
|
||||
|
||||
enum MenuOptions {
|
||||
OPTION_MOVE_UP = 0,
|
||||
OPTION_MOVE_DOWN,
|
||||
OPTION_NEW_BEFORE,
|
||||
OPTION_NEW_AFTER,
|
||||
OPTION_REMOVE,
|
||||
OPTION_CLEAR_ARRAY,
|
||||
OPTION_RESIZE_ARRAY,
|
||||
};
|
||||
int popup_array_index_pressed = -1;
|
||||
PopupMenu *rmb_popup = nullptr;
|
||||
|
||||
struct ArrayElement {
|
||||
PanelContainer *panel = nullptr;
|
||||
MarginContainer *margin = nullptr;
|
||||
HBoxContainer *hbox = nullptr;
|
||||
Button *move_up = nullptr;
|
||||
TextureRect *move_texture_rect = nullptr;
|
||||
Button *move_down = nullptr;
|
||||
Label *number = nullptr;
|
||||
VBoxContainer *vbox = nullptr;
|
||||
Button *erase = nullptr;
|
||||
};
|
||||
LocalVector<ArrayElement> array_elements;
|
||||
|
||||
Ref<StyleBoxFlat> odd_style;
|
||||
Ref<StyleBoxFlat> even_style;
|
||||
|
||||
int _get_array_count();
|
||||
void _add_button_pressed();
|
||||
void _paginator_page_changed(int p_page);
|
||||
|
||||
void _rmb_popup_id_pressed(int p_id);
|
||||
|
||||
void _control_dropping_draw();
|
||||
|
||||
void _vbox_visibility_changed();
|
||||
|
||||
void _panel_draw(int p_index);
|
||||
void _panel_gui_input(Ref<InputEvent> p_event, int p_index);
|
||||
void _panel_gui_focus(int p_index);
|
||||
void _panel_gui_unfocus(int p_index);
|
||||
void _move_element(int p_element_index, int p_to_pos);
|
||||
void _clear_array();
|
||||
void _resize_array(int p_size);
|
||||
Array _extract_properties_as_array(const List<PropertyInfo> &p_list);
|
||||
int _drop_position() const;
|
||||
|
||||
void _new_size_spin_box_value_changed(float p_value);
|
||||
void _new_size_spin_box_text_submitted(const String &p_text);
|
||||
void _resize_dialog_confirmed();
|
||||
|
||||
void _update_elements_visibility();
|
||||
void _setup();
|
||||
|
||||
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
|
||||
void _remove_item(int p_index);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "");
|
||||
void setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = "");
|
||||
VBoxContainer *get_vbox(int p_index);
|
||||
|
||||
void show_menu(int p_index, const Vector2 &p_offset);
|
||||
|
||||
EditorInspectorArray(bool p_read_only);
|
||||
};
|
||||
|
||||
class EditorPaginator : public HBoxContainer {
|
||||
GDCLASS(EditorPaginator, HBoxContainer);
|
||||
|
||||
int page = 0;
|
||||
int max_page = 0;
|
||||
Button *first_page_button = nullptr;
|
||||
Button *prev_page_button = nullptr;
|
||||
LineEdit *page_line_edit = nullptr;
|
||||
Label *page_count_label = nullptr;
|
||||
Button *next_page_button = nullptr;
|
||||
Button *last_page_button = nullptr;
|
||||
|
||||
void _first_page_button_pressed();
|
||||
void _prev_page_button_pressed();
|
||||
void _page_line_edit_text_submitted(const String &p_text);
|
||||
void _next_page_button_pressed();
|
||||
void _last_page_button_pressed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void update(int p_page, int p_max_page);
|
||||
|
||||
EditorPaginator();
|
||||
};
|
||||
|
||||
class EditorInspector : public ScrollContainer {
|
||||
GDCLASS(EditorInspector, ScrollContainer);
|
||||
|
||||
friend class EditorPropertyResource;
|
||||
|
||||
enum {
|
||||
MAX_PLUGINS = 1024
|
||||
};
|
||||
static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
|
||||
static int inspector_plugin_count;
|
||||
|
||||
struct ThemeCache {
|
||||
int vertical_separation = 0;
|
||||
Color prop_subsection;
|
||||
Ref<Texture2D> icon_add;
|
||||
} theme_cache;
|
||||
|
||||
EditorInspectorSection::ThemeCache section_theme_cache;
|
||||
EditorInspectorCategory::ThemeCache category_theme_cache;
|
||||
|
||||
bool can_favorite = false;
|
||||
PackedStringArray current_favorites;
|
||||
VBoxContainer *favorites_section = nullptr;
|
||||
VBoxContainer *favorites_vbox = nullptr;
|
||||
VBoxContainer *favorites_groups_vbox = nullptr;
|
||||
HSeparator *favorites_separator = nullptr;
|
||||
|
||||
EditorInspector *root_inspector = nullptr;
|
||||
|
||||
VBoxContainer *base_vbox = nullptr;
|
||||
VBoxContainer *begin_vbox = nullptr;
|
||||
VBoxContainer *main_vbox = nullptr;
|
||||
|
||||
// Map used to cache the instantiated editors.
|
||||
HashMap<StringName, List<EditorProperty *>> editor_property_map;
|
||||
List<EditorInspectorSection *> sections;
|
||||
HashSet<StringName> pending;
|
||||
|
||||
void _clear(bool p_hide_plugins = true);
|
||||
Object *object = nullptr;
|
||||
Object *next_object = nullptr;
|
||||
|
||||
//
|
||||
|
||||
LineEdit *search_box = nullptr;
|
||||
bool show_standard_categories = false;
|
||||
bool show_custom_categories = false;
|
||||
bool hide_script = true;
|
||||
bool hide_metadata = true;
|
||||
bool use_doc_hints = false;
|
||||
EditorPropertyNameProcessor::Style property_name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED;
|
||||
bool use_settings_name_style = true;
|
||||
bool use_filter = false;
|
||||
bool autoclear = false;
|
||||
bool use_folding = false;
|
||||
int changing;
|
||||
bool update_all_pending = false;
|
||||
bool read_only = false;
|
||||
bool keying = false;
|
||||
bool wide_editors = false;
|
||||
bool deletable_properties = false;
|
||||
bool mark_unsaved = true;
|
||||
|
||||
float refresh_countdown;
|
||||
bool update_tree_pending = false;
|
||||
StringName _prop_edited;
|
||||
StringName property_selected;
|
||||
int property_focusable;
|
||||
int update_scroll_request;
|
||||
|
||||
bool updating_theme = false;
|
||||
|
||||
struct DocCacheInfo {
|
||||
String doc_path;
|
||||
String theme_item_name;
|
||||
};
|
||||
|
||||
HashMap<StringName, HashMap<StringName, DocCacheInfo>> doc_cache;
|
||||
HashSet<StringName> restart_request_props;
|
||||
HashMap<String, String> custom_property_descriptions;
|
||||
|
||||
HashMap<ObjectID, int> scroll_cache;
|
||||
|
||||
String property_prefix; // Used for sectioned inspector.
|
||||
String object_class;
|
||||
Variant property_clipboard;
|
||||
|
||||
bool restrict_to_basic = false;
|
||||
|
||||
void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field);
|
||||
|
||||
void _property_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool p_changing = false, bool p_update_all = false);
|
||||
void _multiple_properties_changed(const Vector<String> &p_paths, const Array &p_values, bool p_changing = false);
|
||||
void _property_keyed(const String &p_path, bool p_advance);
|
||||
void _property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance);
|
||||
void _property_deleted(const String &p_path);
|
||||
void _property_checked(const String &p_path, bool p_checked);
|
||||
void _property_pinned(const String &p_path, bool p_pinned);
|
||||
bool _property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style);
|
||||
bool _resource_properties_matches(const Ref<Resource> &p_resource, const String &p_filter);
|
||||
|
||||
void _resource_selected(const String &p_path, Ref<Resource> p_resource);
|
||||
void _property_selected(const String &p_path, int p_focusable);
|
||||
void _object_id_selected(const String &p_path, ObjectID p_id);
|
||||
|
||||
void _update_current_favorites();
|
||||
void _set_property_favorited(const String &p_path, bool p_favorited);
|
||||
void _clear_current_favorites();
|
||||
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
HashMap<StringName, int> per_array_page;
|
||||
void _page_change_request(int p_new_page, const StringName &p_array_prefix);
|
||||
|
||||
void _changed_callback();
|
||||
void _edit_request_change(Object *p_object, const String &p_prop);
|
||||
|
||||
void _keying_changed();
|
||||
|
||||
void _parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped);
|
||||
|
||||
void _vscroll_changed(double);
|
||||
|
||||
void _feature_profile_changed();
|
||||
|
||||
bool _is_property_disabled_by_feature_profile(const StringName &p_property);
|
||||
|
||||
void _section_toggled_by_user(const String &p_path, bool p_value);
|
||||
|
||||
AddMetadataDialog *add_meta_dialog = nullptr;
|
||||
LineEdit *add_meta_name = nullptr;
|
||||
OptionButton *add_meta_type = nullptr;
|
||||
EditorValidationPanel *validation_panel = nullptr;
|
||||
|
||||
void _add_meta_confirm();
|
||||
void _show_add_meta_dialog();
|
||||
|
||||
static EditorInspector *_get_control_parent_inspector(Control *p_control);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static void add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin);
|
||||
static void remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin);
|
||||
static void cleanup_plugins();
|
||||
|
||||
static EditorProperty *instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false);
|
||||
|
||||
static void initialize_section_theme(EditorInspectorSection::ThemeCache &p_cache, Control *p_control);
|
||||
static void initialize_category_theme(EditorInspectorCategory::ThemeCache &p_cache, Control *p_control);
|
||||
|
||||
bool is_main_editor_inspector() const;
|
||||
String get_selected_path() const;
|
||||
|
||||
void update_tree();
|
||||
void update_property(const String &p_prop);
|
||||
void edit(Object *p_object);
|
||||
Object *get_edited_object();
|
||||
Object *get_next_edited_object();
|
||||
|
||||
void set_keying(bool p_active);
|
||||
void set_read_only(bool p_read_only);
|
||||
void set_mark_unsaved(bool p_mark) { mark_unsaved = p_mark; }
|
||||
|
||||
EditorPropertyNameProcessor::Style get_property_name_style() const;
|
||||
void set_property_name_style(EditorPropertyNameProcessor::Style p_style);
|
||||
|
||||
// If true, the inspector will update its property name style according to the current editor settings.
|
||||
void set_use_settings_name_style(bool p_enable);
|
||||
|
||||
void set_autoclear(bool p_enable);
|
||||
|
||||
void set_show_categories(bool p_show_standard, bool p_show_custom);
|
||||
void set_use_doc_hints(bool p_enable);
|
||||
void set_hide_script(bool p_hide);
|
||||
void set_hide_metadata(bool p_hide);
|
||||
|
||||
void set_use_filter(bool p_use);
|
||||
void register_text_enter(Node *p_line_edit);
|
||||
|
||||
void set_use_folding(bool p_use_folding, bool p_update_tree = true);
|
||||
bool is_using_folding();
|
||||
|
||||
void collapse_all_folding();
|
||||
void expand_all_folding();
|
||||
void expand_revertable();
|
||||
|
||||
void set_scroll_offset(int p_offset);
|
||||
int get_scroll_offset() const;
|
||||
|
||||
void set_property_prefix(const String &p_prefix);
|
||||
String get_property_prefix() const;
|
||||
|
||||
void add_custom_property_description(const String &p_class, const String &p_property, const String &p_description);
|
||||
String get_custom_property_description(const String &p_property) const;
|
||||
|
||||
void set_object_class(const String &p_class);
|
||||
String get_object_class() const;
|
||||
|
||||
void set_use_wide_editors(bool p_enable);
|
||||
void set_root_inspector(EditorInspector *p_root_inspector);
|
||||
EditorInspector *get_root_inspector() { return is_sub_inspector() ? root_inspector : this; }
|
||||
bool is_sub_inspector() const { return root_inspector != nullptr; }
|
||||
|
||||
void set_use_deletable_properties(bool p_enabled);
|
||||
|
||||
void set_restrict_to_basic_settings(bool p_restrict);
|
||||
void set_property_clipboard(const Variant &p_value);
|
||||
Variant get_property_clipboard() const;
|
||||
|
||||
EditorInspector();
|
||||
};
|
||||
933
editor/inspector/editor_preview_plugins.cpp
Normal file
933
editor/inspector/editor_preview_plugins.cpp
Normal file
@@ -0,0 +1,933 @@
|
||||
/**************************************************************************/
|
||||
/* editor_preview_plugins.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_preview_plugins.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/image.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "editor/file_system/editor_paths.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/resources/atlas_texture.h"
|
||||
#include "scene/resources/bit_map.h"
|
||||
#include "scene/resources/font.h"
|
||||
#include "scene/resources/gradient_texture.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/material.h"
|
||||
#include "scene/resources/mesh.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
|
||||
void post_process_preview(Ref<Image> p_image) {
|
||||
if (p_image->get_format() != Image::FORMAT_RGBA8) {
|
||||
p_image->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
const int w = p_image->get_width();
|
||||
const int h = p_image->get_height();
|
||||
|
||||
const int r = MIN(w, h) / 32;
|
||||
const int r2 = r * r;
|
||||
Color transparent = Color(0, 0, 0, 0);
|
||||
|
||||
for (int i = 0; i < r; i++) {
|
||||
for (int j = 0; j < r; j++) {
|
||||
int dx = i - r;
|
||||
int dy = j - r;
|
||||
if (dx * dx + dy * dy > r2) {
|
||||
p_image->set_pixel(i, j, transparent);
|
||||
p_image->set_pixel(w - 1 - i, j, transparent);
|
||||
p_image->set_pixel(w - 1 - i, h - 1 - j, transparent);
|
||||
p_image->set_pixel(i, h - 1 - j, transparent);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorTexturePreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Texture");
|
||||
}
|
||||
|
||||
bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Image> img;
|
||||
|
||||
Ref<AtlasTexture> tex_atlas = p_from;
|
||||
Ref<Texture3D> tex_3d = p_from;
|
||||
Ref<TextureLayered> tex_lyr = p_from;
|
||||
|
||||
if (tex_atlas.is_valid()) {
|
||||
Ref<Texture2D> tex = tex_atlas->get_atlas();
|
||||
if (tex.is_null()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
Ref<Image> atlas = tex->get_image();
|
||||
if (atlas.is_null()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
if (atlas->is_compressed()) {
|
||||
atlas = atlas->duplicate();
|
||||
if (atlas->decompress() != OK) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
}
|
||||
|
||||
if (!tex_atlas->get_region().has_area()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
img = atlas->get_region(tex_atlas->get_region());
|
||||
|
||||
} else if (tex_3d.is_valid()) {
|
||||
if (tex_3d->get_depth() == 0) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
Vector<Ref<Image>> data = tex_3d->get_data();
|
||||
if (data.size() != tex_3d->get_depth()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
// Use the middle slice for the thumbnail.
|
||||
const int mid_depth = (tex_3d->get_depth() - 1) / 2;
|
||||
if (!data.is_empty() && data[mid_depth].is_valid()) {
|
||||
img = data[mid_depth]->duplicate();
|
||||
}
|
||||
|
||||
} else if (tex_lyr.is_valid()) {
|
||||
if (tex_lyr->get_layers() == 0) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
// Use the middle slice for the thumbnail.
|
||||
const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
|
||||
|
||||
Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
|
||||
if (data.is_valid()) {
|
||||
img = data->duplicate();
|
||||
}
|
||||
|
||||
} else {
|
||||
Ref<Texture2D> tex = p_from;
|
||||
if (tex.is_valid()) {
|
||||
img = tex->get_image();
|
||||
if (img.is_valid()) {
|
||||
img = img->duplicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (img.is_null() || img->is_empty()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
p_metadata["dimensions"] = img->get_size();
|
||||
|
||||
img->clear_mipmaps();
|
||||
|
||||
if (img->is_compressed()) {
|
||||
if (img->decompress() != OK) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
} else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
Vector2 new_size = img->get_size();
|
||||
if (new_size.x > p_size.x) {
|
||||
new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
|
||||
}
|
||||
if (new_size.y > p_size.y) {
|
||||
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
|
||||
}
|
||||
Vector2i new_size_i = Vector2i(new_size).maxi(1);
|
||||
img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool EditorImagePreviewPlugin::handles(const String &p_type) const {
|
||||
return p_type == "Image";
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Image> img = p_from;
|
||||
|
||||
if (img.is_null() || img->is_empty()) {
|
||||
return Ref<Image>();
|
||||
}
|
||||
|
||||
img = img->duplicate();
|
||||
img->clear_mipmaps();
|
||||
|
||||
if (img->is_compressed()) {
|
||||
if (img->decompress() != OK) {
|
||||
return Ref<Image>();
|
||||
}
|
||||
} else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
Vector2 new_size = img->get_size();
|
||||
if (new_size.x > p_size.x) {
|
||||
new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
|
||||
}
|
||||
if (new_size.y > p_size.y) {
|
||||
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
|
||||
}
|
||||
img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
bool EditorImagePreviewPlugin::generate_small_preview_automatically() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool EditorBitmapPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "BitMap");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<BitMap> bm = p_from;
|
||||
|
||||
if (bm->get_size() == Size2()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
Vector<uint8_t> data;
|
||||
|
||||
data.resize(bm->get_size().width * bm->get_size().height);
|
||||
|
||||
{
|
||||
uint8_t *w = data.ptrw();
|
||||
|
||||
for (int i = 0; i < bm->get_size().width; i++) {
|
||||
for (int j = 0; j < bm->get_size().height; j++) {
|
||||
if (bm->get_bit(i, j)) {
|
||||
w[j * (int)bm->get_size().width + i] = 255;
|
||||
} else {
|
||||
w[j * (int)bm->get_size().width + i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> img = Image::create_from_data(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data);
|
||||
|
||||
if (img->is_compressed()) {
|
||||
if (img->decompress() != OK) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
} else if (img->get_format() != Image::FORMAT_RGB8 && img->get_format() != Image::FORMAT_RGBA8) {
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
Vector2 new_size = img->get_size();
|
||||
if (new_size.x > p_size.x) {
|
||||
new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
|
||||
}
|
||||
if (new_size.y > p_size.y) {
|
||||
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
|
||||
}
|
||||
img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
bool EditorBitmapPreviewPlugin::generate_small_preview_automatically() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "PackedScene");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
return generate_from_path(p_from->get_path(), p_size, p_metadata);
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
String temp_path = EditorPaths::get_singleton()->get_cache_dir();
|
||||
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
|
||||
cache_base = temp_path.path_join("resthumb-" + cache_base);
|
||||
|
||||
//does not have it, try to load a cached thumbnail
|
||||
|
||||
String path = cache_base + ".png";
|
||||
|
||||
if (!FileAccess::exists(path)) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
Ref<Image> img;
|
||||
img.instantiate();
|
||||
Error err = img->load(path);
|
||||
if (err == OK) {
|
||||
post_process_preview(img);
|
||||
return ImageTexture::create_from_image(img);
|
||||
|
||||
} else {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
void EditorMaterialPreviewPlugin::abort() {
|
||||
draw_requester.abort();
|
||||
}
|
||||
|
||||
bool EditorMaterialPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Material"); // Any material.
|
||||
}
|
||||
|
||||
bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Material> material = p_from;
|
||||
ERR_FAIL_COND_V(material.is_null(), Ref<Texture2D>());
|
||||
|
||||
if (material->get_shader_mode() == Shader::MODE_SPATIAL) {
|
||||
RS::get_singleton()->mesh_surface_set_material(sphere, 0, material->get_rid());
|
||||
|
||||
draw_requester.request_and_wait(viewport);
|
||||
|
||||
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
|
||||
RS::get_singleton()->mesh_surface_set_material(sphere, 0, RID());
|
||||
|
||||
ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
|
||||
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
int thumbnail_size = MAX(p_size.x, p_size.y);
|
||||
img->resize(thumbnail_size, thumbnail_size, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() {
|
||||
scenario = RS::get_singleton()->scenario_create();
|
||||
|
||||
viewport = RS::get_singleton()->viewport_create();
|
||||
RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
|
||||
RS::get_singleton()->viewport_set_scenario(viewport, scenario);
|
||||
RS::get_singleton()->viewport_set_size(viewport, 128, 128);
|
||||
RS::get_singleton()->viewport_set_transparent_background(viewport, true);
|
||||
RS::get_singleton()->viewport_set_active(viewport, true);
|
||||
viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
|
||||
|
||||
camera = RS::get_singleton()->camera_create();
|
||||
RS::get_singleton()->viewport_attach_camera(viewport, camera);
|
||||
RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
|
||||
RS::get_singleton()->camera_set_perspective(camera, 45, 0.1, 10);
|
||||
|
||||
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
|
||||
camera_attributes = RS::get_singleton()->camera_attributes_create();
|
||||
RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds.
|
||||
RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes);
|
||||
}
|
||||
|
||||
light = RS::get_singleton()->directional_light_create();
|
||||
light_instance = RS::get_singleton()->instance_create2(light, scenario);
|
||||
RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
|
||||
|
||||
light2 = RS::get_singleton()->directional_light_create();
|
||||
RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
|
||||
//RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
|
||||
|
||||
light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
|
||||
|
||||
RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
|
||||
|
||||
sphere = RS::get_singleton()->mesh_create();
|
||||
sphere_instance = RS::get_singleton()->instance_create2(sphere, scenario);
|
||||
|
||||
int lats = 32;
|
||||
int lons = 32;
|
||||
const double lat_step = Math::TAU / lats;
|
||||
const double lon_step = Math::TAU / lons;
|
||||
real_t radius = 1.0;
|
||||
|
||||
Vector<Vector3> vertices;
|
||||
Vector<Vector3> normals;
|
||||
Vector<Vector2> uvs;
|
||||
Vector<real_t> tangents;
|
||||
Basis tt = Basis(Vector3(0, 1, 0), Math::PI * 0.5);
|
||||
|
||||
for (int i = 1; i <= lats; i++) {
|
||||
double lat0 = lat_step * (i - 1) - Math::TAU / 4;
|
||||
double z0 = Math::sin(lat0);
|
||||
double zr0 = Math::cos(lat0);
|
||||
|
||||
double lat1 = lat_step * i - Math::TAU / 4;
|
||||
double z1 = Math::sin(lat1);
|
||||
double zr1 = Math::cos(lat1);
|
||||
|
||||
for (int j = lons; j >= 1; j--) {
|
||||
double lng0 = lon_step * (j - 1);
|
||||
double x0 = Math::cos(lng0);
|
||||
double y0 = Math::sin(lng0);
|
||||
|
||||
double lng1 = lon_step * j;
|
||||
double x1 = Math::cos(lng1);
|
||||
double y1 = Math::sin(lng1);
|
||||
|
||||
Vector3 v[4] = {
|
||||
Vector3(x1 * zr0, z0, y1 * zr0),
|
||||
Vector3(x1 * zr1, z1, y1 * zr1),
|
||||
Vector3(x0 * zr1, z1, y0 * zr1),
|
||||
Vector3(x0 * zr0, z0, y0 * zr0)
|
||||
};
|
||||
|
||||
#define ADD_POINT(m_idx) \
|
||||
normals.push_back(v[m_idx]); \
|
||||
vertices.push_back(v[m_idx] * radius); \
|
||||
{ \
|
||||
Vector2 uv(Math::atan2(v[m_idx].x, v[m_idx].z), Math::atan2(-v[m_idx].y, v[m_idx].z)); \
|
||||
uv /= Math::PI; \
|
||||
uv *= 4.0; \
|
||||
uv = uv * 0.5 + Vector2(0.5, 0.5); \
|
||||
uvs.push_back(uv); \
|
||||
} \
|
||||
{ \
|
||||
Vector3 t = tt.xform(v[m_idx]); \
|
||||
tangents.push_back(t.x); \
|
||||
tangents.push_back(t.y); \
|
||||
tangents.push_back(t.z); \
|
||||
tangents.push_back(1.0); \
|
||||
}
|
||||
|
||||
ADD_POINT(0);
|
||||
ADD_POINT(1);
|
||||
ADD_POINT(2);
|
||||
|
||||
ADD_POINT(2);
|
||||
ADD_POINT(3);
|
||||
ADD_POINT(0);
|
||||
}
|
||||
}
|
||||
|
||||
Array arr;
|
||||
arr.resize(RS::ARRAY_MAX);
|
||||
arr[RS::ARRAY_VERTEX] = vertices;
|
||||
arr[RS::ARRAY_NORMAL] = normals;
|
||||
arr[RS::ARRAY_TANGENT] = tangents;
|
||||
arr[RS::ARRAY_TEX_UV] = uvs;
|
||||
RS::get_singleton()->mesh_add_surface_from_arrays(sphere, RS::PRIMITIVE_TRIANGLES, arr);
|
||||
}
|
||||
|
||||
EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() {
|
||||
ERR_FAIL_NULL(RenderingServer::get_singleton());
|
||||
RS::get_singleton()->free(sphere);
|
||||
RS::get_singleton()->free(sphere_instance);
|
||||
RS::get_singleton()->free(viewport);
|
||||
RS::get_singleton()->free(light);
|
||||
RS::get_singleton()->free(light_instance);
|
||||
RS::get_singleton()->free(light2);
|
||||
RS::get_singleton()->free(light_instance2);
|
||||
RS::get_singleton()->free(camera);
|
||||
RS::get_singleton()->free(camera_attributes);
|
||||
RS::get_singleton()->free(scenario);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool EditorScriptPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Script");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorScriptPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Error err;
|
||||
String code = FileAccess::get_file_as_string(p_path, &err);
|
||||
if (err != OK) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
ScriptLanguage *lang = ScriptServer::get_language_for_extension(p_path.get_extension());
|
||||
return _generate_from_source_code(lang, code, p_size, p_metadata);
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Script> scr = p_from;
|
||||
if (scr.is_null()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
String code = scr->get_source_code().strip_edges();
|
||||
return _generate_from_source_code(scr->get_language(), code, p_size, p_metadata);
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorScriptPreviewPlugin::_generate_from_source_code(const ScriptLanguage *p_language, const String &p_source_code, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
if (p_source_code.is_empty()) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
|
||||
HashSet<String> control_flow_keywords;
|
||||
HashSet<String> keywords;
|
||||
|
||||
if (p_language) {
|
||||
for (const String &keyword : p_language->get_reserved_words()) {
|
||||
if (p_language->is_control_flow_keyword(keyword)) {
|
||||
control_flow_keywords.insert(keyword);
|
||||
} else {
|
||||
keywords.insert(keyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int line = 0;
|
||||
int col = 0;
|
||||
int thumbnail_size = MAX(p_size.x, p_size.y);
|
||||
Ref<Image> img = Image::create_empty(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8);
|
||||
|
||||
Color bg_color = EDITOR_GET("text_editor/theme/highlighting/background_color");
|
||||
Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
|
||||
Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
|
||||
Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
|
||||
Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
|
||||
Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
|
||||
Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
|
||||
|
||||
if (bg_color.a == 0) {
|
||||
bg_color = Color(0, 0, 0, 0);
|
||||
}
|
||||
bg_color.a = MAX(bg_color.a, 0.2); // Ensure we have some background, regardless of the text editor setting.
|
||||
|
||||
img->fill(bg_color);
|
||||
|
||||
const int x0 = thumbnail_size / 8;
|
||||
const int y0 = thumbnail_size / 8;
|
||||
const int available_height = thumbnail_size - 2 * y0;
|
||||
col = x0;
|
||||
|
||||
bool prev_is_text = false;
|
||||
bool in_control_flow_keyword = false;
|
||||
bool in_keyword = false;
|
||||
bool in_comment = false;
|
||||
bool in_doc_comment = false;
|
||||
for (int i = 0; i < p_source_code.length(); i++) {
|
||||
char32_t c = p_source_code[i];
|
||||
if (c > 32) {
|
||||
if (col < thumbnail_size) {
|
||||
Color color = text_color;
|
||||
|
||||
if (c == '#') {
|
||||
if (i < p_source_code.length() - 1 && p_source_code[i + 1] == '#') {
|
||||
in_doc_comment = true;
|
||||
} else {
|
||||
in_comment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_comment) {
|
||||
color = comment_color;
|
||||
} else if (in_doc_comment) {
|
||||
color = doc_comment_color;
|
||||
} else {
|
||||
if (is_symbol(c)) {
|
||||
// Make symbol a little visible.
|
||||
color = symbol_color;
|
||||
in_control_flow_keyword = false;
|
||||
in_keyword = false;
|
||||
} else if (!prev_is_text && is_ascii_identifier_char(c)) {
|
||||
int pos = i;
|
||||
|
||||
while (is_ascii_identifier_char(p_source_code[pos])) {
|
||||
pos++;
|
||||
}
|
||||
String word = p_source_code.substr(i, pos - i);
|
||||
if (control_flow_keywords.has(word)) {
|
||||
in_control_flow_keyword = true;
|
||||
} else if (keywords.has(word)) {
|
||||
in_keyword = true;
|
||||
}
|
||||
|
||||
} else if (!is_ascii_identifier_char(c)) {
|
||||
in_keyword = false;
|
||||
}
|
||||
|
||||
if (in_control_flow_keyword) {
|
||||
color = control_flow_keyword_color;
|
||||
} else if (in_keyword) {
|
||||
color = keyword_color;
|
||||
}
|
||||
}
|
||||
Color ul = color;
|
||||
ul.a *= 0.5;
|
||||
img->set_pixel(col, y0 + line * 2, bg_color.blend(ul));
|
||||
img->set_pixel(col, y0 + line * 2 + 1, color);
|
||||
|
||||
prev_is_text = is_ascii_identifier_char(c);
|
||||
}
|
||||
col++;
|
||||
} else {
|
||||
prev_is_text = false;
|
||||
in_control_flow_keyword = false;
|
||||
in_keyword = false;
|
||||
|
||||
if (c == '\n') {
|
||||
in_comment = false;
|
||||
in_doc_comment = false;
|
||||
|
||||
col = x0;
|
||||
line++;
|
||||
if (line >= available_height / 2) {
|
||||
break;
|
||||
}
|
||||
} else if (c == '\t') {
|
||||
col += 3;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
post_process_preview(img);
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "AudioStream");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<AudioStream> stream = p_from;
|
||||
ERR_FAIL_COND_V(stream.is_null(), Ref<Texture2D>());
|
||||
|
||||
Vector<uint8_t> img;
|
||||
|
||||
int w = p_size.x;
|
||||
int h = p_size.y;
|
||||
img.resize(w * h * 3);
|
||||
|
||||
uint8_t *imgdata = img.ptrw();
|
||||
uint8_t *imgw = imgdata;
|
||||
|
||||
Ref<AudioStreamPlayback> playback = stream->instantiate_playback();
|
||||
ERR_FAIL_COND_V(playback.is_null(), Ref<Texture2D>());
|
||||
|
||||
real_t len_s = stream->get_length();
|
||||
if (len_s == 0) {
|
||||
len_s = 60; //one minute audio if no length specified
|
||||
}
|
||||
int frame_length = AudioServer::get_singleton()->get_mix_rate() * len_s;
|
||||
|
||||
Vector<AudioFrame> frames;
|
||||
frames.resize(frame_length);
|
||||
|
||||
playback->start();
|
||||
playback->mix(frames.ptrw(), 1, frames.size());
|
||||
playback->stop();
|
||||
|
||||
for (int i = 0; i < w; i++) {
|
||||
real_t max = -1000;
|
||||
real_t min = 1000;
|
||||
int from = uint64_t(i) * frame_length / w;
|
||||
int to = (uint64_t(i) + 1) * frame_length / w;
|
||||
to = MIN(to, frame_length);
|
||||
from = MIN(from, frame_length - 1);
|
||||
if (to == from) {
|
||||
to = from + 1;
|
||||
}
|
||||
|
||||
for (int j = from; j < to; j++) {
|
||||
max = MAX(max, frames[j].left);
|
||||
max = MAX(max, frames[j].right);
|
||||
|
||||
min = MIN(min, frames[j].left);
|
||||
min = MIN(min, frames[j].right);
|
||||
}
|
||||
|
||||
int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
|
||||
int pto = CLAMP((max * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4;
|
||||
|
||||
for (int j = 0; j < h; j++) {
|
||||
uint8_t *p = &imgw[(j * w + i) * 3];
|
||||
if (j < pfrom || j > pto) {
|
||||
p[0] = 100;
|
||||
p[1] = 100;
|
||||
p[2] = 100;
|
||||
} else {
|
||||
p[0] = 180;
|
||||
p[1] = 180;
|
||||
p[2] = 180;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_metadata["length"] = stream->get_length();
|
||||
|
||||
//post_process_preview(img);
|
||||
|
||||
Ref<Image> image = Image::create_from_data(w, h, false, Image::FORMAT_RGB8, img);
|
||||
return ImageTexture::create_from_image(image);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EditorMeshPreviewPlugin::abort() {
|
||||
draw_requester.abort();
|
||||
}
|
||||
|
||||
bool EditorMeshPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Mesh"); // Any mesh.
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Mesh> mesh = p_from;
|
||||
ERR_FAIL_COND_V(mesh.is_null(), Ref<Texture2D>());
|
||||
|
||||
RS::get_singleton()->instance_set_base(mesh_instance, mesh->get_rid());
|
||||
|
||||
AABB aabb = mesh->get_aabb();
|
||||
Vector3 ofs = aabb.get_center();
|
||||
aabb.position -= ofs;
|
||||
Transform3D xform;
|
||||
xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math::PI * 0.125);
|
||||
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math::PI * 0.125) * xform.basis;
|
||||
AABB rot_aabb = xform.xform(aabb);
|
||||
real_t m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5;
|
||||
if (m == 0) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
m = 1.0 / m;
|
||||
m *= 0.5;
|
||||
xform.basis.scale(Vector3(m, m, m));
|
||||
xform.origin = -xform.basis.xform(ofs); //-ofs*m;
|
||||
xform.origin.z -= rot_aabb.size.z * 2;
|
||||
RS::get_singleton()->instance_set_transform(mesh_instance, xform);
|
||||
|
||||
draw_requester.request_and_wait(viewport);
|
||||
|
||||
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
|
||||
ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
|
||||
|
||||
RS::get_singleton()->instance_set_base(mesh_instance, RID());
|
||||
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
|
||||
Vector2 new_size = img->get_size();
|
||||
if (new_size.x > p_size.x) {
|
||||
new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
|
||||
}
|
||||
if (new_size.y > p_size.y) {
|
||||
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
|
||||
}
|
||||
img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() {
|
||||
scenario = RS::get_singleton()->scenario_create();
|
||||
|
||||
viewport = RS::get_singleton()->viewport_create();
|
||||
RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
|
||||
RS::get_singleton()->viewport_set_scenario(viewport, scenario);
|
||||
RS::get_singleton()->viewport_set_size(viewport, 128, 128);
|
||||
RS::get_singleton()->viewport_set_transparent_background(viewport, true);
|
||||
RS::get_singleton()->viewport_set_active(viewport, true);
|
||||
viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
|
||||
|
||||
camera = RS::get_singleton()->camera_create();
|
||||
RS::get_singleton()->viewport_attach_camera(viewport, camera);
|
||||
RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3)));
|
||||
//RS::get_singleton()->camera_set_perspective(camera,45,0.1,10);
|
||||
RS::get_singleton()->camera_set_orthogonal(camera, 1.0, 0.01, 1000.0);
|
||||
|
||||
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
|
||||
camera_attributes = RS::get_singleton()->camera_attributes_create();
|
||||
RS::get_singleton()->camera_attributes_set_exposure(camera_attributes, 1.0, 0.000032552); // Matches default CameraAttributesPhysical to work well with default DirectionalLight3Ds.
|
||||
RS::get_singleton()->camera_set_camera_attributes(camera, camera_attributes);
|
||||
}
|
||||
|
||||
light = RS::get_singleton()->directional_light_create();
|
||||
light_instance = RS::get_singleton()->instance_create2(light, scenario);
|
||||
RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0)));
|
||||
|
||||
light2 = RS::get_singleton()->directional_light_create();
|
||||
RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
|
||||
//RS::get_singleton()->light_set_color(light2, RS::LIGHT_COLOR_SPECULAR, Color(0.0, 0.0, 0.0));
|
||||
light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
|
||||
|
||||
RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)));
|
||||
|
||||
//sphere = RS::get_singleton()->mesh_create();
|
||||
mesh_instance = RS::get_singleton()->instance_create();
|
||||
RS::get_singleton()->instance_set_scenario(mesh_instance, scenario);
|
||||
}
|
||||
|
||||
EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() {
|
||||
ERR_FAIL_NULL(RenderingServer::get_singleton());
|
||||
//RS::get_singleton()->free(sphere);
|
||||
RS::get_singleton()->free(mesh_instance);
|
||||
RS::get_singleton()->free(viewport);
|
||||
RS::get_singleton()->free(light);
|
||||
RS::get_singleton()->free(light_instance);
|
||||
RS::get_singleton()->free(light2);
|
||||
RS::get_singleton()->free(light_instance2);
|
||||
RS::get_singleton()->free(camera);
|
||||
RS::get_singleton()->free(camera_attributes);
|
||||
RS::get_singleton()->free(scenario);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EditorFontPreviewPlugin::abort() {
|
||||
draw_requester.abort();
|
||||
}
|
||||
|
||||
bool EditorFontPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Font");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Font> sampled_font = ResourceLoader::load(p_path);
|
||||
ERR_FAIL_COND_V(sampled_font.is_null(), Ref<Texture2D>());
|
||||
|
||||
String sample;
|
||||
static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀";
|
||||
for (int i = 0; i < sample_base.length(); i++) {
|
||||
if (sampled_font->has_char(sample_base[i])) {
|
||||
sample += sample_base[i];
|
||||
}
|
||||
}
|
||||
if (sample.is_empty()) {
|
||||
sample = sampled_font->get_supported_chars().substr(0, 6);
|
||||
}
|
||||
Vector2 size = sampled_font->get_string_size(sample, HORIZONTAL_ALIGNMENT_LEFT, -1, 50);
|
||||
|
||||
Vector2 pos;
|
||||
|
||||
pos.x = 64 - size.x / 2;
|
||||
pos.y = 80;
|
||||
|
||||
const Color c = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
|
||||
const float fg = c.get_luminance() < 0.5 ? 1.0 : 0.0;
|
||||
sampled_font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(fg, fg, fg));
|
||||
|
||||
draw_requester.request_and_wait(viewport);
|
||||
|
||||
RS::get_singleton()->canvas_item_clear(canvas_item);
|
||||
|
||||
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
|
||||
ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>());
|
||||
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
|
||||
Vector2 new_size = img->get_size();
|
||||
if (new_size.x > p_size.x) {
|
||||
new_size = Vector2(p_size.x, new_size.y * p_size.x / new_size.x);
|
||||
}
|
||||
if (new_size.y > p_size.y) {
|
||||
new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y);
|
||||
}
|
||||
img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
|
||||
post_process_preview(img);
|
||||
|
||||
return ImageTexture::create_from_image(img);
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorFontPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
String path = p_from->get_path();
|
||||
if (!FileAccess::exists(path)) {
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
return generate_from_path(path, p_size, p_metadata);
|
||||
}
|
||||
|
||||
EditorFontPreviewPlugin::EditorFontPreviewPlugin() {
|
||||
viewport = RS::get_singleton()->viewport_create();
|
||||
RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_DISABLED);
|
||||
RS::get_singleton()->viewport_set_size(viewport, 128, 128);
|
||||
RS::get_singleton()->viewport_set_active(viewport, true);
|
||||
viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
|
||||
|
||||
canvas = RS::get_singleton()->canvas_create();
|
||||
canvas_item = RS::get_singleton()->canvas_item_create();
|
||||
|
||||
RS::get_singleton()->viewport_attach_canvas(viewport, canvas);
|
||||
RS::get_singleton()->canvas_item_set_parent(canvas_item, canvas);
|
||||
}
|
||||
|
||||
EditorFontPreviewPlugin::~EditorFontPreviewPlugin() {
|
||||
ERR_FAIL_NULL(RenderingServer::get_singleton());
|
||||
RS::get_singleton()->free(canvas_item);
|
||||
RS::get_singleton()->free(canvas);
|
||||
RS::get_singleton()->free(viewport);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const real_t GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR = 4.0;
|
||||
|
||||
bool EditorGradientPreviewPlugin::handles(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Gradient");
|
||||
}
|
||||
|
||||
bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Gradient> gradient = p_from;
|
||||
if (gradient.is_valid()) {
|
||||
Ref<GradientTexture1D> ptex;
|
||||
ptex.instantiate();
|
||||
ptex->set_width(p_size.width * GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR * EDSCALE);
|
||||
ptex->set_gradient(gradient);
|
||||
return ImageTexture::create_from_image(ptex->get_image());
|
||||
}
|
||||
return Ref<Texture2D>();
|
||||
}
|
||||
170
editor/inspector/editor_preview_plugins.h
Normal file
170
editor/inspector/editor_preview_plugins.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/**************************************************************************/
|
||||
/* editor_preview_plugins.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_resource_preview.h"
|
||||
|
||||
class ScriptLanguage;
|
||||
|
||||
void post_process_preview(Ref<Image> p_image);
|
||||
|
||||
class EditorTexturePreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorTexturePreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual bool generate_small_preview_automatically() const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorImagePreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorImagePreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual bool generate_small_preview_automatically() const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorBitmapPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorBitmapPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual bool generate_small_preview_automatically() const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorPackedScenePreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorPackedScenePreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorMaterialPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
RID scenario;
|
||||
RID sphere;
|
||||
RID sphere_instance;
|
||||
RID viewport;
|
||||
RID viewport_texture;
|
||||
RID light;
|
||||
RID light_instance;
|
||||
RID light2;
|
||||
RID light_instance2;
|
||||
RID camera;
|
||||
RID camera_attributes;
|
||||
mutable DrawRequester draw_requester;
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual bool generate_small_preview_automatically() const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual void abort() override;
|
||||
|
||||
EditorMaterialPreviewPlugin();
|
||||
~EditorMaterialPreviewPlugin();
|
||||
};
|
||||
|
||||
class EditorScriptPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorScriptPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
Ref<Texture2D> _generate_from_source_code(const ScriptLanguage *p_language, const String &p_source_code, const Size2 &p_size, Dictionary &p_metadata) const;
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorAudioStreamPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
|
||||
class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorMeshPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
RID scenario;
|
||||
RID mesh_instance;
|
||||
RID viewport;
|
||||
RID viewport_texture;
|
||||
RID light;
|
||||
RID light_instance;
|
||||
RID light2;
|
||||
RID light_instance2;
|
||||
RID camera;
|
||||
RID camera_attributes;
|
||||
mutable DrawRequester draw_requester;
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual void abort() override;
|
||||
|
||||
EditorMeshPreviewPlugin();
|
||||
~EditorMeshPreviewPlugin();
|
||||
};
|
||||
|
||||
class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorFontPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
RID viewport;
|
||||
RID viewport_texture;
|
||||
RID canvas;
|
||||
RID canvas_item;
|
||||
mutable DrawRequester draw_requester;
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
virtual void abort() override;
|
||||
|
||||
EditorFontPreviewPlugin();
|
||||
~EditorFontPreviewPlugin();
|
||||
};
|
||||
|
||||
class EditorGradientPreviewPlugin : public EditorResourcePreviewGenerator {
|
||||
GDCLASS(EditorGradientPreviewPlugin, EditorResourcePreviewGenerator);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const override;
|
||||
virtual bool generate_small_preview_automatically() const override;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const override;
|
||||
};
|
||||
4098
editor/inspector/editor_properties.cpp
Normal file
4098
editor/inspector/editor_properties.cpp
Normal file
File diff suppressed because it is too large
Load Diff
758
editor/inspector/editor_properties.h
Normal file
758
editor/inspector/editor_properties.h
Normal file
@@ -0,0 +1,758 @@
|
||||
/**************************************************************************/
|
||||
/* editor_properties.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
|
||||
class CheckBox;
|
||||
class ColorPickerButton;
|
||||
class CreateDialog;
|
||||
class EditorFileDialog;
|
||||
class EditorLocaleDialog;
|
||||
class EditorResourcePicker;
|
||||
class EditorSpinSlider;
|
||||
class EditorVariantTypePopupMenu;
|
||||
class MenuButton;
|
||||
class PropertySelector;
|
||||
class SceneTreeDialog;
|
||||
class TextEdit;
|
||||
class TextureButton;
|
||||
|
||||
class EditorPropertyNil : public EditorProperty {
|
||||
GDCLASS(EditorPropertyNil, EditorProperty);
|
||||
LineEdit *text = nullptr;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyNil();
|
||||
};
|
||||
|
||||
class EditorPropertyVariant : public EditorProperty {
|
||||
GDCLASS(EditorPropertyVariant, EditorProperty);
|
||||
|
||||
HBoxContainer *content = nullptr;
|
||||
EditorProperty *sub_property = nullptr;
|
||||
Button *edit_button = nullptr;
|
||||
EditorVariantTypePopupMenu *change_type = nullptr;
|
||||
|
||||
Variant::Type current_type = Variant::VARIANT_MAX;
|
||||
Variant::Type new_type = Variant::VARIANT_MAX;
|
||||
|
||||
void _change_type(int p_to_type);
|
||||
void _popup_edit_menu();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyVariant();
|
||||
};
|
||||
|
||||
class EditorPropertyText : public EditorProperty {
|
||||
GDCLASS(EditorPropertyText, EditorProperty);
|
||||
LineEdit *text = nullptr;
|
||||
|
||||
bool updating = false;
|
||||
bool string_name = false;
|
||||
void _text_changed(const String &p_string);
|
||||
void _text_submitted(const String &p_string);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
void set_string_name(bool p_enabled);
|
||||
virtual void update_property() override;
|
||||
void set_placeholder(const String &p_string);
|
||||
void set_secret(bool p_enabled);
|
||||
EditorPropertyText();
|
||||
};
|
||||
|
||||
class EditorPropertyMultilineText : public EditorProperty {
|
||||
GDCLASS(EditorPropertyMultilineText, EditorProperty);
|
||||
TextEdit *text = nullptr;
|
||||
|
||||
AcceptDialog *big_text_dialog = nullptr;
|
||||
TextEdit *big_text = nullptr;
|
||||
Button *open_big_text = nullptr;
|
||||
|
||||
void _big_text_changed();
|
||||
void _text_changed();
|
||||
void _open_big_text();
|
||||
bool expression = false;
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyMultilineText(bool p_expression = false);
|
||||
};
|
||||
|
||||
class EditorPropertyTextEnum : public EditorProperty {
|
||||
GDCLASS(EditorPropertyTextEnum, EditorProperty);
|
||||
|
||||
HBoxContainer *default_layout = nullptr;
|
||||
HBoxContainer *edit_custom_layout = nullptr;
|
||||
|
||||
OptionButton *option_button = nullptr;
|
||||
Button *edit_button = nullptr;
|
||||
|
||||
LineEdit *custom_value_edit = nullptr;
|
||||
Button *accept_button = nullptr;
|
||||
Button *cancel_button = nullptr;
|
||||
|
||||
Vector<String> options;
|
||||
bool string_name = false;
|
||||
bool loose_mode = false;
|
||||
|
||||
void _emit_changed_value(const String &p_string);
|
||||
void _option_selected(int p_which);
|
||||
|
||||
void _edit_custom_value();
|
||||
void _custom_value_submitted(const String &p_value);
|
||||
void _custom_value_accepted();
|
||||
void _custom_value_canceled();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(const Vector<String> &p_options, bool p_string_name = false, bool p_loose_mode = false);
|
||||
virtual void update_property() override;
|
||||
EditorPropertyTextEnum();
|
||||
};
|
||||
|
||||
class EditorPropertyPath : public EditorProperty {
|
||||
GDCLASS(EditorPropertyPath, EditorProperty);
|
||||
Vector<String> extensions;
|
||||
bool folder = false;
|
||||
bool global = false;
|
||||
bool save_mode = false;
|
||||
bool enable_uid = false;
|
||||
bool display_uid = false;
|
||||
|
||||
EditorFileDialog *dialog = nullptr;
|
||||
LineEdit *path = nullptr;
|
||||
Button *toggle_uid = nullptr;
|
||||
Button *path_edit = nullptr;
|
||||
|
||||
String _get_path_text(bool p_allow_uid = false);
|
||||
|
||||
void _path_selected(const String &p_path);
|
||||
void _path_pressed();
|
||||
void _path_focus_exited();
|
||||
void _toggle_uid_display();
|
||||
void _update_uid_icon();
|
||||
void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
LineEdit *get_path_edit() const { return path; }
|
||||
|
||||
void setup(const Vector<String> &p_extensions, bool p_folder, bool p_global, bool p_enable_uid);
|
||||
void set_save_mode();
|
||||
virtual void update_property() override;
|
||||
EditorPropertyPath();
|
||||
};
|
||||
|
||||
class EditorPropertyLocale : public EditorProperty {
|
||||
GDCLASS(EditorPropertyLocale, EditorProperty);
|
||||
EditorLocaleDialog *dialog = nullptr;
|
||||
LineEdit *locale = nullptr;
|
||||
Button *locale_edit = nullptr;
|
||||
|
||||
void _locale_selected(const String &p_locale);
|
||||
void _locale_pressed();
|
||||
void _locale_focus_exited();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(const String &p_hit_string);
|
||||
virtual void update_property() override;
|
||||
EditorPropertyLocale();
|
||||
};
|
||||
|
||||
class EditorPropertyClassName : public EditorProperty {
|
||||
GDCLASS(EditorPropertyClassName, EditorProperty);
|
||||
|
||||
private:
|
||||
CreateDialog *dialog = nullptr;
|
||||
Button *property = nullptr;
|
||||
String selected_type;
|
||||
String base_type;
|
||||
void _property_selected();
|
||||
void _dialog_created();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
void setup(const String &p_base_type, const String &p_selected_type);
|
||||
virtual void update_property() override;
|
||||
EditorPropertyClassName();
|
||||
};
|
||||
|
||||
class EditorPropertyCheck : public EditorProperty {
|
||||
GDCLASS(EditorPropertyCheck, EditorProperty);
|
||||
CheckBox *checkbox = nullptr;
|
||||
|
||||
void _checkbox_pressed();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyCheck();
|
||||
};
|
||||
|
||||
class EditorPropertyEnum : public EditorProperty {
|
||||
GDCLASS(EditorPropertyEnum, EditorProperty);
|
||||
OptionButton *options = nullptr;
|
||||
|
||||
void _option_selected(int p_which);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
void setup(const Vector<String> &p_options);
|
||||
virtual void update_property() override;
|
||||
void set_option_button_clip(bool p_enable);
|
||||
EditorPropertyEnum();
|
||||
};
|
||||
|
||||
class EditorPropertyFlags : public EditorProperty {
|
||||
GDCLASS(EditorPropertyFlags, EditorProperty);
|
||||
VBoxContainer *vbox = nullptr;
|
||||
Vector<CheckBox *> flags;
|
||||
Vector<uint32_t> flag_values;
|
||||
|
||||
void _flag_toggled(int p_index);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
void setup(const Vector<String> &p_options);
|
||||
virtual void update_property() override;
|
||||
EditorPropertyFlags();
|
||||
};
|
||||
|
||||
///////////////////// LAYERS /////////////////////////
|
||||
|
||||
class EditorPropertyLayersGrid : public Control {
|
||||
GDCLASS(EditorPropertyLayersGrid, Control);
|
||||
|
||||
private:
|
||||
Vector<Rect2> flag_rects;
|
||||
Rect2 expand_rect;
|
||||
bool expand_hovered = false;
|
||||
bool expanded = false;
|
||||
int expansion_rows = 0;
|
||||
uint32_t hovered_index = INT32_MAX; // Nothing is hovered.
|
||||
bool read_only = false;
|
||||
int renamed_layer_index = -1;
|
||||
PopupMenu *layer_rename = nullptr;
|
||||
ConfirmationDialog *rename_dialog = nullptr;
|
||||
LineEdit *rename_dialog_text = nullptr;
|
||||
|
||||
void _rename_pressed(int p_menu);
|
||||
void _rename_operation_confirm();
|
||||
void _update_hovered(const Vector2 &p_position);
|
||||
void _on_hover_exit();
|
||||
void _update_flag(bool p_replace);
|
||||
Size2 get_grid_size() const;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
uint32_t value = 0;
|
||||
int layer_group_size = 0;
|
||||
uint32_t layer_count = 0;
|
||||
Vector<String> names;
|
||||
Vector<String> tooltips;
|
||||
|
||||
void set_read_only(bool p_read_only);
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
void gui_input(const Ref<InputEvent> &p_ev) override;
|
||||
void set_flag(uint32_t p_flag);
|
||||
EditorPropertyLayersGrid();
|
||||
};
|
||||
|
||||
class EditorPropertyLayers : public EditorProperty {
|
||||
GDCLASS(EditorPropertyLayers, EditorProperty);
|
||||
|
||||
public:
|
||||
enum LayerType {
|
||||
LAYER_PHYSICS_2D,
|
||||
LAYER_RENDER_2D,
|
||||
LAYER_NAVIGATION_2D,
|
||||
LAYER_PHYSICS_3D,
|
||||
LAYER_RENDER_3D,
|
||||
LAYER_NAVIGATION_3D,
|
||||
LAYER_AVOIDANCE,
|
||||
};
|
||||
|
||||
private:
|
||||
EditorPropertyLayersGrid *grid = nullptr;
|
||||
void _grid_changed(uint32_t p_grid);
|
||||
String basename;
|
||||
LayerType layer_type;
|
||||
PopupMenu *layers = nullptr;
|
||||
TextureButton *button = nullptr;
|
||||
|
||||
void _button_pressed();
|
||||
void _menu_pressed(int p_menu);
|
||||
void _refresh_names();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
void setup(LayerType p_layer_type);
|
||||
void set_layer_name(int p_index, const String &p_name);
|
||||
String get_layer_name(int p_index) const;
|
||||
virtual void update_property() override;
|
||||
EditorPropertyLayers();
|
||||
};
|
||||
|
||||
class EditorPropertyInteger : public EditorProperty {
|
||||
GDCLASS(EditorPropertyInteger, EditorProperty);
|
||||
EditorSpinSlider *spin = nullptr;
|
||||
void _value_changed(int64_t p_val);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix = String());
|
||||
EditorPropertyInteger();
|
||||
};
|
||||
|
||||
class EditorPropertyObjectID : public EditorProperty {
|
||||
GDCLASS(EditorPropertyObjectID, EditorProperty);
|
||||
Button *edit = nullptr;
|
||||
String base_type;
|
||||
void _edit_pressed();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(const String &p_base_type);
|
||||
EditorPropertyObjectID();
|
||||
};
|
||||
|
||||
class EditorPropertySignal : public EditorProperty {
|
||||
GDCLASS(EditorPropertySignal, EditorProperty);
|
||||
Button *edit = nullptr;
|
||||
String base_type;
|
||||
void _edit_pressed();
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertySignal();
|
||||
};
|
||||
|
||||
class EditorPropertyCallable : public EditorProperty {
|
||||
GDCLASS(EditorPropertyCallable, EditorProperty);
|
||||
Button *edit = nullptr;
|
||||
String base_type;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyCallable();
|
||||
};
|
||||
|
||||
class EditorPropertyFloat : public EditorProperty {
|
||||
GDCLASS(EditorPropertyFloat, EditorProperty);
|
||||
EditorSpinSlider *spin = nullptr;
|
||||
bool radians_as_degrees = false;
|
||||
void _value_changed(double p_val);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_exp_range, bool p_greater, bool p_lesser, const String &p_suffix = String(), bool p_radians_as_degrees = false);
|
||||
EditorPropertyFloat();
|
||||
};
|
||||
|
||||
class EditorPropertyEasing : public EditorProperty {
|
||||
GDCLASS(EditorPropertyEasing, EditorProperty);
|
||||
Control *easing_draw = nullptr;
|
||||
PopupMenu *preset = nullptr;
|
||||
EditorSpinSlider *spin = nullptr;
|
||||
|
||||
bool dragging = false;
|
||||
bool full = false;
|
||||
bool flip = false;
|
||||
bool positive_only = false;
|
||||
|
||||
enum {
|
||||
EASING_ZERO,
|
||||
EASING_LINEAR,
|
||||
EASING_IN,
|
||||
EASING_OUT,
|
||||
EASING_IN_OUT,
|
||||
EASING_OUT_IN,
|
||||
EASING_MAX
|
||||
|
||||
};
|
||||
|
||||
void _drag_easing(const Ref<InputEvent> &p_ev);
|
||||
void _draw_easing();
|
||||
void _set_preset(int);
|
||||
|
||||
void _setup_spin();
|
||||
void _spin_value_changed(double p_value);
|
||||
void _spin_focus_exited();
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(bool p_positive_only, bool p_flip);
|
||||
EditorPropertyEasing();
|
||||
};
|
||||
|
||||
class EditorPropertyRect2 : public EditorProperty {
|
||||
GDCLASS(EditorPropertyRect2, EditorProperty);
|
||||
EditorSpinSlider *spin[4];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyRect2(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyRect2i : public EditorProperty {
|
||||
GDCLASS(EditorPropertyRect2i, EditorProperty);
|
||||
EditorSpinSlider *spin[4];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(int p_min, int p_max, const String &p_suffix = String());
|
||||
EditorPropertyRect2i(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyPlane : public EditorProperty {
|
||||
GDCLASS(EditorPropertyPlane, EditorProperty);
|
||||
EditorSpinSlider *spin[4];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyPlane(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyQuaternion : public EditorProperty {
|
||||
GDCLASS(EditorPropertyQuaternion, EditorProperty);
|
||||
BoxContainer *default_layout = nullptr;
|
||||
EditorSpinSlider *spin[4];
|
||||
|
||||
Button *warning = nullptr;
|
||||
AcceptDialog *warning_dialog = nullptr;
|
||||
|
||||
Label *euler_label = nullptr;
|
||||
VBoxContainer *edit_custom_bc = nullptr;
|
||||
EditorSpinSlider *euler[3];
|
||||
Button *edit_button = nullptr;
|
||||
|
||||
Vector3 edit_euler;
|
||||
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
void _edit_custom_value();
|
||||
void _custom_value_changed(double p_val);
|
||||
void _warning_pressed();
|
||||
|
||||
bool is_grabbing_euler();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String(), bool p_hide_editor = false);
|
||||
EditorPropertyQuaternion();
|
||||
};
|
||||
|
||||
class EditorPropertyAABB : public EditorProperty {
|
||||
GDCLASS(EditorPropertyAABB, EditorProperty);
|
||||
EditorSpinSlider *spin[6];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyAABB();
|
||||
};
|
||||
|
||||
class EditorPropertyTransform2D : public EditorProperty {
|
||||
GDCLASS(EditorPropertyTransform2D, EditorProperty);
|
||||
EditorSpinSlider *spin[6];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyTransform2D(bool p_include_origin = true);
|
||||
};
|
||||
|
||||
class EditorPropertyBasis : public EditorProperty {
|
||||
GDCLASS(EditorPropertyBasis, EditorProperty);
|
||||
EditorSpinSlider *spin[9];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyBasis();
|
||||
};
|
||||
|
||||
class EditorPropertyTransform3D : public EditorProperty {
|
||||
GDCLASS(EditorPropertyTransform3D, EditorProperty);
|
||||
EditorSpinSlider *spin[12];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
virtual void update_using_transform(Transform3D p_transform);
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyTransform3D();
|
||||
};
|
||||
|
||||
class EditorPropertyProjection : public EditorProperty {
|
||||
GDCLASS(EditorPropertyProjection, EditorProperty);
|
||||
EditorSpinSlider *spin[16];
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
virtual void update_using_transform(Projection p_transform);
|
||||
void setup(double p_min, double p_max, double p_step, bool p_hide_slider, const String &p_suffix = String());
|
||||
EditorPropertyProjection();
|
||||
};
|
||||
|
||||
class EditorPropertyColor : public EditorProperty {
|
||||
GDCLASS(EditorPropertyColor, EditorProperty);
|
||||
ColorPickerButton *picker = nullptr;
|
||||
void _color_changed(const Color &p_color);
|
||||
void _picker_created();
|
||||
void _popup_opening();
|
||||
void _popup_closed();
|
||||
|
||||
Color last_color;
|
||||
bool live_changes_enabled = true;
|
||||
bool was_checked = false;
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(bool p_show_alpha);
|
||||
void set_live_changes_enabled(bool p_enabled);
|
||||
EditorPropertyColor();
|
||||
};
|
||||
|
||||
class EditorPropertyNodePath : public EditorProperty {
|
||||
GDCLASS(EditorPropertyNodePath, EditorProperty);
|
||||
|
||||
enum {
|
||||
ACTION_CLEAR,
|
||||
ACTION_COPY,
|
||||
ACTION_EDIT,
|
||||
ACTION_SELECT,
|
||||
};
|
||||
|
||||
Button *assign = nullptr;
|
||||
MenuButton *menu = nullptr;
|
||||
LineEdit *edit = nullptr;
|
||||
|
||||
SceneTreeDialog *scene_tree = nullptr;
|
||||
bool use_path_from_scene_root = false;
|
||||
bool editing_node = false;
|
||||
bool dropping = false;
|
||||
|
||||
Vector<StringName> valid_types;
|
||||
void _node_selected(const NodePath &p_path, bool p_absolute = true);
|
||||
void _node_assign();
|
||||
void _assign_draw();
|
||||
Node *get_base_node();
|
||||
void _update_menu();
|
||||
void _menu_option(int p_idx);
|
||||
void _accept_text();
|
||||
void _text_submitted(const String &p_text);
|
||||
const NodePath _get_node_path() const;
|
||||
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
bool is_drop_valid(const Dictionary &p_drag_data) const;
|
||||
|
||||
virtual Variant _get_cache_value(const StringName &p_prop, bool &r_valid) const override;
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(const Vector<StringName> &p_valid_types, bool p_use_path_from_scene_root = true, bool p_editing_node = false);
|
||||
EditorPropertyNodePath();
|
||||
};
|
||||
|
||||
class EditorPropertyRID : public EditorProperty {
|
||||
GDCLASS(EditorPropertyRID, EditorProperty);
|
||||
Label *label = nullptr;
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyRID();
|
||||
};
|
||||
|
||||
class EditorPropertyResource : public EditorProperty {
|
||||
GDCLASS(EditorPropertyResource, EditorProperty);
|
||||
|
||||
EditorResourcePicker *resource_picker = nullptr;
|
||||
SceneTreeDialog *scene_tree = nullptr;
|
||||
|
||||
bool use_sub_inspector = false;
|
||||
EditorInspector *sub_inspector = nullptr;
|
||||
bool opened_editor = false;
|
||||
bool use_filter = false;
|
||||
|
||||
void _resource_selected(const Ref<Resource> &p_resource, bool p_inspect);
|
||||
void _resource_changed(const Ref<Resource> &p_resource);
|
||||
|
||||
void _viewport_selected(const NodePath &p_path);
|
||||
|
||||
void _sub_inspector_property_keyed(const String &p_property, const Variant &p_value, bool p_advance);
|
||||
void _sub_inspector_resource_selected(const Ref<Resource> &p_resource, const String &p_property);
|
||||
void _sub_inspector_object_id_selected(int p_id);
|
||||
|
||||
void _open_editor_pressed();
|
||||
void _update_preferred_shader();
|
||||
bool _should_stop_editing() const;
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(Object *p_object, const String &p_path, const String &p_base_type);
|
||||
|
||||
void collapse_all_folding() override;
|
||||
void expand_all_folding() override;
|
||||
void expand_revertable() override;
|
||||
|
||||
void set_use_sub_inspector(bool p_enable);
|
||||
void set_use_filter(bool p_use);
|
||||
void fold_resource();
|
||||
|
||||
virtual bool is_colored(ColorationMode p_mode) override;
|
||||
|
||||
EditorPropertyResource();
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
/// \brief The EditorInspectorDefaultPlugin class
|
||||
///
|
||||
class EditorInspectorDefaultPlugin : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorDefaultPlugin, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override;
|
||||
|
||||
static EditorProperty *get_editor_for_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false);
|
||||
};
|
||||
1717
editor/inspector/editor_properties_array_dict.cpp
Normal file
1717
editor/inspector/editor_properties_array_dict.cpp
Normal file
File diff suppressed because it is too large
Load Diff
301
editor/inspector/editor_properties_array_dict.h
Normal file
301
editor/inspector/editor_properties_array_dict.h
Normal file
@@ -0,0 +1,301 @@
|
||||
/**************************************************************************/
|
||||
/* editor_properties_array_dict.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/translations/editor_locale_dialog.h"
|
||||
|
||||
class Button;
|
||||
class EditorSpinSlider;
|
||||
class EditorVariantTypePopupMenu;
|
||||
class MarginContainer;
|
||||
|
||||
class EditorPropertyArrayObject : public RefCounted {
|
||||
GDCLASS(EditorPropertyArrayObject, RefCounted);
|
||||
|
||||
Variant array;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
|
||||
public:
|
||||
enum {
|
||||
NOT_CHANGING_TYPE = -1,
|
||||
};
|
||||
|
||||
void set_array(const Variant &p_array);
|
||||
Variant get_array();
|
||||
};
|
||||
|
||||
class EditorPropertyDictionaryObject : public RefCounted {
|
||||
GDCLASS(EditorPropertyDictionaryObject, RefCounted);
|
||||
|
||||
Variant new_item_key;
|
||||
Variant new_item_value;
|
||||
Dictionary dict;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
|
||||
public:
|
||||
enum {
|
||||
NOT_CHANGING_TYPE = -3,
|
||||
NEW_KEY_INDEX,
|
||||
NEW_VALUE_INDEX,
|
||||
};
|
||||
|
||||
bool get_by_property_name(const String &p_name, Variant &r_ret) const;
|
||||
void set_dict(const Dictionary &p_dict);
|
||||
Dictionary get_dict();
|
||||
|
||||
void set_new_item_key(const Variant &p_new_item);
|
||||
Variant get_new_item_key();
|
||||
|
||||
void set_new_item_value(const Variant &p_new_item);
|
||||
Variant get_new_item_value();
|
||||
|
||||
String get_label_for_index(int p_index);
|
||||
String get_property_name_for_index(int p_index);
|
||||
String get_key_name_for_index(int p_index);
|
||||
};
|
||||
|
||||
class EditorPropertyArray : public EditorProperty {
|
||||
GDCLASS(EditorPropertyArray, EditorProperty);
|
||||
|
||||
struct Slot {
|
||||
Ref<EditorPropertyArrayObject> object;
|
||||
HBoxContainer *container = nullptr;
|
||||
int index = -1;
|
||||
Variant::Type type = Variant::VARIANT_MAX;
|
||||
bool as_id = false;
|
||||
EditorProperty *prop = nullptr;
|
||||
Button *reorder_button = nullptr;
|
||||
|
||||
void set_index(int p_idx) {
|
||||
String prop_name = "indices/" + itos(p_idx);
|
||||
prop->set_object_and_property(object.ptr(), prop_name);
|
||||
prop->set_label(itos(p_idx));
|
||||
index = p_idx;
|
||||
}
|
||||
};
|
||||
|
||||
EditorVariantTypePopupMenu *change_type = nullptr;
|
||||
|
||||
bool preview_value = false;
|
||||
int page_length = 20;
|
||||
int page_index = 0;
|
||||
int changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;
|
||||
Button *edit = nullptr;
|
||||
PanelContainer *container = nullptr;
|
||||
VBoxContainer *property_vbox = nullptr;
|
||||
EditorSpinSlider *size_slider = nullptr;
|
||||
Button *button_add_item = nullptr;
|
||||
EditorPaginator *paginator = nullptr;
|
||||
Variant::Type array_type;
|
||||
Variant::Type subtype;
|
||||
PropertyHint subtype_hint;
|
||||
String subtype_hint_string;
|
||||
LocalVector<Slot> slots;
|
||||
|
||||
Slot reorder_slot;
|
||||
int reorder_to_index = -1;
|
||||
float reorder_mouse_y_delta = 0.0f;
|
||||
void initialize_array(Variant &p_array);
|
||||
|
||||
void _page_changed(int p_page);
|
||||
|
||||
void _reorder_button_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _reorder_button_down(int p_index);
|
||||
void _reorder_button_up();
|
||||
void _create_new_property_slot();
|
||||
|
||||
Node *get_base_node();
|
||||
|
||||
protected:
|
||||
Ref<EditorPropertyArrayObject> object;
|
||||
|
||||
bool updating = false;
|
||||
bool dropping = false;
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
virtual void _add_element();
|
||||
virtual void _length_changed(double p_page);
|
||||
virtual void _edit_pressed();
|
||||
virtual void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false);
|
||||
virtual void _change_type(Object *p_button, int p_slot_index);
|
||||
virtual void _change_type_menu(int p_index);
|
||||
|
||||
virtual void _object_id_selected(const StringName &p_property, ObjectID p_id);
|
||||
virtual void _remove_pressed(int p_index);
|
||||
|
||||
virtual void _button_draw();
|
||||
virtual void _button_add_item_draw();
|
||||
virtual bool _is_drop_valid(const Dictionary &p_drag_data) const;
|
||||
virtual bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
virtual void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
|
||||
public:
|
||||
void setup(Variant::Type p_array_type, const String &p_hint_string = "");
|
||||
void set_preview_value(bool p_preview_value);
|
||||
virtual void update_property() override;
|
||||
virtual bool is_colored(ColorationMode p_mode) override;
|
||||
EditorPropertyArray();
|
||||
};
|
||||
|
||||
class EditorPropertyDictionary : public EditorProperty {
|
||||
GDCLASS(EditorPropertyDictionary, EditorProperty);
|
||||
|
||||
struct Slot {
|
||||
Ref<EditorPropertyDictionaryObject> object;
|
||||
HBoxContainer *container = nullptr;
|
||||
int index = -1;
|
||||
Variant::Type type = Variant::VARIANT_MAX;
|
||||
Variant::Type key_type = Variant::VARIANT_MAX;
|
||||
bool as_id = false;
|
||||
bool key_as_id = false;
|
||||
EditorProperty *prop = nullptr;
|
||||
EditorProperty *prop_key = nullptr;
|
||||
String prop_name;
|
||||
String key_name;
|
||||
|
||||
void set_index(int p_idx) {
|
||||
index = p_idx;
|
||||
prop_name = object->get_property_name_for_index(p_idx);
|
||||
key_name = object->get_key_name_for_index(p_idx);
|
||||
update_prop_or_index();
|
||||
}
|
||||
|
||||
void set_prop(EditorProperty *p_prop) {
|
||||
prop->add_sibling(p_prop);
|
||||
prop->queue_free();
|
||||
prop = p_prop;
|
||||
update_prop_or_index();
|
||||
}
|
||||
|
||||
void set_key_prop(EditorProperty *p_prop) {
|
||||
if (prop_key) {
|
||||
prop_key->add_sibling(p_prop);
|
||||
prop_key->queue_free();
|
||||
prop_key = p_prop;
|
||||
update_prop_or_index();
|
||||
}
|
||||
}
|
||||
|
||||
void update_prop_or_index() {
|
||||
prop->set_object_and_property(object.ptr(), prop_name);
|
||||
if (prop_key) {
|
||||
prop_key->set_object_and_property(object.ptr(), key_name);
|
||||
} else {
|
||||
prop->set_label(object->get_label_for_index(index));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EditorVariantTypePopupMenu *change_type = nullptr;
|
||||
bool updating = false;
|
||||
|
||||
bool preview_value = false;
|
||||
Ref<EditorPropertyDictionaryObject> object;
|
||||
int page_length = 20;
|
||||
int page_index = 0;
|
||||
int changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE;
|
||||
Button *edit = nullptr;
|
||||
PanelContainer *container = nullptr;
|
||||
VBoxContainer *property_vbox = nullptr;
|
||||
PanelContainer *add_panel = nullptr;
|
||||
EditorSpinSlider *size_sliderv = nullptr;
|
||||
Button *button_add_item = nullptr;
|
||||
EditorPaginator *paginator = nullptr;
|
||||
LocalVector<Slot> slots;
|
||||
void _create_new_property_slot(int p_idx);
|
||||
|
||||
void _page_changed(int p_page);
|
||||
void _edit_pressed();
|
||||
void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false);
|
||||
void _change_type(Object *p_button, int p_slot_index);
|
||||
void _change_type_menu(int p_index);
|
||||
|
||||
void _add_key_value();
|
||||
void _object_id_selected(const StringName &p_property, ObjectID p_id);
|
||||
void _remove_pressed(int p_slot_index);
|
||||
|
||||
Variant::Type key_subtype;
|
||||
PropertyHint key_subtype_hint;
|
||||
String key_subtype_hint_string;
|
||||
Variant::Type value_subtype;
|
||||
PropertyHint value_subtype_hint;
|
||||
String value_subtype_hint_string;
|
||||
void initialize_dictionary(Variant &p_dictionary);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(PropertyHint p_hint, const String &p_hint_string = "");
|
||||
void set_preview_value(bool p_preview_value);
|
||||
virtual void update_property() override;
|
||||
virtual bool is_colored(ColorationMode p_mode) override;
|
||||
EditorPropertyDictionary();
|
||||
};
|
||||
|
||||
class EditorPropertyLocalizableString : public EditorProperty {
|
||||
GDCLASS(EditorPropertyLocalizableString, EditorProperty);
|
||||
|
||||
EditorLocaleDialog *locale_select = nullptr;
|
||||
|
||||
bool updating;
|
||||
|
||||
Ref<EditorPropertyDictionaryObject> object;
|
||||
int page_length = 20;
|
||||
int page_index = 0;
|
||||
Button *edit = nullptr;
|
||||
MarginContainer *container = nullptr;
|
||||
VBoxContainer *property_vbox = nullptr;
|
||||
EditorSpinSlider *size_slider = nullptr;
|
||||
Button *button_add_item = nullptr;
|
||||
EditorPaginator *paginator = nullptr;
|
||||
|
||||
void _page_changed(int p_page);
|
||||
void _edit_pressed();
|
||||
void _remove_item(Object *p_button, int p_index);
|
||||
void _property_changed(const String &p_property, const Variant &p_value, const String &p_name = "", bool p_changing = false);
|
||||
|
||||
void _add_locale_popup();
|
||||
void _add_locale(const String &p_locale);
|
||||
void _object_id_selected(const StringName &p_property, ObjectID p_id);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyLocalizableString();
|
||||
};
|
||||
268
editor/inspector/editor_properties_vector.cpp
Normal file
268
editor/inspector/editor_properties_vector.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/**************************************************************************/
|
||||
/* editor_properties_vector.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_properties_vector.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_spin_slider.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/texture_button.h"
|
||||
|
||||
const String EditorPropertyVectorN::COMPONENT_LABELS[4] = { "x", "y", "z", "w" };
|
||||
|
||||
void EditorPropertyVectorN::_set_read_only(bool p_read_only) {
|
||||
for (EditorSpinSlider *spin : spin_sliders) {
|
||||
spin->set_read_only(p_read_only);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::_value_changed(double val, const String &p_name) {
|
||||
if (linked->is_pressed()) {
|
||||
int changed_component = -1;
|
||||
for (int i = 0; i < component_count; i++) {
|
||||
if (p_name == COMPONENT_LABELS[i]) {
|
||||
changed_component = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DEV_ASSERT(changed_component >= 0);
|
||||
|
||||
for (int i = 0; i < component_count - 1; i++) {
|
||||
int slider_idx = (changed_component + 1 + i) % component_count;
|
||||
int ratio_idx = changed_component * (component_count - 1) + i;
|
||||
|
||||
if (ratio[ratio_idx] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_sliders[slider_idx]->set_value_no_signal(spin_sliders[changed_component]->get_value() * ratio[ratio_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
Variant v;
|
||||
Callable::CallError cerror;
|
||||
Variant::construct(vector_type, v, nullptr, 0, cerror);
|
||||
|
||||
for (int i = 0; i < component_count; i++) {
|
||||
if (radians_as_degrees) {
|
||||
v.set(i, Math::deg_to_rad(spin_sliders[i]->get_value()));
|
||||
} else {
|
||||
v.set(i, spin_sliders[i]->get_value());
|
||||
}
|
||||
}
|
||||
emit_changed(get_edited_property(), v, linked->is_pressed() ? "" : p_name);
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::update_property() {
|
||||
Variant val = get_edited_property_value();
|
||||
for (int i = 0; i < component_count; i++) {
|
||||
if (radians_as_degrees) {
|
||||
spin_sliders[i]->set_value_no_signal(Math::rad_to_deg((real_t)val.get(i)));
|
||||
} else {
|
||||
spin_sliders[i]->set_value_no_signal(val.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_grabbed) {
|
||||
_update_ratio();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::_update_ratio() {
|
||||
linked->set_modulate(Color(1, 1, 1, linked->is_pressed() ? 1.0 : 0.5));
|
||||
|
||||
double *ratio_write = ratio.ptrw();
|
||||
for (int i = 0; i < ratio.size(); i++) {
|
||||
int base_slider_idx = i / (component_count - 1);
|
||||
int secondary_slider_idx = ((base_slider_idx + 1) + i % (component_count - 1)) % component_count;
|
||||
|
||||
if (spin_sliders[base_slider_idx]->get_value() != 0) {
|
||||
ratio_write[i] = spin_sliders[secondary_slider_idx]->get_value() / spin_sliders[base_slider_idx]->get_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::_store_link(bool p_linked) {
|
||||
if (!get_edited_object()) {
|
||||
return;
|
||||
}
|
||||
const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
|
||||
EditorSettings::get_singleton()->set_project_metadata("linked_properties", key, p_linked);
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::_grab_changed(bool p_grab) {
|
||||
if (p_grab) {
|
||||
_update_ratio();
|
||||
}
|
||||
is_grabbed = p_grab;
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
if (linked->is_visible()) {
|
||||
if (get_edited_object()) {
|
||||
const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
|
||||
linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
|
||||
_update_ratio();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
|
||||
|
||||
linked->set_texture_normal(get_editor_theme_icon(SNAME("Unlinked")));
|
||||
linked->set_texture_pressed(get_editor_theme_icon(SNAME("Instance")));
|
||||
linked->set_custom_minimum_size(Size2(icon_size + 8 * EDSCALE, 0));
|
||||
|
||||
const Color *colors = _get_property_colors();
|
||||
for (int i = 0; i < component_count; i++) {
|
||||
spin_sliders[i]->add_theme_color_override("label_color", colors[i]);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVectorN::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link, const String &p_suffix, bool p_radians_as_degrees, bool p_is_int) {
|
||||
radians_as_degrees = p_radians_as_degrees;
|
||||
|
||||
for (EditorSpinSlider *spin : spin_sliders) {
|
||||
spin->set_min(p_min);
|
||||
spin->set_max(p_max);
|
||||
spin->set_step(p_step);
|
||||
spin->set_hide_slider(p_hide_slider);
|
||||
spin->set_allow_greater(true);
|
||||
spin->set_allow_lesser(true);
|
||||
spin->set_suffix(p_suffix);
|
||||
spin->set_editing_integer(p_is_int);
|
||||
}
|
||||
|
||||
if (!p_link) {
|
||||
linked->hide();
|
||||
}
|
||||
}
|
||||
|
||||
EditorPropertyVectorN::EditorPropertyVectorN(Variant::Type p_type, bool p_force_wide, bool p_horizontal) {
|
||||
vector_type = p_type;
|
||||
switch (vector_type) {
|
||||
case Variant::VECTOR2:
|
||||
case Variant::VECTOR2I:
|
||||
component_count = 2;
|
||||
break;
|
||||
|
||||
case Variant::VECTOR3:
|
||||
case Variant::VECTOR3I:
|
||||
component_count = 3;
|
||||
break;
|
||||
|
||||
case Variant::VECTOR4:
|
||||
case Variant::VECTOR4I:
|
||||
component_count = 4;
|
||||
break;
|
||||
|
||||
default: // Needed to silence a warning.
|
||||
ERR_PRINT("Not a Vector type.");
|
||||
break;
|
||||
}
|
||||
bool horizontal = p_force_wide || p_horizontal;
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
hb->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
BoxContainer *bc;
|
||||
|
||||
if (p_force_wide) {
|
||||
bc = memnew(HBoxContainer);
|
||||
hb->add_child(bc);
|
||||
} else if (horizontal) {
|
||||
bc = memnew(HBoxContainer);
|
||||
hb->add_child(bc);
|
||||
set_bottom_editor(hb);
|
||||
} else {
|
||||
bc = memnew(VBoxContainer);
|
||||
hb->add_child(bc);
|
||||
}
|
||||
bc->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
spin_sliders.resize(component_count);
|
||||
EditorSpinSlider **spin = spin_sliders.ptrw();
|
||||
|
||||
for (int i = 0; i < component_count; i++) {
|
||||
spin[i] = memnew(EditorSpinSlider);
|
||||
bc->add_child(spin[i]);
|
||||
spin[i]->set_flat(true);
|
||||
spin[i]->set_label(String(COMPONENT_LABELS[i]));
|
||||
spin[i]->set_accessibility_name(String(COMPONENT_LABELS[i]));
|
||||
if (horizontal) {
|
||||
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
}
|
||||
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyVectorN::_value_changed).bind(String(COMPONENT_LABELS[i])));
|
||||
spin[i]->connect(SNAME("grabbed"), callable_mp(this, &EditorPropertyVectorN::_grab_changed).bind(true));
|
||||
spin[i]->connect(SNAME("ungrabbed"), callable_mp(this, &EditorPropertyVectorN::_grab_changed).bind(false));
|
||||
add_focusable(spin[i]);
|
||||
}
|
||||
|
||||
ratio.resize(component_count * (component_count - 1));
|
||||
ratio.fill(1.0);
|
||||
|
||||
linked = memnew(TextureButton);
|
||||
linked->set_toggle_mode(true);
|
||||
linked->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED);
|
||||
linked->set_tooltip_text(TTR("Lock/Unlock Component Ratio"));
|
||||
linked->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyVectorN::_update_ratio));
|
||||
linked->connect(SceneStringName(toggled), callable_mp(this, &EditorPropertyVectorN::_store_link));
|
||||
hb->add_child(linked);
|
||||
|
||||
add_child(hb);
|
||||
if (!horizontal) {
|
||||
set_label_reference(spin_sliders[0]); // Show text and buttons around this.
|
||||
}
|
||||
}
|
||||
|
||||
EditorPropertyVector2::EditorPropertyVector2(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR2, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector2_editing")) {}
|
||||
|
||||
EditorPropertyVector2i::EditorPropertyVector2i(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR2I, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector2_editing")) {}
|
||||
|
||||
EditorPropertyVector3::EditorPropertyVector3(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR3, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector_types_editing")) {}
|
||||
|
||||
EditorPropertyVector3i::EditorPropertyVector3i(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR3I, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector_types_editing")) {}
|
||||
|
||||
EditorPropertyVector4::EditorPropertyVector4(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR4, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector_types_editing")) {}
|
||||
|
||||
EditorPropertyVector4i::EditorPropertyVector4i(bool p_force_wide) :
|
||||
EditorPropertyVectorN(Variant::VECTOR4I, p_force_wide, EDITOR_GET("interface/inspector/horizontal_vector_types_editing")) {}
|
||||
108
editor/inspector/editor_properties_vector.h
Normal file
108
editor/inspector/editor_properties_vector.h
Normal file
@@ -0,0 +1,108 @@
|
||||
/**************************************************************************/
|
||||
/* editor_properties_vector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
|
||||
class EditorSpinSlider;
|
||||
class TextureButton;
|
||||
|
||||
class EditorPropertyVectorN : public EditorProperty {
|
||||
GDCLASS(EditorPropertyVectorN, EditorProperty);
|
||||
|
||||
static const String COMPONENT_LABELS[4];
|
||||
|
||||
int component_count = 0;
|
||||
Variant::Type vector_type;
|
||||
|
||||
Vector<EditorSpinSlider *> spin_sliders;
|
||||
TextureButton *linked = nullptr;
|
||||
Vector<double> ratio;
|
||||
bool is_grabbed = false;
|
||||
|
||||
bool radians_as_degrees = false;
|
||||
|
||||
void _update_ratio();
|
||||
void _store_link(bool p_linked);
|
||||
void _grab_changed(bool p_grab);
|
||||
void _value_changed(double p_val, const String &p_name);
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
void setup(double p_min, double p_max, double p_step = 1.0, bool p_hide_slider = true, bool p_link = false, const String &p_suffix = String(), bool p_radians_as_degrees = false, bool p_is_int = false);
|
||||
EditorPropertyVectorN(Variant::Type p_type, bool p_force_wide, bool p_horizontal);
|
||||
};
|
||||
|
||||
class EditorPropertyVector2 : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector2, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector2(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyVector2i : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector2i, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector2i(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyVector3 : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector3, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector3(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyVector3i : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector3i, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector3i(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyVector4 : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector4, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector4(bool p_force_wide = false);
|
||||
};
|
||||
|
||||
class EditorPropertyVector4i : public EditorPropertyVectorN {
|
||||
GDCLASS(EditorPropertyVector4i, EditorPropertyVectorN);
|
||||
|
||||
public:
|
||||
EditorPropertyVector4i(bool p_force_wide = false);
|
||||
};
|
||||
374
editor/inspector/editor_property_name_processor.cpp
Normal file
374
editor/inspector/editor_property_name_processor.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
/**************************************************************************/
|
||||
/* editor_property_name_processor.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_property_name_processor.h"
|
||||
|
||||
#include "core/string/translation_server.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
|
||||
EditorPropertyNameProcessor *EditorPropertyNameProcessor::singleton = nullptr;
|
||||
|
||||
EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_default_inspector_style() {
|
||||
if (!EditorSettings::get_singleton()) {
|
||||
return STYLE_CAPITALIZED;
|
||||
}
|
||||
const Style style = (Style)EDITOR_GET("interface/inspector/default_property_name_style").operator int();
|
||||
if (style == STYLE_LOCALIZED && !is_localization_available()) {
|
||||
return STYLE_CAPITALIZED;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_settings_style() {
|
||||
if (!EditorSettings::get_singleton()) {
|
||||
return STYLE_LOCALIZED;
|
||||
}
|
||||
const bool translate = EDITOR_GET("interface/editor/localize_settings");
|
||||
return translate ? STYLE_LOCALIZED : STYLE_CAPITALIZED;
|
||||
}
|
||||
|
||||
EditorPropertyNameProcessor::Style EditorPropertyNameProcessor::get_tooltip_style(Style p_style) {
|
||||
return p_style == STYLE_LOCALIZED ? STYLE_CAPITALIZED : STYLE_LOCALIZED;
|
||||
}
|
||||
|
||||
bool EditorPropertyNameProcessor::is_localization_available() {
|
||||
return EditorSettings::get_singleton() && EDITOR_GET("interface/editor/editor_language") != "en";
|
||||
}
|
||||
|
||||
String EditorPropertyNameProcessor::_capitalize_name(const String &p_name) const {
|
||||
HashMap<String, String>::ConstIterator cached = capitalize_string_cache.find(p_name);
|
||||
if (cached) {
|
||||
return cached->value;
|
||||
}
|
||||
|
||||
Vector<String> parts = p_name.split("_", false);
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
// Articles/conjunctions/prepositions which should only be capitalized when not at beginning and end.
|
||||
if (i > 0 && i + 1 < parts.size() && stop_words.has(parts[i])) {
|
||||
continue;
|
||||
}
|
||||
HashMap<String, String>::ConstIterator remap = capitalize_string_remaps.find(parts[i]);
|
||||
if (remap) {
|
||||
parts.write[i] = remap->value;
|
||||
} else {
|
||||
parts.write[i] = parts[i].capitalize();
|
||||
}
|
||||
}
|
||||
const String capitalized = String(" ").join(parts);
|
||||
|
||||
capitalize_string_cache[p_name] = capitalized;
|
||||
return capitalized;
|
||||
}
|
||||
|
||||
StringName EditorPropertyNameProcessor::_get_context(const String &p_name, const String &p_property, const StringName &p_class) const {
|
||||
if (p_property.is_empty() && p_class == StringName()) {
|
||||
return StringName();
|
||||
}
|
||||
const HashMap<String, StringName> *context_map = translation_contexts.getptr(p_name);
|
||||
if (context_map == nullptr) {
|
||||
return StringName();
|
||||
}
|
||||
// It's expected that full property path is enough to distinguish between usages.
|
||||
// In case a class name is needed, all usages should be prefixed with the class name.
|
||||
const StringName *context = context_map->getptr(p_property);
|
||||
if (context == nullptr && p_class != StringName()) {
|
||||
context = context_map->getptr(String(p_class) + "::" + p_property);
|
||||
}
|
||||
if (context == nullptr) {
|
||||
return StringName();
|
||||
}
|
||||
return *context;
|
||||
}
|
||||
|
||||
String EditorPropertyNameProcessor::process_name(const String &p_name, Style p_style, const String &p_property, const StringName &p_class) const {
|
||||
switch (p_style) {
|
||||
case STYLE_RAW: {
|
||||
return p_name;
|
||||
} break;
|
||||
|
||||
case STYLE_CAPITALIZED: {
|
||||
return _capitalize_name(p_name);
|
||||
} break;
|
||||
|
||||
case STYLE_LOCALIZED: {
|
||||
const String capitalized = _capitalize_name(p_name);
|
||||
if (TranslationServer::get_singleton()) {
|
||||
return TranslationServer::get_singleton()->property_translate(capitalized, _get_context(p_name, p_property, p_class));
|
||||
}
|
||||
return capitalized;
|
||||
} break;
|
||||
}
|
||||
ERR_FAIL_V_MSG(p_name, "Unexpected property name style.");
|
||||
}
|
||||
|
||||
String EditorPropertyNameProcessor::translate_group_name(const String &p_name) const {
|
||||
if (TranslationServer::get_singleton()) {
|
||||
return TranslationServer::get_singleton()->property_translate(p_name);
|
||||
}
|
||||
return p_name;
|
||||
}
|
||||
|
||||
EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
|
||||
// The following initialization is parsed by the l10n extraction script with a regex.
|
||||
// The map name and value definition format should be kept synced with the regex.
|
||||
// https://github.com/godotengine/godot-editor-l10n/blob/main/scripts/common.py
|
||||
capitalize_string_remaps["2d"] = "2D";
|
||||
capitalize_string_remaps["3d"] = "3D";
|
||||
capitalize_string_remaps["4d"] = "4D";
|
||||
capitalize_string_remaps["aa"] = "AA";
|
||||
capitalize_string_remaps["aabb"] = "AABB";
|
||||
capitalize_string_remaps["adb"] = "ADB";
|
||||
capitalize_string_remaps["ao"] = "AO";
|
||||
capitalize_string_remaps["api"] = "API";
|
||||
capitalize_string_remaps["apk"] = "APK";
|
||||
capitalize_string_remaps["arm32"] = "arm32";
|
||||
capitalize_string_remaps["arm64"] = "arm64";
|
||||
capitalize_string_remaps["arm64-v8a"] = "arm64-v8a";
|
||||
capitalize_string_remaps["armeabi-v7a"] = "armeabi-v7a";
|
||||
capitalize_string_remaps["arvr"] = "ARVR";
|
||||
capitalize_string_remaps["astc"] = "ASTC";
|
||||
capitalize_string_remaps["bbcode"] = "BBCode";
|
||||
capitalize_string_remaps["bg"] = "BG";
|
||||
capitalize_string_remaps["bidi"] = "BiDi";
|
||||
capitalize_string_remaps["bp"] = "BP";
|
||||
capitalize_string_remaps["bpc"] = "BPC";
|
||||
capitalize_string_remaps["bpm"] = "BPM";
|
||||
capitalize_string_remaps["bptc"] = "BPTC";
|
||||
capitalize_string_remaps["bvh"] = "BVH";
|
||||
capitalize_string_remaps["ca"] = "CA";
|
||||
capitalize_string_remaps["ccdik"] = "CCDIK";
|
||||
capitalize_string_remaps["cd"] = "CD";
|
||||
capitalize_string_remaps["cpu"] = "CPU";
|
||||
capitalize_string_remaps["csg"] = "CSG";
|
||||
capitalize_string_remaps["d3d12"] = "D3D12";
|
||||
capitalize_string_remaps["db"] = "dB";
|
||||
capitalize_string_remaps["dof"] = "DoF";
|
||||
capitalize_string_remaps["dpi"] = "DPI";
|
||||
capitalize_string_remaps["dtls"] = "DTLS";
|
||||
capitalize_string_remaps["eol"] = "EOL";
|
||||
capitalize_string_remaps["erp"] = "ERP";
|
||||
capitalize_string_remaps["etc2"] = "ETC2";
|
||||
capitalize_string_remaps["fabrik"] = "FABRIK";
|
||||
capitalize_string_remaps["fbx"] = "FBX";
|
||||
capitalize_string_remaps["fbx2gltf"] = "FBX2glTF";
|
||||
capitalize_string_remaps["fft"] = "FFT";
|
||||
capitalize_string_remaps["fg"] = "FG";
|
||||
capitalize_string_remaps["filesystem"] = "FileSystem";
|
||||
capitalize_string_remaps["fov"] = "FOV";
|
||||
capitalize_string_remaps["fps"] = "FPS";
|
||||
capitalize_string_remaps["fs"] = "FS";
|
||||
capitalize_string_remaps["fsr"] = "FSR";
|
||||
capitalize_string_remaps["fxaa"] = "FXAA";
|
||||
capitalize_string_remaps["gdscript"] = "GDScript";
|
||||
capitalize_string_remaps["ggx"] = "GGX";
|
||||
capitalize_string_remaps["gi"] = "GI";
|
||||
capitalize_string_remaps["gl"] = "GL";
|
||||
capitalize_string_remaps["glb"] = "GLB";
|
||||
capitalize_string_remaps["gles"] = "GLES";
|
||||
capitalize_string_remaps["gles2"] = "GLES2";
|
||||
capitalize_string_remaps["gles3"] = "GLES3";
|
||||
capitalize_string_remaps["gltf"] = "glTF";
|
||||
capitalize_string_remaps["gridmap"] = "GridMap";
|
||||
capitalize_string_remaps["gpu"] = "GPU";
|
||||
capitalize_string_remaps["gui"] = "GUI";
|
||||
capitalize_string_remaps["guid"] = "GUID";
|
||||
capitalize_string_remaps["hdr"] = "HDR";
|
||||
capitalize_string_remaps["hidpi"] = "hiDPI";
|
||||
capitalize_string_remaps["hipass"] = "High-pass";
|
||||
capitalize_string_remaps["hl"] = "HL";
|
||||
capitalize_string_remaps["hsv"] = "HSV";
|
||||
capitalize_string_remaps["html"] = "HTML";
|
||||
capitalize_string_remaps["http"] = "HTTP";
|
||||
capitalize_string_remaps["id"] = "ID";
|
||||
capitalize_string_remaps["ids"] = "IDs";
|
||||
capitalize_string_remaps["igd"] = "IGD";
|
||||
capitalize_string_remaps["ik"] = "IK";
|
||||
capitalize_string_remaps["image@2x"] = "Image @2x";
|
||||
capitalize_string_remaps["image@3x"] = "Image @3x";
|
||||
capitalize_string_remaps["iod"] = "IOD";
|
||||
capitalize_string_remaps["ios"] = "iOS";
|
||||
capitalize_string_remaps["ip"] = "IP";
|
||||
capitalize_string_remaps["ipad"] = "iPad";
|
||||
capitalize_string_remaps["iphone"] = "iPhone";
|
||||
capitalize_string_remaps["ipv6"] = "IPv6";
|
||||
capitalize_string_remaps["ir"] = "IR";
|
||||
capitalize_string_remaps["itunes"] = "iTunes";
|
||||
capitalize_string_remaps["jit"] = "JIT";
|
||||
capitalize_string_remaps["k1"] = "K1";
|
||||
capitalize_string_remaps["k2"] = "K2";
|
||||
capitalize_string_remaps["kb"] = "(KB)"; // Unit.
|
||||
capitalize_string_remaps["lcd"] = "LCD";
|
||||
capitalize_string_remaps["ldr"] = "LDR";
|
||||
capitalize_string_remaps["linuxbsd"] = "Linux/*BSD";
|
||||
capitalize_string_remaps["lod"] = "LOD";
|
||||
capitalize_string_remaps["lods"] = "LODs";
|
||||
capitalize_string_remaps["loongarch64"] = "loongarch64";
|
||||
capitalize_string_remaps["lowpass"] = "Low-pass";
|
||||
capitalize_string_remaps["macos"] = "macOS";
|
||||
capitalize_string_remaps["mb"] = "(MB)"; // Unit.
|
||||
capitalize_string_remaps["mjpeg"] = "MJPEG";
|
||||
capitalize_string_remaps["mms"] = "MMS";
|
||||
capitalize_string_remaps["ms"] = "(ms)"; // Unit
|
||||
capitalize_string_remaps["msaa"] = "MSAA";
|
||||
capitalize_string_remaps["msdf"] = "MSDF";
|
||||
// Not used for now as AudioEffectReverb has a `msec` property.
|
||||
//capitalize_string_remaps["msec"] = "(msec)"; // Unit.
|
||||
capitalize_string_remaps["navmesh"] = "NavMesh";
|
||||
capitalize_string_remaps["nfc"] = "NFC";
|
||||
capitalize_string_remaps["ogv"] = "OGV";
|
||||
capitalize_string_remaps["oidn"] = "OIDN";
|
||||
capitalize_string_remaps["ok"] = "OK";
|
||||
capitalize_string_remaps["opengl"] = "OpenGL";
|
||||
capitalize_string_remaps["opengl3"] = "OpenGL 3";
|
||||
capitalize_string_remaps["opentype"] = "OpenType";
|
||||
capitalize_string_remaps["openxr"] = "OpenXR";
|
||||
capitalize_string_remaps["osslsigncode"] = "osslsigncode";
|
||||
capitalize_string_remaps["pck"] = "PCK";
|
||||
capitalize_string_remaps["png"] = "PNG";
|
||||
capitalize_string_remaps["po2"] = "(Power of 2)"; // Unit.
|
||||
capitalize_string_remaps["ppc64"] = "ppc64";
|
||||
capitalize_string_remaps["pvrtc"] = "PVRTC";
|
||||
capitalize_string_remaps["pvs"] = "PVS";
|
||||
capitalize_string_remaps["rcodesign"] = "rcodesign";
|
||||
capitalize_string_remaps["rdo"] = "RDO";
|
||||
capitalize_string_remaps["rgb"] = "RGB";
|
||||
capitalize_string_remaps["rid"] = "RID";
|
||||
capitalize_string_remaps["rmb"] = "RMB";
|
||||
capitalize_string_remaps["rpc"] = "RPC";
|
||||
capitalize_string_remaps["rv64"] = "rv64";
|
||||
capitalize_string_remaps["s3tc"] = "S3TC";
|
||||
capitalize_string_remaps["scp"] = "SCP";
|
||||
capitalize_string_remaps["sdf"] = "SDF";
|
||||
capitalize_string_remaps["sdfgi"] = "SDFGI";
|
||||
capitalize_string_remaps["sdk"] = "SDK";
|
||||
capitalize_string_remaps["sec"] = "(sec)"; // Unit.
|
||||
capitalize_string_remaps["signtool"] = "signtool";
|
||||
capitalize_string_remaps["smaa"] = "SMAA";
|
||||
capitalize_string_remaps["sms"] = "SMS";
|
||||
capitalize_string_remaps["srgb"] = "sRGB";
|
||||
capitalize_string_remaps["ssao"] = "SSAO";
|
||||
capitalize_string_remaps["ssh"] = "SSH";
|
||||
capitalize_string_remaps["ssil"] = "SSIL";
|
||||
capitalize_string_remaps["ssl"] = "SSL";
|
||||
capitalize_string_remaps["sss"] = "SSS";
|
||||
capitalize_string_remaps["stderr"] = "stderr";
|
||||
capitalize_string_remaps["stdout"] = "stdout";
|
||||
capitalize_string_remaps["sv"] = "SV";
|
||||
capitalize_string_remaps["svg"] = "SVG";
|
||||
capitalize_string_remaps["taa"] = "TAA";
|
||||
capitalize_string_remaps["tcp"] = "TCP";
|
||||
capitalize_string_remaps["textfile"] = "TextFile";
|
||||
capitalize_string_remaps["tls"] = "TLS";
|
||||
capitalize_string_remaps["tv"] = "TV";
|
||||
capitalize_string_remaps["tvos"] = "tvOS";
|
||||
capitalize_string_remaps["uastc"] = "UASTC";
|
||||
capitalize_string_remaps["ui"] = "UI";
|
||||
capitalize_string_remaps["uri"] = "URI";
|
||||
capitalize_string_remaps["url"] = "URL";
|
||||
capitalize_string_remaps["urls"] = "URLs";
|
||||
capitalize_string_remaps["us"] = U"(µs)"; // Unit.
|
||||
capitalize_string_remaps["usb"] = "USB";
|
||||
capitalize_string_remaps["usec"] = U"(µsec)"; // Unit.
|
||||
capitalize_string_remaps["uid"] = "UID";
|
||||
capitalize_string_remaps["uuid"] = "UUID";
|
||||
capitalize_string_remaps["uv"] = "UV";
|
||||
capitalize_string_remaps["uv1"] = "UV1";
|
||||
capitalize_string_remaps["uv2"] = "UV2";
|
||||
capitalize_string_remaps["vector2"] = "Vector2";
|
||||
capitalize_string_remaps["visionos"] = "visionOS";
|
||||
capitalize_string_remaps["vpn"] = "VPN";
|
||||
capitalize_string_remaps["vram"] = "VRAM";
|
||||
capitalize_string_remaps["vrs"] = "VRS";
|
||||
capitalize_string_remaps["vsync"] = "V-Sync";
|
||||
capitalize_string_remaps["wap"] = "WAP";
|
||||
capitalize_string_remaps["webp"] = "WebP";
|
||||
capitalize_string_remaps["webrtc"] = "WebRTC";
|
||||
capitalize_string_remaps["websocket"] = "WebSocket";
|
||||
capitalize_string_remaps["wintab"] = "WinTab";
|
||||
capitalize_string_remaps["winink"] = "Windows Ink";
|
||||
capitalize_string_remaps["wifi"] = "Wi-Fi";
|
||||
capitalize_string_remaps["x86"] = "x86";
|
||||
capitalize_string_remaps["x86_32"] = "x86_32";
|
||||
capitalize_string_remaps["x86_64"] = "x86_64";
|
||||
capitalize_string_remaps["xr"] = "XR";
|
||||
capitalize_string_remaps["xray"] = "X-Ray";
|
||||
capitalize_string_remaps["xy"] = "XY";
|
||||
capitalize_string_remaps["xz"] = "XZ";
|
||||
capitalize_string_remaps["yz"] = "YZ";
|
||||
|
||||
// Articles, conjunctions, prepositions.
|
||||
// The following initialization is parsed in `editor/translations/scripts/common.py` with a regex.
|
||||
// The word definition format should be kept synced with the regex.
|
||||
stop_words = LocalVector<String>({
|
||||
"a",
|
||||
"an",
|
||||
"and",
|
||||
"as",
|
||||
"at",
|
||||
"by",
|
||||
"for",
|
||||
"in",
|
||||
"not",
|
||||
"of",
|
||||
"on",
|
||||
"or",
|
||||
"over",
|
||||
"per",
|
||||
"the",
|
||||
"then",
|
||||
"to",
|
||||
});
|
||||
|
||||
// Translation context associated with a name.
|
||||
// The second key is either:
|
||||
// - `full/property/path`
|
||||
// - `Class::full/property/path`
|
||||
// In case a class name is needed to distinguish between usages, all usages should use the second format.
|
||||
//
|
||||
// The following initialization is parsed in `editor/translations/scripts/common.py` with a regex.
|
||||
// The map name and value definition format should be kept synced with the regex.
|
||||
translation_contexts["force"]["constant_force"] = "Physics";
|
||||
translation_contexts["force"]["force/8_bit"] = "Enforce";
|
||||
translation_contexts["force"]["force/mono"] = "Enforce";
|
||||
translation_contexts["force"]["force/max_rate"] = "Enforce";
|
||||
translation_contexts["force"]["force/max_rate_hz"] = "Enforce";
|
||||
translation_contexts["normal"]["theme_override_styles/normal"] = "Ordinary";
|
||||
translation_contexts["normal"]["TextureButton::texture_normal"] = "Ordinary";
|
||||
translation_contexts["normal"]["Decal::texture_normal"] = "Geometry";
|
||||
translation_contexts["normal"]["detail_normal"] = "Geometry";
|
||||
translation_contexts["normal"]["normal"] = "Geometry";
|
||||
}
|
||||
|
||||
EditorPropertyNameProcessor::~EditorPropertyNameProcessor() {
|
||||
singleton = nullptr;
|
||||
}
|
||||
77
editor/inspector/editor_property_name_processor.h
Normal file
77
editor/inspector/editor_property_name_processor.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************/
|
||||
/* editor_property_name_processor.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class EditorPropertyNameProcessor : public Node {
|
||||
GDCLASS(EditorPropertyNameProcessor, Node);
|
||||
|
||||
static EditorPropertyNameProcessor *singleton;
|
||||
|
||||
mutable HashMap<String, String> capitalize_string_cache;
|
||||
HashMap<String, String> capitalize_string_remaps;
|
||||
LocalVector<String> stop_words; // Exceptions that shouldn't be capitalized.
|
||||
|
||||
HashMap<String, HashMap<String, StringName>> translation_contexts;
|
||||
|
||||
// Capitalizes property path segments.
|
||||
String _capitalize_name(const String &p_name) const;
|
||||
|
||||
// Returns the translation context for the given name.
|
||||
StringName _get_context(const String &p_name, const String &p_property, const StringName &p_class) const;
|
||||
|
||||
public:
|
||||
// Matches `interface/inspector/default_property_name_style` editor setting.
|
||||
enum Style {
|
||||
STYLE_RAW,
|
||||
STYLE_CAPITALIZED,
|
||||
STYLE_LOCALIZED,
|
||||
};
|
||||
|
||||
static EditorPropertyNameProcessor *get_singleton() { return singleton; }
|
||||
|
||||
static Style get_default_inspector_style();
|
||||
static Style get_settings_style();
|
||||
static Style get_tooltip_style(Style p_style);
|
||||
|
||||
static bool is_localization_available();
|
||||
|
||||
// Turns property path segment into the given style.
|
||||
// `p_class` and `p_property` are only used for `STYLE_LOCALIZED`, associating the name with a translation context.
|
||||
String process_name(const String &p_name, Style p_style, const String &p_property = "", const StringName &p_class = "") const;
|
||||
|
||||
// Translate plain text group names.
|
||||
String translate_group_name(const String &p_name) const;
|
||||
|
||||
EditorPropertyNameProcessor();
|
||||
~EditorPropertyNameProcessor();
|
||||
};
|
||||
1463
editor/inspector/editor_resource_picker.cpp
Normal file
1463
editor/inspector/editor_resource_picker.cpp
Normal file
File diff suppressed because it is too large
Load Diff
216
editor/inspector/editor_resource_picker.h
Normal file
216
editor/inspector/editor_resource_picker.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/**************************************************************************/
|
||||
/* editor_resource_picker.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
|
||||
class Button;
|
||||
class ConfirmationDialog;
|
||||
class EditorFileDialog;
|
||||
class PopupMenu;
|
||||
class TextureRect;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
|
||||
class EditorResourcePicker : public HBoxContainer {
|
||||
GDCLASS(EditorResourcePicker, HBoxContainer);
|
||||
|
||||
String base_type;
|
||||
Ref<Resource> edited_resource;
|
||||
|
||||
bool editable = true;
|
||||
bool dropping = false;
|
||||
|
||||
Vector<String> inheritors_array;
|
||||
mutable HashSet<StringName> allowed_types_without_convert;
|
||||
mutable HashSet<StringName> allowed_types_with_convert;
|
||||
|
||||
Button *assign_button = nullptr;
|
||||
TextureRect *preview_rect = nullptr;
|
||||
Button *edit_button = nullptr;
|
||||
Button *quick_load_button = nullptr;
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
|
||||
ConfirmationDialog *duplicate_resources_dialog = nullptr;
|
||||
Tree *duplicate_resources_tree = nullptr;
|
||||
|
||||
Size2i assign_button_min_size = Size2i(1, 1);
|
||||
|
||||
enum MenuOption {
|
||||
OBJ_MENU_LOAD,
|
||||
OBJ_MENU_QUICKLOAD,
|
||||
OBJ_MENU_INSPECT,
|
||||
OBJ_MENU_CLEAR,
|
||||
OBJ_MENU_MAKE_UNIQUE,
|
||||
OBJ_MENU_MAKE_UNIQUE_RECURSIVE,
|
||||
OBJ_MENU_SAVE,
|
||||
OBJ_MENU_SAVE_AS,
|
||||
OBJ_MENU_COPY,
|
||||
OBJ_MENU_PASTE,
|
||||
OBJ_MENU_PASTE_AS_UNIQUE,
|
||||
OBJ_MENU_SHOW_IN_FILE_SYSTEM,
|
||||
|
||||
TYPE_BASE_ID = 100,
|
||||
CONVERT_BASE_ID = 1000,
|
||||
};
|
||||
|
||||
Object *resource_owner = nullptr;
|
||||
|
||||
PopupMenu *edit_menu = nullptr;
|
||||
|
||||
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
|
||||
|
||||
void _resource_selected();
|
||||
void _resource_changed();
|
||||
void _file_selected(const String &p_path);
|
||||
|
||||
void _resource_saved(Object *p_resource);
|
||||
|
||||
void _update_menu();
|
||||
void _update_menu_items();
|
||||
void _edit_menu_cbk(int p_which);
|
||||
|
||||
void _button_draw();
|
||||
void _button_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
String _get_owner_path() const;
|
||||
String _get_resource_type(const Ref<Resource> &p_resource) const;
|
||||
void _ensure_allowed_types() const;
|
||||
bool _is_drop_valid(const Dictionary &p_drag_data) const;
|
||||
bool _is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const;
|
||||
bool _is_custom_type_script() const;
|
||||
|
||||
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
|
||||
void _ensure_resource_menu();
|
||||
void _gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name = "") const;
|
||||
void _duplicate_selected_resources();
|
||||
|
||||
protected:
|
||||
virtual void _update_resource();
|
||||
|
||||
Button *get_assign_button() { return assign_button; }
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
void set_assign_button_min_size(const Size2i &p_size);
|
||||
|
||||
GDVIRTUAL1(_set_create_options, Object *)
|
||||
GDVIRTUAL1R(bool, _handle_menu_selected, int)
|
||||
|
||||
public:
|
||||
void set_base_type(const String &p_base_type);
|
||||
String get_base_type() const;
|
||||
Vector<String> get_allowed_types() const;
|
||||
|
||||
void set_edited_resource(Ref<Resource> p_resource);
|
||||
void set_edited_resource_no_check(Ref<Resource> p_resource);
|
||||
Ref<Resource> get_edited_resource();
|
||||
|
||||
void set_toggle_mode(bool p_enable);
|
||||
bool is_toggle_mode() const;
|
||||
void set_toggle_pressed(bool p_pressed);
|
||||
bool is_toggle_pressed() const;
|
||||
|
||||
void set_resource_owner(Object *p_object);
|
||||
|
||||
void set_editable(bool p_editable);
|
||||
bool is_editable() const;
|
||||
|
||||
virtual void set_create_options(Object *p_menu_node);
|
||||
virtual bool handle_menu_selected(int p_which);
|
||||
|
||||
EditorResourcePicker(bool p_hide_assign_button_controls = false);
|
||||
};
|
||||
|
||||
class EditorScriptPicker : public EditorResourcePicker {
|
||||
GDCLASS(EditorScriptPicker, EditorResourcePicker);
|
||||
|
||||
enum ExtraMenuOption {
|
||||
OBJ_MENU_NEW_SCRIPT = 50,
|
||||
OBJ_MENU_EXTEND_SCRIPT = 51
|
||||
};
|
||||
|
||||
Node *script_owner = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void set_create_options(Object *p_menu_node) override;
|
||||
virtual bool handle_menu_selected(int p_which) override;
|
||||
|
||||
void set_script_owner(Node *p_owner);
|
||||
Node *get_script_owner() const;
|
||||
};
|
||||
|
||||
class EditorShaderPicker : public EditorResourcePicker {
|
||||
GDCLASS(EditorShaderPicker, EditorResourcePicker);
|
||||
|
||||
enum ExtraMenuOption {
|
||||
OBJ_MENU_NEW_SHADER = 50,
|
||||
};
|
||||
|
||||
ShaderMaterial *edited_material = nullptr;
|
||||
int preferred_mode = -1;
|
||||
|
||||
public:
|
||||
virtual void set_create_options(Object *p_menu_node) override;
|
||||
virtual bool handle_menu_selected(int p_which) override;
|
||||
|
||||
void set_edited_material(ShaderMaterial *p_material);
|
||||
ShaderMaterial *get_edited_material() const;
|
||||
void set_preferred_mode(int p_preferred_mode);
|
||||
};
|
||||
|
||||
class EditorAudioStreamPicker : public EditorResourcePicker {
|
||||
GDCLASS(EditorAudioStreamPicker, EditorResourcePicker);
|
||||
|
||||
uint64_t last_preview_version = 0;
|
||||
Control *stream_preview_rect = nullptr;
|
||||
|
||||
enum {
|
||||
MAX_TAGGED_FRAMES = 8
|
||||
};
|
||||
float tagged_frame_offsets[MAX_TAGGED_FRAMES];
|
||||
uint32_t tagged_frame_offset_count = 0;
|
||||
|
||||
void _preview_draw();
|
||||
virtual void _update_resource() override;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
EditorAudioStreamPicker();
|
||||
};
|
||||
613
editor/inspector/editor_resource_preview.cpp
Normal file
613
editor/inspector/editor_resource_preview.cpp
Normal file
@@ -0,0 +1,613 @@
|
||||
/**************************************************************************/
|
||||
/* editor_resource_preview.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_resource_preview.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/variant/variant_utility.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/file_system/editor_paths.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "servers/rendering/rendering_server_globals.h"
|
||||
|
||||
bool EditorResourcePreviewGenerator::handles(const String &p_type) const {
|
||||
bool success = false;
|
||||
if (GDVIRTUAL_CALL(_handles, p_type, success)) {
|
||||
return success;
|
||||
}
|
||||
ERR_FAIL_V_MSG(false, "EditorResourcePreviewGenerator::_handles needs to be overridden.");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorResourcePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Texture2D> preview;
|
||||
if (GDVIRTUAL_CALL(_generate, p_from, p_size, p_metadata, preview)) {
|
||||
return preview;
|
||||
}
|
||||
ERR_FAIL_V_MSG(Ref<Texture2D>(), "EditorResourcePreviewGenerator::_generate needs to be overridden.");
|
||||
}
|
||||
|
||||
Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
|
||||
Ref<Texture2D> preview;
|
||||
if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, p_metadata, preview)) {
|
||||
return preview;
|
||||
}
|
||||
|
||||
Ref<Resource> res = ResourceLoader::load(p_path);
|
||||
if (res.is_null()) {
|
||||
return res;
|
||||
}
|
||||
return generate(res, p_size, p_metadata);
|
||||
}
|
||||
|
||||
bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const {
|
||||
bool success = false;
|
||||
GDVIRTUAL_CALL(_generate_small_preview_automatically, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EditorResourcePreviewGenerator::can_generate_small_preview() const {
|
||||
bool success = false;
|
||||
GDVIRTUAL_CALL(_can_generate_small_preview, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
void EditorResourcePreviewGenerator::_bind_methods() {
|
||||
GDVIRTUAL_BIND(_handles, "type");
|
||||
GDVIRTUAL_BIND(_generate, "resource", "size", "metadata");
|
||||
GDVIRTUAL_BIND(_generate_from_path, "path", "size", "metadata");
|
||||
GDVIRTUAL_BIND(_generate_small_preview_automatically);
|
||||
GDVIRTUAL_BIND(_can_generate_small_preview);
|
||||
}
|
||||
|
||||
void EditorResourcePreviewGenerator::DrawRequester::request_and_wait(RID p_viewport) {
|
||||
Callable request_vp_update_once = callable_mp(RS::get_singleton(), &RS::viewport_set_update_mode).bind(p_viewport, RS::VIEWPORT_UPDATE_ONCE);
|
||||
|
||||
if (EditorResourcePreview::get_singleton()->is_threaded()) {
|
||||
RS::get_singleton()->connect(SNAME("frame_pre_draw"), request_vp_update_once, Object::CONNECT_ONE_SHOT);
|
||||
RS::get_singleton()->request_frame_drawn_callback(callable_mp(this, &EditorResourcePreviewGenerator::DrawRequester::_post_semaphore));
|
||||
|
||||
semaphore.wait();
|
||||
} else {
|
||||
// Avoid the main viewport and children being redrawn.
|
||||
SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
|
||||
ERR_FAIL_NULL_MSG(st, "Editor's MainLoop is not a SceneTree. This is a bug.");
|
||||
RID root_vp = st->get_root()->get_viewport_rid();
|
||||
RenderingServer::get_singleton()->viewport_set_active(root_vp, false);
|
||||
|
||||
request_vp_update_once.call();
|
||||
RS::get_singleton()->draw(false);
|
||||
|
||||
// Let main viewport and children be drawn again.
|
||||
RenderingServer::get_singleton()->viewport_set_active(root_vp, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorResourcePreviewGenerator::DrawRequester::abort() {
|
||||
if (EditorResourcePreview::get_singleton()->is_threaded()) {
|
||||
semaphore.post();
|
||||
}
|
||||
}
|
||||
|
||||
Variant EditorResourcePreviewGenerator::DrawRequester::_post_semaphore() {
|
||||
semaphore.post();
|
||||
return Variant(); // Needed because of how the callback is used.
|
||||
}
|
||||
|
||||
bool EditorResourcePreview::is_threaded() const {
|
||||
return RSG::rasterizer->can_create_resources_async();
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_thread_func(void *ud) {
|
||||
EditorResourcePreview *erp = (EditorResourcePreview *)ud;
|
||||
erp->_thread();
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata) {
|
||||
{
|
||||
MutexLock lock(preview_mutex);
|
||||
|
||||
uint64_t modified_time = 0;
|
||||
|
||||
if (!p_path.begins_with("ID:")) {
|
||||
modified_time = FileAccess::get_modified_time(p_path);
|
||||
String import_path = p_path + ".import";
|
||||
if (FileAccess::exists(import_path)) {
|
||||
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
|
||||
}
|
||||
}
|
||||
|
||||
Item item;
|
||||
item.preview = p_texture;
|
||||
item.small_preview = p_small_texture;
|
||||
item.last_hash = p_hash;
|
||||
item.modified_time = modified_time;
|
||||
item.preview_metadata = p_metadata;
|
||||
|
||||
cache[p_path] = item;
|
||||
}
|
||||
|
||||
Callable(id, p_func).call_deferred(p_path, p_texture, p_small_texture, p_ud);
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata) {
|
||||
String type;
|
||||
|
||||
uint64_t started_at = OS::get_singleton()->get_ticks_usec();
|
||||
|
||||
if (p_item.resource.is_valid()) {
|
||||
type = p_item.resource->get_class();
|
||||
} else {
|
||||
type = ResourceLoader::get_resource_type(p_item.path);
|
||||
}
|
||||
|
||||
if (type.is_empty()) {
|
||||
r_texture = Ref<ImageTexture>();
|
||||
r_small_texture = Ref<ImageTexture>();
|
||||
|
||||
if (is_print_verbose_enabled()) {
|
||||
print_line(vformat("Generated '%s' preview in %d usec", p_item.path, OS::get_singleton()->get_ticks_usec() - started_at));
|
||||
}
|
||||
return; //could not guess type
|
||||
}
|
||||
|
||||
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
|
||||
thumbnail_size *= EDSCALE;
|
||||
|
||||
r_texture = Ref<ImageTexture>();
|
||||
r_small_texture = Ref<ImageTexture>();
|
||||
|
||||
for (int i = 0; i < preview_generators.size(); i++) {
|
||||
if (!preview_generators[i]->handles(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Texture2D> generated;
|
||||
if (p_item.resource.is_valid()) {
|
||||
generated = preview_generators.write[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size), p_metadata);
|
||||
} else {
|
||||
generated = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size), p_metadata);
|
||||
}
|
||||
r_texture = generated;
|
||||
|
||||
if (preview_generators[i]->can_generate_small_preview()) {
|
||||
Ref<Texture2D> generated_small;
|
||||
Dictionary d;
|
||||
if (p_item.resource.is_valid()) {
|
||||
generated_small = preview_generators.write[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size), d);
|
||||
} else {
|
||||
generated_small = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size), d);
|
||||
}
|
||||
r_small_texture = generated_small;
|
||||
}
|
||||
|
||||
if (r_small_texture.is_null() && r_texture.is_valid() && preview_generators[i]->generate_small_preview_automatically()) {
|
||||
Ref<Image> small_image = r_texture->get_image()->duplicate();
|
||||
Vector2i new_size = Vector2i(1, 1) * small_thumbnail_size;
|
||||
const real_t aspect = small_image->get_size().aspect();
|
||||
if (aspect > 1.0) {
|
||||
new_size.y = MAX(1, new_size.y / aspect);
|
||||
} else if (aspect < 1.0) {
|
||||
new_size.x = MAX(1, new_size.x * aspect);
|
||||
}
|
||||
small_image->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
|
||||
|
||||
// Make sure the image is always square.
|
||||
if (aspect != 1.0) {
|
||||
Ref<Image> rect = small_image;
|
||||
const Vector2i rect_size = rect->get_size();
|
||||
small_image = Image::create_empty(small_thumbnail_size, small_thumbnail_size, false, rect->get_format());
|
||||
// Blit the rectangle in the center of the square.
|
||||
small_image->blit_rect(rect, Rect2i(Vector2i(), rect_size), (Vector2i(1, 1) * small_thumbnail_size - rect_size) / 2);
|
||||
}
|
||||
|
||||
r_small_texture.instantiate();
|
||||
r_small_texture->set_image(small_image);
|
||||
}
|
||||
|
||||
if (generated.is_valid()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_item.resource.is_null()) {
|
||||
// Cache the preview in case it's a resource on disk.
|
||||
if (r_texture.is_valid()) {
|
||||
// Wow it generated a preview... save cache.
|
||||
bool has_small_texture = r_small_texture.is_valid();
|
||||
ResourceSaver::save(r_texture, cache_base + ".png");
|
||||
if (has_small_texture) {
|
||||
ResourceSaver::save(r_small_texture, cache_base + "_small.png");
|
||||
}
|
||||
Ref<FileAccess> f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE);
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + cache_base + ".txt'. Check user write permissions.");
|
||||
|
||||
uint64_t modtime = FileAccess::get_modified_time(p_item.path);
|
||||
String import_path = p_item.path + ".import";
|
||||
if (FileAccess::exists(import_path)) {
|
||||
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
|
||||
}
|
||||
|
||||
_write_preview_cache(f, thumbnail_size, has_small_texture, modtime, FileAccess::get_md5(p_item.path), p_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_print_verbose_enabled()) {
|
||||
print_line(vformat("Generated '%s' preview in %d usec", p_item.path, OS::get_singleton()->get_ticks_usec() - started_at));
|
||||
}
|
||||
}
|
||||
|
||||
const Dictionary EditorResourcePreview::get_preview_metadata(const String &p_path) const {
|
||||
ERR_FAIL_COND_V(!cache.has(p_path), Dictionary());
|
||||
return cache[p_path].preview_metadata;
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_iterate() {
|
||||
preview_mutex.lock();
|
||||
|
||||
if (queue.is_empty()) {
|
||||
preview_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
QueueItem item = queue.front()->get();
|
||||
queue.pop_front();
|
||||
|
||||
if (cache.has(item.path)) {
|
||||
Item cached_item = cache[item.path];
|
||||
// Already has it because someone loaded it, just let it know it's ready.
|
||||
_preview_ready(item.path, cached_item.last_hash, cached_item.preview, cached_item.small_preview, item.id, item.function, item.userdata, cached_item.preview_metadata);
|
||||
preview_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
preview_mutex.unlock();
|
||||
|
||||
Ref<ImageTexture> texture;
|
||||
Ref<ImageTexture> small_texture;
|
||||
|
||||
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
|
||||
thumbnail_size *= EDSCALE;
|
||||
|
||||
if (item.resource.is_valid()) {
|
||||
Dictionary preview_metadata;
|
||||
_generate_preview(texture, small_texture, item, String(), preview_metadata);
|
||||
_preview_ready(item.path, item.resource->hash_edited_version_for_preview(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata);
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary preview_metadata;
|
||||
String temp_path = EditorPaths::get_singleton()->get_cache_dir();
|
||||
String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text();
|
||||
cache_base = temp_path.path_join("resthumb-" + cache_base);
|
||||
|
||||
// Does not have it, try to load a cached thumbnail.
|
||||
|
||||
String file = cache_base + ".txt";
|
||||
Ref<FileAccess> f = FileAccess::open(file, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
// No cache found, generate.
|
||||
_generate_preview(texture, small_texture, item, cache_base, preview_metadata);
|
||||
} else {
|
||||
uint64_t modtime = FileAccess::get_modified_time(item.path);
|
||||
String import_path = item.path + ".import";
|
||||
if (FileAccess::exists(import_path)) {
|
||||
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
|
||||
}
|
||||
|
||||
int tsize;
|
||||
bool has_small_texture;
|
||||
uint64_t last_modtime;
|
||||
String hash;
|
||||
bool outdated;
|
||||
_read_preview_cache(f, &tsize, &has_small_texture, &last_modtime, &hash, &preview_metadata, &outdated);
|
||||
|
||||
bool cache_valid = true;
|
||||
|
||||
if (tsize != thumbnail_size) {
|
||||
cache_valid = false;
|
||||
f.unref();
|
||||
} else if (outdated) {
|
||||
cache_valid = false;
|
||||
f.unref();
|
||||
} else if (last_modtime != modtime) {
|
||||
String last_md5 = f->get_line();
|
||||
String md5 = FileAccess::get_md5(item.path);
|
||||
f.unref();
|
||||
|
||||
if (last_md5 != md5) {
|
||||
cache_valid = false;
|
||||
} else {
|
||||
// Update modified time.
|
||||
|
||||
Ref<FileAccess> f2 = FileAccess::open(file, FileAccess::WRITE);
|
||||
if (f2.is_null()) {
|
||||
// Not returning as this would leave the thread hanging and would require
|
||||
// some proper cleanup/disabling of resource preview generation.
|
||||
ERR_PRINT("Cannot create file '" + file + "'. Check user write permissions.");
|
||||
} else {
|
||||
_write_preview_cache(f2, thumbnail_size, has_small_texture, modtime, md5, preview_metadata);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f.unref();
|
||||
}
|
||||
|
||||
if (cache_valid) {
|
||||
Ref<Image> img;
|
||||
img.instantiate();
|
||||
Ref<Image> small_img;
|
||||
small_img.instantiate();
|
||||
|
||||
if (img->load(cache_base + ".png") != OK) {
|
||||
cache_valid = false;
|
||||
} else {
|
||||
texture.instantiate();
|
||||
texture->set_image(img);
|
||||
|
||||
if (has_small_texture) {
|
||||
if (small_img->load(cache_base + "_small.png") != OK) {
|
||||
cache_valid = false;
|
||||
} else {
|
||||
small_texture.instantiate();
|
||||
small_texture->set_image(small_img);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cache_valid) {
|
||||
_generate_preview(texture, small_texture, item, cache_base, preview_metadata);
|
||||
}
|
||||
}
|
||||
_preview_ready(item.path, 0, texture, small_texture, item.id, item.function, item.userdata, preview_metadata);
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, const String &p_hash, const Dictionary &p_metadata) {
|
||||
p_file->store_line(itos(p_thumbnail_size));
|
||||
p_file->store_line(itos(p_has_small_texture));
|
||||
p_file->store_line(itos(p_modified_time));
|
||||
p_file->store_line(p_hash);
|
||||
p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace_char('\n', ' '));
|
||||
p_file->store_line(itos(CURRENT_METADATA_VERSION));
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_read_preview_cache(Ref<FileAccess> p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata, bool *r_outdated) {
|
||||
*r_thumbnail_size = p_file->get_line().to_int();
|
||||
*r_has_small_texture = p_file->get_line().to_int();
|
||||
*r_modified_time = p_file->get_line().to_int();
|
||||
*r_hash = p_file->get_line();
|
||||
*r_metadata = VariantUtilityFunctions::str_to_var(p_file->get_line());
|
||||
*r_outdated = p_file->get_line().to_int() < CURRENT_METADATA_VERSION;
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_thread() {
|
||||
exited.clear();
|
||||
while (!exiting.is_set()) {
|
||||
preview_sem.wait();
|
||||
_iterate();
|
||||
}
|
||||
exited.set();
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_idle_callback() {
|
||||
if (!singleton) {
|
||||
// Just in case the shutdown of the editor involves the deletion of the singleton
|
||||
// happening while additional idle callbacks can happen.
|
||||
return;
|
||||
}
|
||||
|
||||
// Process preview tasks, trying to leave a little bit of responsiveness worst case.
|
||||
uint64_t start = OS::get_singleton()->get_ticks_msec();
|
||||
while (!singleton->queue.is_empty() && OS::get_singleton()->get_ticks_msec() - start < 100) {
|
||||
singleton->_iterate();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_update_thumbnail_sizes() {
|
||||
if (small_thumbnail_size == -1) {
|
||||
// Kind of a workaround to retrieve the default icon size.
|
||||
small_thumbnail_size = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Object"), EditorStringName(EditorIcons))->get_width();
|
||||
}
|
||||
}
|
||||
|
||||
EditorResourcePreview::PreviewItem EditorResourcePreview::get_resource_preview_if_available(const String &p_path) {
|
||||
PreviewItem item;
|
||||
{
|
||||
MutexLock lock(preview_mutex);
|
||||
|
||||
HashMap<String, EditorResourcePreview::Item>::Iterator I = cache.find(p_path);
|
||||
if (!I) {
|
||||
return item;
|
||||
}
|
||||
|
||||
EditorResourcePreview::Item &cached_item = I->value;
|
||||
item.preview = cached_item.preview;
|
||||
item.small_preview = cached_item.small_preview;
|
||||
}
|
||||
preview_sem.post();
|
||||
return item;
|
||||
}
|
||||
|
||||
void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
|
||||
ERR_FAIL_NULL(p_receiver);
|
||||
ERR_FAIL_COND(p_res.is_null());
|
||||
_update_thumbnail_sizes();
|
||||
|
||||
{
|
||||
MutexLock lock(preview_mutex);
|
||||
|
||||
String path_id = "ID:" + itos(p_res->get_instance_id());
|
||||
|
||||
if (cache.has(path_id) && cache[path_id].last_hash == p_res->hash_edited_version_for_preview()) {
|
||||
p_receiver->call(p_receiver_func, path_id, cache[path_id].preview, cache[path_id].small_preview, p_userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
cache.erase(path_id); //erase if exists, since it will be regen
|
||||
|
||||
QueueItem item;
|
||||
item.function = p_receiver_func;
|
||||
item.id = p_receiver->get_instance_id();
|
||||
item.resource = p_res;
|
||||
item.path = path_id;
|
||||
item.userdata = p_userdata;
|
||||
|
||||
queue.push_back(item);
|
||||
}
|
||||
preview_sem.post();
|
||||
}
|
||||
|
||||
void EditorResourcePreview::queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
|
||||
ERR_FAIL_NULL(p_receiver);
|
||||
_update_thumbnail_sizes();
|
||||
|
||||
{
|
||||
MutexLock lock(preview_mutex);
|
||||
|
||||
if (cache.has(p_path)) {
|
||||
p_receiver->call(p_receiver_func, p_path, cache[p_path].preview, cache[p_path].small_preview, p_userdata);
|
||||
return;
|
||||
}
|
||||
|
||||
QueueItem item;
|
||||
item.function = p_receiver_func;
|
||||
item.id = p_receiver->get_instance_id();
|
||||
item.path = p_path;
|
||||
item.userdata = p_userdata;
|
||||
|
||||
queue.push_back(item);
|
||||
}
|
||||
preview_sem.post();
|
||||
}
|
||||
|
||||
void EditorResourcePreview::add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) {
|
||||
preview_generators.push_back(p_generator);
|
||||
}
|
||||
|
||||
void EditorResourcePreview::remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) {
|
||||
preview_generators.erase(p_generator);
|
||||
}
|
||||
|
||||
EditorResourcePreview *EditorResourcePreview::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("queue_resource_preview", "path", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::queue_resource_preview);
|
||||
ClassDB::bind_method(D_METHOD("queue_edited_resource_preview", "resource", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::queue_edited_resource_preview);
|
||||
ClassDB::bind_method(D_METHOD("add_preview_generator", "generator"), &EditorResourcePreview::add_preview_generator);
|
||||
ClassDB::bind_method(D_METHOD("remove_preview_generator", "generator"), &EditorResourcePreview::remove_preview_generator);
|
||||
ClassDB::bind_method(D_METHOD("check_for_invalidation", "path"), &EditorResourcePreview::check_for_invalidation);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("preview_invalidated", PropertyInfo(Variant::STRING, "path")));
|
||||
}
|
||||
|
||||
void EditorResourcePreview::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
stop();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorResourcePreview::check_for_invalidation(const String &p_path) {
|
||||
bool call_invalidated = false;
|
||||
{
|
||||
MutexLock lock(preview_mutex);
|
||||
|
||||
if (cache.has(p_path)) {
|
||||
uint64_t modified_time = FileAccess::get_modified_time(p_path);
|
||||
String import_path = p_path + ".import";
|
||||
if (FileAccess::exists(import_path)) {
|
||||
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
|
||||
}
|
||||
|
||||
if (modified_time != cache[p_path].modified_time) {
|
||||
cache.erase(p_path);
|
||||
call_invalidated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (call_invalidated) { //do outside mutex
|
||||
call_deferred(SNAME("emit_signal"), "preview_invalidated", p_path);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorResourcePreview::start() {
|
||||
if (DisplayServer::get_singleton()->get_name() == "headless") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_threaded()) {
|
||||
ERR_FAIL_COND_MSG(thread.is_started(), "Thread already started.");
|
||||
thread.start(_thread_func, this);
|
||||
} else {
|
||||
SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
|
||||
ERR_FAIL_NULL_MSG(st, "Editor's MainLoop is not a SceneTree. This is a bug.");
|
||||
st->add_idle_callback(&_idle_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorResourcePreview::stop() {
|
||||
if (is_threaded()) {
|
||||
if (thread.is_started()) {
|
||||
exiting.set();
|
||||
preview_sem.post();
|
||||
|
||||
for (int i = 0; i < preview_generators.size(); i++) {
|
||||
preview_generators.write[i]->abort();
|
||||
}
|
||||
|
||||
while (!exited.is_set()) {
|
||||
// Sync pending work.
|
||||
OS::get_singleton()->delay_usec(10000);
|
||||
RenderingServer::get_singleton()->sync();
|
||||
MessageQueue::get_singleton()->flush();
|
||||
}
|
||||
|
||||
thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorResourcePreview::EditorResourcePreview() {
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
EditorResourcePreview::~EditorResourcePreview() {
|
||||
stop();
|
||||
}
|
||||
152
editor/inspector/editor_resource_preview.h
Normal file
152
editor/inspector/editor_resource_preview.h
Normal file
@@ -0,0 +1,152 @@
|
||||
/**************************************************************************/
|
||||
/* editor_resource_preview.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/semaphore.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class ImageTexture;
|
||||
class Texture2D;
|
||||
|
||||
class EditorResourcePreviewGenerator : public RefCounted {
|
||||
GDCLASS(EditorResourcePreviewGenerator, RefCounted);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL1RC(bool, _handles, String)
|
||||
GDVIRTUAL3RC(Ref<Texture2D>, _generate, Ref<Resource>, Vector2i, Dictionary)
|
||||
GDVIRTUAL3RC(Ref<Texture2D>, _generate_from_path, String, Vector2i, Dictionary)
|
||||
GDVIRTUAL0RC(bool, _generate_small_preview_automatically)
|
||||
GDVIRTUAL0RC(bool, _can_generate_small_preview)
|
||||
|
||||
class DrawRequester : public Object {
|
||||
Semaphore semaphore;
|
||||
|
||||
Variant _post_semaphore();
|
||||
|
||||
public:
|
||||
void request_and_wait(RID p_viewport);
|
||||
void abort();
|
||||
};
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_type) const;
|
||||
virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const;
|
||||
virtual Ref<Texture2D> generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const;
|
||||
virtual void abort() {}
|
||||
|
||||
virtual bool generate_small_preview_automatically() const;
|
||||
virtual bool can_generate_small_preview() const;
|
||||
};
|
||||
|
||||
class EditorResourcePreview : public Node {
|
||||
GDCLASS(EditorResourcePreview, Node);
|
||||
|
||||
static constexpr int CURRENT_METADATA_VERSION = 1; // Increment this number to invalidate all previews.
|
||||
inline static EditorResourcePreview *singleton = nullptr;
|
||||
|
||||
struct QueueItem {
|
||||
Ref<Resource> resource;
|
||||
String path;
|
||||
ObjectID id;
|
||||
StringName function;
|
||||
Variant userdata;
|
||||
};
|
||||
|
||||
List<QueueItem> queue;
|
||||
|
||||
Mutex preview_mutex;
|
||||
Semaphore preview_sem;
|
||||
Thread thread;
|
||||
SafeFlag exiting;
|
||||
SafeFlag exited;
|
||||
|
||||
struct Item {
|
||||
Ref<Texture2D> preview;
|
||||
Ref<Texture2D> small_preview;
|
||||
Dictionary preview_metadata;
|
||||
uint32_t last_hash = 0;
|
||||
uint64_t modified_time = 0;
|
||||
};
|
||||
|
||||
HashMap<String, Item> cache;
|
||||
|
||||
void _preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata);
|
||||
void _generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata);
|
||||
|
||||
int small_thumbnail_size = -1;
|
||||
|
||||
static void _thread_func(void *ud);
|
||||
void _thread(); // For rendering drivers supporting async texture creation.
|
||||
static void _idle_callback(); // For other rendering drivers (i.e., OpenGL).
|
||||
void _iterate();
|
||||
|
||||
void _write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, const String &p_hash, const Dictionary &p_metadata);
|
||||
void _read_preview_cache(Ref<FileAccess> p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata, bool *r_outdated);
|
||||
|
||||
Vector<Ref<EditorResourcePreviewGenerator>> preview_generators;
|
||||
|
||||
void _update_thumbnail_sizes();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorResourcePreview *get_singleton();
|
||||
|
||||
struct PreviewItem {
|
||||
Ref<Texture2D> preview;
|
||||
Ref<Texture2D> small_preview;
|
||||
};
|
||||
|
||||
// p_receiver_func callback has signature (String p_path, Ref<Texture2D> p_preview, Ref<Texture2D> p_preview_small, Variant p_userdata)
|
||||
// p_preview will be null if there was an error
|
||||
void queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
|
||||
void queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
|
||||
const Dictionary get_preview_metadata(const String &p_path) const;
|
||||
|
||||
PreviewItem get_resource_preview_if_available(const String &p_path);
|
||||
|
||||
void add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
|
||||
void remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
|
||||
void check_for_invalidation(const String &p_path);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool is_threaded() const;
|
||||
|
||||
EditorResourcePreview();
|
||||
~EditorResourcePreview();
|
||||
};
|
||||
162
editor/inspector/editor_resource_tooltip_plugins.cpp
Normal file
162
editor/inspector/editor_resource_tooltip_plugins.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/**************************************************************************/
|
||||
/* editor_resource_tooltip_plugins.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_resource_tooltip_plugins.h"
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/inspector/editor_resource_preview.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
void EditorResourceTooltipPlugin::_thumbnail_ready(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
|
||||
ObjectID trid = p_udata;
|
||||
TextureRect *tr = ObjectDB::get_instance<TextureRect>(trid);
|
||||
|
||||
if (!tr) {
|
||||
return;
|
||||
}
|
||||
|
||||
tr->set_texture(p_preview);
|
||||
}
|
||||
|
||||
void EditorResourceTooltipPlugin::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_thumbnail_ready"), &EditorResourceTooltipPlugin::_thumbnail_ready);
|
||||
ClassDB::bind_method(D_METHOD("request_thumbnail", "path", "control"), &EditorResourceTooltipPlugin::request_thumbnail);
|
||||
|
||||
GDVIRTUAL_BIND(_handles, "type");
|
||||
GDVIRTUAL_BIND(_make_tooltip_for_path, "path", "metadata", "base");
|
||||
}
|
||||
|
||||
VBoxContainer *EditorResourceTooltipPlugin::make_default_tooltip(const String &p_resource_path) {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->add_theme_constant_override("separation", -4 * EDSCALE);
|
||||
{
|
||||
Label *label = memnew(Label(p_resource_path.get_file()));
|
||||
vb->add_child(label);
|
||||
}
|
||||
|
||||
ResourceUID::ID id = EditorFileSystem::get_singleton()->get_file_uid(p_resource_path);
|
||||
if (id != ResourceUID::INVALID_ID) {
|
||||
Label *label = memnew(Label(ResourceUID::get_singleton()->id_to_text(id)));
|
||||
vb->add_child(label);
|
||||
}
|
||||
|
||||
{
|
||||
Ref<FileAccess> f = FileAccess::open(p_resource_path, FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
Label *label = memnew(Label(vformat(TTR("Size: %s"), String::humanize_size(f->get_length()))));
|
||||
vb->add_child(label);
|
||||
} else {
|
||||
Label *label = memnew(Label(TTR("Invalid file or broken link.")));
|
||||
label->add_theme_color_override(SceneStringName(font_color), EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
vb->add_child(label);
|
||||
return vb;
|
||||
}
|
||||
}
|
||||
|
||||
if (ResourceLoader::exists(p_resource_path)) {
|
||||
String type = ResourceLoader::get_resource_type(p_resource_path);
|
||||
Label *label = memnew(Label(vformat(TTR("Type: %s"), type)));
|
||||
vb->add_child(label);
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
|
||||
void EditorResourceTooltipPlugin::request_thumbnail(const String &p_path, TextureRect *p_for_control) const {
|
||||
ERR_FAIL_NULL(p_for_control);
|
||||
EditorResourcePreview::get_singleton()->queue_resource_preview(p_path, const_cast<EditorResourceTooltipPlugin *>(this), "_thumbnail_ready", p_for_control->get_instance_id());
|
||||
}
|
||||
|
||||
bool EditorResourceTooltipPlugin::handles(const String &p_resource_type) const {
|
||||
bool ret = false;
|
||||
GDVIRTUAL_CALL(_handles, p_resource_type, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Control *EditorResourceTooltipPlugin::make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const {
|
||||
Control *ret = nullptr;
|
||||
GDVIRTUAL_CALL(_make_tooltip_for_path, p_resource_path, p_metadata, p_base, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// EditorTextureTooltipPlugin
|
||||
|
||||
bool EditorTextureTooltipPlugin::handles(const String &p_resource_type) const {
|
||||
return ClassDB::is_parent_class(p_resource_type, "Texture2D") || ClassDB::is_parent_class(p_resource_type, "Image");
|
||||
}
|
||||
|
||||
Control *EditorTextureTooltipPlugin::make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const {
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
VBoxContainer *vb = Object::cast_to<VBoxContainer>(p_base);
|
||||
DEV_ASSERT(vb);
|
||||
vb->set_alignment(BoxContainer::ALIGNMENT_CENTER);
|
||||
|
||||
Vector2 dimensions = p_metadata.get("dimensions", Vector2());
|
||||
Label *label = memnew(Label(vformat(TTR(U"Dimensions: %d × %d"), dimensions.x, dimensions.y)));
|
||||
vb->add_child(label);
|
||||
|
||||
TextureRect *tr = memnew(TextureRect);
|
||||
tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
hb->add_child(tr);
|
||||
request_thumbnail(p_resource_path, tr);
|
||||
|
||||
hb->add_child(vb);
|
||||
return hb;
|
||||
}
|
||||
|
||||
// EditorAudioStreamTooltipPlugin
|
||||
|
||||
bool EditorAudioStreamTooltipPlugin::handles(const String &p_resource_type) const {
|
||||
return ClassDB::is_parent_class(p_resource_type, "AudioStream");
|
||||
}
|
||||
|
||||
Control *EditorAudioStreamTooltipPlugin::make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const {
|
||||
VBoxContainer *vb = Object::cast_to<VBoxContainer>(p_base);
|
||||
DEV_ASSERT(vb);
|
||||
|
||||
double length = p_metadata.get("length", 0.0);
|
||||
if (length >= 60.0) {
|
||||
vb->add_child(memnew(Label(vformat(TTR("Length: %0dm %0ds"), int(length / 60.0), int(std::fmod(length, 60))))));
|
||||
} else if (length >= 1.0) {
|
||||
vb->add_child(memnew(Label(vformat(TTR("Length: %0.1fs"), length))));
|
||||
} else {
|
||||
vb->add_child(memnew(Label(vformat(TTR("Length: %0.3fs"), length))));
|
||||
}
|
||||
|
||||
TextureRect *tr = memnew(TextureRect);
|
||||
vb->add_child(tr);
|
||||
request_thumbnail(p_resource_path, tr);
|
||||
|
||||
return vb;
|
||||
}
|
||||
74
editor/inspector/editor_resource_tooltip_plugins.h
Normal file
74
editor/inspector/editor_resource_tooltip_plugins.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/**************************************************************************/
|
||||
/* editor_resource_tooltip_plugins.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "scene/gui/control.h"
|
||||
|
||||
class Texture2D;
|
||||
class TextureRect;
|
||||
class VBoxContainer;
|
||||
|
||||
class EditorResourceTooltipPlugin : public RefCounted {
|
||||
GDCLASS(EditorResourceTooltipPlugin, RefCounted);
|
||||
|
||||
void _thumbnail_ready(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL1RC(bool, _handles, String)
|
||||
GDVIRTUAL3RC(Control *, _make_tooltip_for_path, String, Dictionary, Control *)
|
||||
|
||||
public:
|
||||
static VBoxContainer *make_default_tooltip(const String &p_resource_path);
|
||||
void request_thumbnail(const String &p_path, TextureRect *p_for_control) const;
|
||||
|
||||
virtual bool handles(const String &p_resource_type) const;
|
||||
virtual Control *make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const;
|
||||
};
|
||||
|
||||
class EditorTextureTooltipPlugin : public EditorResourceTooltipPlugin {
|
||||
GDCLASS(EditorTextureTooltipPlugin, EditorResourceTooltipPlugin);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_resource_type) const override;
|
||||
virtual Control *make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const override;
|
||||
};
|
||||
|
||||
class EditorAudioStreamTooltipPlugin : public EditorResourceTooltipPlugin {
|
||||
GDCLASS(EditorAudioStreamTooltipPlugin, EditorResourceTooltipPlugin);
|
||||
|
||||
public:
|
||||
virtual bool handles(const String &p_resource_type) const override;
|
||||
virtual Control *make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const override;
|
||||
};
|
||||
387
editor/inspector/editor_sectioned_inspector.cpp
Normal file
387
editor/inspector/editor_sectioned_inspector.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
/**************************************************************************/
|
||||
/* editor_sectioned_inspector.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_sectioned_inspector.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/inspector/editor_property_name_processor.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
static bool _property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) {
|
||||
if (p_property_path.containsn(p_filter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const Vector<String> sections = p_property_path.split("/");
|
||||
for (int i = 0; i < sections.size(); i++) {
|
||||
if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(sections[i], p_style, p_property_path))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class SectionedInspectorFilter : public Object {
|
||||
GDCLASS(SectionedInspectorFilter, Object);
|
||||
|
||||
Object *edited = nullptr;
|
||||
String section;
|
||||
bool allow_sub = false;
|
||||
|
||||
bool _set(const StringName &p_name, const Variant &p_value) {
|
||||
if (!edited) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = p_name;
|
||||
if (!section.is_empty()) {
|
||||
name = section + "/" + name;
|
||||
}
|
||||
|
||||
bool valid;
|
||||
edited->set(name, p_value, &valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (!edited) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = p_name;
|
||||
if (!section.is_empty()) {
|
||||
name = section + "/" + name;
|
||||
}
|
||||
|
||||
bool valid = false;
|
||||
|
||||
r_ret = edited->get(name, &valid);
|
||||
return valid;
|
||||
}
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const {
|
||||
if (!edited) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PropertyInfo> pinfo;
|
||||
edited->get_property_list(&pinfo);
|
||||
for (PropertyInfo &pi : pinfo) {
|
||||
int sp = pi.name.find_char('/');
|
||||
|
||||
if (pi.name == "resource_path" || pi.name == "resource_name" || pi.name == "resource_local_to_scene" || pi.name.begins_with("script/") || pi.name.begins_with("_global_script")) { //skip resource stuff
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sp == -1) {
|
||||
pi.name = "global/" + pi.name;
|
||||
}
|
||||
|
||||
if (pi.name.begins_with(section + "/")) {
|
||||
pi.name = pi.name.replace_first(section + "/", "");
|
||||
if (!allow_sub && pi.name.contains_char('/')) {
|
||||
continue;
|
||||
}
|
||||
p_list->push_back(pi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _property_can_revert(const StringName &p_name) const {
|
||||
return edited->property_can_revert(section + "/" + p_name);
|
||||
}
|
||||
|
||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const {
|
||||
r_property = edited->property_get_revert(section + "/" + p_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
void set_section(const String &p_section, bool p_allow_sub) {
|
||||
section = p_section;
|
||||
allow_sub = p_allow_sub;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
void set_edited(Object *p_edited) {
|
||||
edited = p_edited;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
};
|
||||
|
||||
void SectionedInspector::_bind_methods() {
|
||||
ClassDB::bind_method("update_category_list", &SectionedInspector::update_category_list);
|
||||
ADD_SIGNAL(MethodInfo("category_changed", PropertyInfo(Variant::STRING, "new_category")));
|
||||
}
|
||||
|
||||
void SectionedInspector::_section_selected() {
|
||||
if (!sections->get_selected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
selected_category = sections->get_selected()->get_metadata(0);
|
||||
filter->set_section(selected_category, sections->get_selected()->get_first_child() == nullptr);
|
||||
inspector->set_property_prefix(selected_category + "/");
|
||||
inspector->set_scroll_offset(0);
|
||||
emit_signal(SNAME("category_changed"), selected_category);
|
||||
}
|
||||
|
||||
void SectionedInspector::set_current_section(const String &p_section) {
|
||||
if (section_map.has(p_section)) {
|
||||
TreeItem *item = section_map[p_section];
|
||||
item->select(0);
|
||||
sections->scroll_to_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
String SectionedInspector::get_current_section() const {
|
||||
if (sections->get_selected()) {
|
||||
return sections->get_selected()->get_metadata(0);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String SectionedInspector::get_full_item_path(const String &p_item) {
|
||||
String base = get_current_section();
|
||||
|
||||
if (!base.is_empty()) {
|
||||
return base + "/" + p_item;
|
||||
} else {
|
||||
return p_item;
|
||||
}
|
||||
}
|
||||
|
||||
void SectionedInspector::edit(Object *p_object) {
|
||||
if (!p_object) {
|
||||
obj = ObjectID();
|
||||
sections->clear();
|
||||
|
||||
filter->set_edited(nullptr);
|
||||
inspector->edit(nullptr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectID id = p_object->get_instance_id();
|
||||
|
||||
inspector->set_object_class(p_object->get_class());
|
||||
|
||||
if (obj != id) {
|
||||
obj = id;
|
||||
update_category_list();
|
||||
|
||||
filter->set_edited(p_object);
|
||||
inspector->edit(filter);
|
||||
|
||||
TreeItem *first_item = sections->get_root();
|
||||
if (first_item) {
|
||||
while (first_item->get_first_child()) {
|
||||
first_item = first_item->get_first_child();
|
||||
}
|
||||
|
||||
first_item->select(0);
|
||||
selected_category = first_item->get_metadata(0);
|
||||
}
|
||||
} else {
|
||||
update_category_list();
|
||||
}
|
||||
}
|
||||
|
||||
void SectionedInspector::update_category_list() {
|
||||
sections->clear();
|
||||
|
||||
Object *o = ObjectDB::get_instance(obj);
|
||||
|
||||
if (!o) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PropertyInfo> pinfo;
|
||||
o->get_property_list(&pinfo);
|
||||
|
||||
section_map.clear();
|
||||
|
||||
TreeItem *root = sections->create_item();
|
||||
section_map[""] = root;
|
||||
|
||||
String filter_text;
|
||||
if (search_box) {
|
||||
filter_text = search_box->get_text();
|
||||
}
|
||||
|
||||
const EditorPropertyNameProcessor::Style name_style = EditorPropertyNameProcessor::get_settings_style();
|
||||
const EditorPropertyNameProcessor::Style tooltip_style = EditorPropertyNameProcessor::get_tooltip_style(name_style);
|
||||
|
||||
for (PropertyInfo &pi : pinfo) {
|
||||
if (pi.usage & PROPERTY_USAGE_CATEGORY) {
|
||||
continue;
|
||||
} else if (!(pi.usage & PROPERTY_USAGE_EDITOR) ||
|
||||
(filter_text.is_empty() && restrict_to_basic && !(pi.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi.name.contains_char(':') || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene" || pi.name.begins_with("_global_script")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filter_text.is_empty() && !_property_path_matches(pi.name, filter_text, name_style)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int sp = pi.name.find_char('/');
|
||||
if (sp == -1) {
|
||||
pi.name = "global/" + pi.name;
|
||||
}
|
||||
|
||||
Vector<String> sectionarr = pi.name.split("/");
|
||||
String metasection;
|
||||
|
||||
int sc = MIN(2, sectionarr.size() - 1);
|
||||
|
||||
for (int i = 0; i < sc; i++) {
|
||||
TreeItem *parent = section_map[metasection];
|
||||
//parent->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
|
||||
parent->set_custom_font(0, get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
|
||||
if (i > 0) {
|
||||
metasection += "/" + sectionarr[i];
|
||||
} else {
|
||||
metasection = sectionarr[i];
|
||||
}
|
||||
|
||||
if (!section_map.has(metasection)) {
|
||||
TreeItem *ms = sections->create_item(parent);
|
||||
section_map[metasection] = ms;
|
||||
|
||||
const String text = EditorPropertyNameProcessor::get_singleton()->process_name(sectionarr[i], name_style, pi.name);
|
||||
const String tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(sectionarr[i], tooltip_style, pi.name);
|
||||
|
||||
ms->set_text(0, text);
|
||||
ms->set_tooltip_text(0, tooltip);
|
||||
ms->set_metadata(0, metasection);
|
||||
ms->set_selectable(0, false);
|
||||
}
|
||||
|
||||
if (i == sc - 1) {
|
||||
//if it has children, make selectable
|
||||
section_map[metasection]->set_selectable(0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (section_map.has(selected_category)) {
|
||||
section_map[selected_category]->select(0);
|
||||
}
|
||||
|
||||
inspector->update_tree();
|
||||
}
|
||||
|
||||
void SectionedInspector::register_search_box(LineEdit *p_box) {
|
||||
search_box = p_box;
|
||||
inspector->register_text_enter(p_box);
|
||||
search_box->connect(SceneStringName(text_changed), callable_mp(this, &SectionedInspector::_search_changed));
|
||||
}
|
||||
|
||||
void SectionedInspector::register_advanced_toggle(CheckButton *p_toggle) {
|
||||
advanced_toggle = p_toggle;
|
||||
advanced_toggle->connect(SceneStringName(toggled), callable_mp(this, &SectionedInspector::_advanced_toggled));
|
||||
_advanced_toggled(advanced_toggle->is_pressed());
|
||||
}
|
||||
|
||||
void SectionedInspector::_search_changed(const String &p_what) {
|
||||
if (advanced_toggle) {
|
||||
if (p_what.is_empty()) {
|
||||
advanced_toggle->set_pressed_no_signal(!restrict_to_basic);
|
||||
advanced_toggle->set_disabled(false);
|
||||
advanced_toggle->set_tooltip_text(String());
|
||||
} else {
|
||||
advanced_toggle->set_pressed_no_signal(true);
|
||||
advanced_toggle->set_disabled(true);
|
||||
advanced_toggle->set_tooltip_text(TTRC("Advanced settings are always shown when searching."));
|
||||
}
|
||||
}
|
||||
update_category_list();
|
||||
}
|
||||
|
||||
void SectionedInspector::_advanced_toggled(bool p_toggled_on) {
|
||||
restrict_to_basic = !p_toggled_on;
|
||||
update_category_list();
|
||||
inspector->set_restrict_to_basic_settings(restrict_to_basic);
|
||||
}
|
||||
|
||||
void SectionedInspector::_notification(int p_notification) {
|
||||
if (p_notification == NOTIFICATION_TRANSLATION_CHANGED) {
|
||||
if (sections->get_root()) {
|
||||
// Only update when initialized.
|
||||
callable_mp(this, &SectionedInspector::update_category_list).call_deferred();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorInspector *SectionedInspector::get_inspector() {
|
||||
return inspector;
|
||||
}
|
||||
|
||||
SectionedInspector::SectionedInspector() :
|
||||
sections(memnew(Tree)),
|
||||
filter(memnew(SectionedInspectorFilter)),
|
||||
inspector(memnew(EditorInspector)) {
|
||||
add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up
|
||||
|
||||
VBoxContainer *left_vb = memnew(VBoxContainer);
|
||||
left_vb->set_custom_minimum_size(Size2(190, 0) * EDSCALE);
|
||||
add_child(left_vb);
|
||||
|
||||
sections->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
sections->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
sections->set_hide_root(true);
|
||||
sections->set_theme_type_variation("TreeSecondary");
|
||||
|
||||
left_vb->add_child(sections, true);
|
||||
|
||||
VBoxContainer *right_vb = memnew(VBoxContainer);
|
||||
right_vb->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
|
||||
right_vb->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
add_child(right_vb);
|
||||
|
||||
inspector->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
right_vb->add_child(inspector, true);
|
||||
inspector->set_use_doc_hints(true);
|
||||
|
||||
sections->connect("cell_selected", callable_mp(this, &SectionedInspector::_section_selected));
|
||||
}
|
||||
|
||||
SectionedInspector::~SectionedInspector() {
|
||||
memdelete(filter);
|
||||
}
|
||||
83
editor/inspector/editor_sectioned_inspector.h
Normal file
83
editor/inspector/editor_sectioned_inspector.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/**************************************************************************/
|
||||
/* editor_sectioned_inspector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/split_container.h"
|
||||
|
||||
class CheckButton;
|
||||
class EditorInspector;
|
||||
class LineEdit;
|
||||
class SectionedInspectorFilter;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
|
||||
class SectionedInspector : public HSplitContainer {
|
||||
GDCLASS(SectionedInspector, HSplitContainer);
|
||||
|
||||
ObjectID obj;
|
||||
|
||||
Tree *sections = nullptr;
|
||||
SectionedInspectorFilter *filter = nullptr;
|
||||
|
||||
HashMap<String, TreeItem *> section_map;
|
||||
EditorInspector *inspector = nullptr;
|
||||
LineEdit *search_box = nullptr;
|
||||
CheckButton *advanced_toggle = nullptr;
|
||||
|
||||
String selected_category;
|
||||
|
||||
bool restrict_to_basic = false;
|
||||
|
||||
static void _bind_methods();
|
||||
void _section_selected();
|
||||
|
||||
void _search_changed(const String &p_what);
|
||||
void _advanced_toggled(bool p_toggled_on);
|
||||
|
||||
protected:
|
||||
void _notification(int p_notification);
|
||||
|
||||
public:
|
||||
void register_search_box(LineEdit *p_box);
|
||||
void register_advanced_toggle(CheckButton *p_toggle);
|
||||
|
||||
EditorInspector *get_inspector();
|
||||
void edit(Object *p_object);
|
||||
String get_full_item_path(const String &p_item);
|
||||
|
||||
void set_current_section(const String &p_section);
|
||||
String get_current_section() const;
|
||||
|
||||
void update_category_list();
|
||||
|
||||
SectionedInspector();
|
||||
~SectionedInspector();
|
||||
};
|
||||
165
editor/inspector/input_event_editor_plugin.cpp
Normal file
165
editor/inspector/input_event_editor_plugin.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/**************************************************************************/
|
||||
/* input_event_editor_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "input_event_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/settings/event_listener_line_edit.h"
|
||||
#include "editor/settings/input_event_configuration_dialog.h"
|
||||
|
||||
void InputEventConfigContainer::_configure_pressed() {
|
||||
config_dialog->popup_and_configure(input_event);
|
||||
}
|
||||
|
||||
void InputEventConfigContainer::_event_changed() {
|
||||
input_event_text->set_text(input_event->as_text());
|
||||
}
|
||||
|
||||
void InputEventConfigContainer::_config_dialog_confirmed() {
|
||||
Ref<InputEvent> ie = config_dialog->get_event();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Event Configured"));
|
||||
|
||||
// When command_or_control_autoremap is toggled to false, it should be set first;
|
||||
// and when it is toggled to true, it should be set last.
|
||||
bool will_toggle = false;
|
||||
bool pending = false;
|
||||
Ref<InputEventWithModifiers> iewm = input_event;
|
||||
if (iewm.is_valid()) {
|
||||
Variant new_value = ie->get("command_or_control_autoremap");
|
||||
will_toggle = new_value != input_event->get("command_or_control_autoremap");
|
||||
if (will_toggle) {
|
||||
pending = new_value;
|
||||
if (pending) {
|
||||
undo_redo->add_undo_property(input_event.ptr(), "command_or_control_autoremap", !pending);
|
||||
} else {
|
||||
undo_redo->add_do_property(input_event.ptr(), "command_or_control_autoremap", pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<PropertyInfo> pi;
|
||||
ie->get_property_list(&pi);
|
||||
for (const PropertyInfo &E : pi) {
|
||||
if (E.name == "resource_path") {
|
||||
continue; // Do not change path.
|
||||
}
|
||||
if (E.name == "command_or_control_autoremap") {
|
||||
continue; // Handle it separately.
|
||||
}
|
||||
Variant old_value = input_event->get(E.name);
|
||||
Variant new_value = ie->get(E.name);
|
||||
if (old_value == new_value) {
|
||||
continue;
|
||||
}
|
||||
undo_redo->add_do_property(input_event.ptr(), E.name, new_value);
|
||||
undo_redo->add_undo_property(input_event.ptr(), E.name, old_value);
|
||||
}
|
||||
|
||||
if (will_toggle) {
|
||||
if (pending) {
|
||||
undo_redo->add_do_property(input_event.ptr(), "command_or_control_autoremap", pending);
|
||||
} else {
|
||||
undo_redo->add_undo_property(input_event.ptr(), "command_or_control_autoremap", !pending);
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->add_do_property(input_event_text, "text", ie->as_text());
|
||||
undo_redo->add_undo_property(input_event_text, "text", input_event->as_text());
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) {
|
||||
Ref<InputEventKey> k = p_event;
|
||||
Ref<InputEventMouseButton> m = p_event;
|
||||
Ref<InputEventJoypadButton> jb = p_event;
|
||||
Ref<InputEventJoypadMotion> jm = p_event;
|
||||
|
||||
if (k.is_valid()) {
|
||||
config_dialog->set_allowed_input_types(INPUT_KEY);
|
||||
} else if (m.is_valid()) {
|
||||
config_dialog->set_allowed_input_types(INPUT_MOUSE_BUTTON);
|
||||
} else if (jb.is_valid()) {
|
||||
config_dialog->set_allowed_input_types(INPUT_JOY_BUTTON);
|
||||
} else if (jm.is_valid()) {
|
||||
config_dialog->set_allowed_input_types(INPUT_JOY_MOTION);
|
||||
}
|
||||
|
||||
input_event = p_event;
|
||||
_event_changed();
|
||||
input_event->connect_changed(callable_mp(this, &InputEventConfigContainer::_event_changed));
|
||||
}
|
||||
|
||||
InputEventConfigContainer::InputEventConfigContainer() {
|
||||
input_event_text = memnew(Label);
|
||||
input_event_text->set_focus_mode(FOCUS_ACCESSIBILITY);
|
||||
input_event_text->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
input_event_text->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD_SMART);
|
||||
input_event_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
add_child(input_event_text);
|
||||
|
||||
EditorInspectorActionButton *open_config_button = memnew(EditorInspectorActionButton(TTRC("Configure"), SNAME("Edit")));
|
||||
open_config_button->connect(SceneStringName(pressed), callable_mp(this, &InputEventConfigContainer::_configure_pressed));
|
||||
add_child(open_config_button);
|
||||
|
||||
add_child(memnew(Control));
|
||||
|
||||
config_dialog = memnew(InputEventConfigurationDialog);
|
||||
config_dialog->connect(SceneStringName(confirmed), callable_mp(this, &InputEventConfigContainer::_config_dialog_confirmed));
|
||||
add_child(config_dialog);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
bool EditorInspectorPluginInputEvent::can_handle(Object *p_object) {
|
||||
Ref<InputEventKey> k = Ref<InputEventKey>(p_object);
|
||||
Ref<InputEventMouseButton> m = Ref<InputEventMouseButton>(p_object);
|
||||
Ref<InputEventJoypadButton> jb = Ref<InputEventJoypadButton>(p_object);
|
||||
Ref<InputEventJoypadMotion> jm = Ref<InputEventJoypadMotion>(p_object);
|
||||
|
||||
return k.is_valid() || m.is_valid() || jb.is_valid() || jm.is_valid();
|
||||
}
|
||||
|
||||
void EditorInspectorPluginInputEvent::parse_begin(Object *p_object) {
|
||||
Ref<InputEvent> ie = Ref<InputEvent>(p_object);
|
||||
|
||||
InputEventConfigContainer *picker_controls = memnew(InputEventConfigContainer);
|
||||
picker_controls->set_event(ie);
|
||||
add_custom_control(picker_controls);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
InputEventEditorPlugin::InputEventEditorPlugin() {
|
||||
Ref<EditorInspectorPluginInputEvent> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
||||
71
editor/inspector/input_event_editor_plugin.h
Normal file
71
editor/inspector/input_event_editor_plugin.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/**************************************************************************/
|
||||
/* input_event_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "editor/settings/action_map_editor.h"
|
||||
|
||||
class InputEventConfigContainer : public VBoxContainer {
|
||||
GDCLASS(InputEventConfigContainer, VBoxContainer);
|
||||
|
||||
Label *input_event_text = nullptr;
|
||||
|
||||
Ref<InputEvent> input_event;
|
||||
InputEventConfigurationDialog *config_dialog = nullptr;
|
||||
|
||||
void _config_dialog_confirmed();
|
||||
void _configure_pressed();
|
||||
|
||||
void _event_changed();
|
||||
|
||||
public:
|
||||
void set_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
InputEventConfigContainer();
|
||||
};
|
||||
|
||||
class EditorInspectorPluginInputEvent : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginInputEvent, EditorInspectorPlugin);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class InputEventEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(InputEventEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "InputEvent"; }
|
||||
|
||||
InputEventEditorPlugin();
|
||||
};
|
||||
318
editor/inspector/multi_node_edit.cpp
Normal file
318
editor/inspector/multi_node_edit.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
/**************************************************************************/
|
||||
/* multi_node_edit.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "multi_node_edit.h"
|
||||
|
||||
#include "core/math/math_fieldwise.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
|
||||
bool MultiNodeEdit::_set(const StringName &p_name, const Variant &p_value) {
|
||||
return _set_impl(p_name, p_value, "");
|
||||
}
|
||||
|
||||
bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) {
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = p_name;
|
||||
|
||||
if (name == "scripts") { // Script set is intercepted at object level (check Variant Object::get()), so use a different name.
|
||||
name = "script";
|
||||
} else if (name.begins_with("Metadata/")) {
|
||||
name = name.replace_first("Metadata/", "metadata/");
|
||||
}
|
||||
|
||||
Node *node_path_target = nullptr;
|
||||
if (p_value.get_type() == Variant::NODE_PATH && p_value != NodePath()) {
|
||||
node_path_target = es->get_node(p_value);
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS);
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *n = es->get_node_or_null(E);
|
||||
if (!n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p_value.get_type() == Variant::NODE_PATH) {
|
||||
NodePath path;
|
||||
if (node_path_target) {
|
||||
path = n->get_path_to(node_path_target);
|
||||
}
|
||||
ur->add_do_property(n, name, path);
|
||||
} else {
|
||||
Variant new_value;
|
||||
if (p_field.is_empty()) {
|
||||
// whole value
|
||||
new_value = p_value;
|
||||
} else {
|
||||
// only one field
|
||||
new_value = fieldwise_assign(n->get(name), p_value, p_field);
|
||||
}
|
||||
ur->add_do_property(n, name, new_value);
|
||||
}
|
||||
|
||||
ur->add_undo_property(n, name, n->get(name));
|
||||
}
|
||||
|
||||
ur->commit_action();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MultiNodeEdit::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = p_name;
|
||||
if (name == "scripts") { // Script set is intercepted at object level (check Variant Object::get()), so use a different name.
|
||||
name = "script";
|
||||
} else if (name.begins_with("Metadata/")) {
|
||||
name = name.replace_first("Metadata/", "metadata/");
|
||||
}
|
||||
|
||||
for (const NodePath &E : nodes) {
|
||||
const Node *n = es->get_node_or_null(E);
|
||||
if (!n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found;
|
||||
r_ret = n->get(name, &found);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
HashMap<String, PLData> usage;
|
||||
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nc = 0;
|
||||
|
||||
List<PLData *> data_list;
|
||||
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *n = es->get_node_or_null(E);
|
||||
if (!n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<PropertyInfo> plist;
|
||||
n->get_property_list(&plist, true);
|
||||
|
||||
for (PropertyInfo F : plist) {
|
||||
if (F.name == "script") {
|
||||
continue; // Added later manually, since this is intercepted before being set (check Variant Object::get()).
|
||||
} else if (F.name.begins_with("metadata/")) {
|
||||
F.name = F.name.replace_first("metadata/", "Metadata/"); // Trick to not get actual metadata edited from MultiNodeEdit.
|
||||
}
|
||||
|
||||
if (!usage.has(F.name)) {
|
||||
PLData pld;
|
||||
pld.uses = 0;
|
||||
pld.info = F;
|
||||
pld.info.name = F.name;
|
||||
usage[F.name] = pld;
|
||||
data_list.push_back(usage.getptr(F.name));
|
||||
}
|
||||
|
||||
// Make sure only properties with the same exact PropertyInfo data will appear.
|
||||
if (usage[F.name].info == F) {
|
||||
usage[F.name].uses++;
|
||||
}
|
||||
}
|
||||
|
||||
nc++;
|
||||
}
|
||||
|
||||
for (const PLData *E : data_list) {
|
||||
if (nc == E->uses) {
|
||||
p_list->push_back(E->info);
|
||||
}
|
||||
}
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "scripts", PROPERTY_HINT_RESOURCE_TYPE, "Script"));
|
||||
}
|
||||
|
||||
String MultiNodeEdit::_get_editor_name() const {
|
||||
return vformat(TTR("%s (%d Selected)"), get_edited_class_name(), get_node_count());
|
||||
}
|
||||
|
||||
bool MultiNodeEdit::_property_can_revert(const StringName &p_name) const {
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ClassDB::has_property(get_edited_class_name(), p_name)) {
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *node = es->get_node_or_null(E);
|
||||
if (node) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show the revert button if the edited class doesn't have the property.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MultiNodeEdit::_property_get_revert(const StringName &p_name, Variant &r_property) const {
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *node = es->get_node_or_null(E);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r_property = ClassDB::class_get_default_property_value(node->get_class_name(), p_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiNodeEdit::_queue_notify_property_list_changed() {
|
||||
if (notify_property_list_changed_pending) {
|
||||
return;
|
||||
}
|
||||
notify_property_list_changed_pending = true;
|
||||
callable_mp(this, &MultiNodeEdit::_notify_property_list_changed).call_deferred();
|
||||
}
|
||||
|
||||
void MultiNodeEdit::_notify_property_list_changed() {
|
||||
notify_property_list_changed_pending = false;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
void MultiNodeEdit::add_node(const NodePath &p_node) {
|
||||
nodes.push_back(p_node);
|
||||
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (es) {
|
||||
Node *node = es->get_node_or_null(p_node);
|
||||
if (node) {
|
||||
node->connect(CoreStringName(property_list_changed), callable_mp(this, &MultiNodeEdit::_queue_notify_property_list_changed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int MultiNodeEdit::get_node_count() const {
|
||||
return nodes.size();
|
||||
}
|
||||
|
||||
NodePath MultiNodeEdit::get_node(int p_index) const {
|
||||
ERR_FAIL_INDEX_V(p_index, get_node_count(), NodePath());
|
||||
return nodes[p_index];
|
||||
}
|
||||
|
||||
StringName MultiNodeEdit::get_edited_class_name() const {
|
||||
Node *es = EditorNode::get_singleton()->get_edited_scene();
|
||||
if (!es) {
|
||||
return SNAME("Node");
|
||||
}
|
||||
|
||||
// Get the class name of the first node.
|
||||
StringName class_name;
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *node = es->get_node_or_null(E);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
class_name = node->get_class_name();
|
||||
break;
|
||||
}
|
||||
|
||||
if (class_name == StringName()) {
|
||||
return SNAME("Node");
|
||||
}
|
||||
|
||||
bool check_again = true;
|
||||
while (check_again) {
|
||||
check_again = false;
|
||||
|
||||
if (class_name == SNAME("Node") || class_name == StringName()) {
|
||||
// All nodes inherit from Node, so no need to continue checking.
|
||||
return SNAME("Node");
|
||||
}
|
||||
|
||||
// Check that all nodes inherit from class_name.
|
||||
for (const NodePath &E : nodes) {
|
||||
Node *node = es->get_node_or_null(E);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const StringName node_class_name = node->get_class_name();
|
||||
if (class_name == node_class_name || ClassDB::is_parent_class(node_class_name, class_name)) {
|
||||
// class_name is the same or a parent of the node's class.
|
||||
continue;
|
||||
}
|
||||
|
||||
// class_name is not a parent of the node's class, so check again with the parent class.
|
||||
class_name = ClassDB::get_parent_class(class_name);
|
||||
check_again = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return class_name;
|
||||
}
|
||||
|
||||
void MultiNodeEdit::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) {
|
||||
_set_impl(p_property, p_value, p_field);
|
||||
}
|
||||
|
||||
void MultiNodeEdit::_bind_methods() {
|
||||
ClassDB::bind_method("_hide_script_from_inspector", &MultiNodeEdit::_hide_script_from_inspector);
|
||||
ClassDB::bind_method("_hide_metadata_from_inspector", &MultiNodeEdit::_hide_metadata_from_inspector);
|
||||
ClassDB::bind_method("_get_editor_name", &MultiNodeEdit::_get_editor_name);
|
||||
}
|
||||
85
editor/inspector/multi_node_edit.h
Normal file
85
editor/inspector/multi_node_edit.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/**************************************************************************/
|
||||
/* multi_node_edit.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class MultiNodeEdit : public RefCounted {
|
||||
GDCLASS(MultiNodeEdit, RefCounted);
|
||||
|
||||
LocalVector<NodePath> nodes;
|
||||
bool notify_property_list_changed_pending = false;
|
||||
struct PLData {
|
||||
int uses = 0;
|
||||
PropertyInfo info;
|
||||
};
|
||||
|
||||
bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field);
|
||||
void _queue_notify_property_list_changed();
|
||||
void _notify_property_list_changed();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
bool _hide_script_from_inspector() { return true; }
|
||||
bool _hide_metadata_from_inspector() { return true; }
|
||||
|
||||
bool _property_can_revert(const StringName &p_name) const;
|
||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
||||
String _get_editor_name() const;
|
||||
|
||||
void add_node(const NodePath &p_node);
|
||||
|
||||
int get_node_count() const;
|
||||
NodePath get_node(int p_index) const;
|
||||
StringName get_edited_class_name() const;
|
||||
|
||||
void set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field);
|
||||
|
||||
// If the nodes selected are the same independently of order then return true.
|
||||
bool is_same_selection(const MultiNodeEdit *p_other) const {
|
||||
if (get_node_count() != p_other->get_node_count()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < get_node_count(); i++) {
|
||||
if (!nodes.has(p_other->get_node(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
687
editor/inspector/property_selector.cpp
Normal file
687
editor/inspector/property_selector.cpp
Normal file
@@ -0,0 +1,687 @@
|
||||
/**************************************************************************/
|
||||
/* property_selector.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "property_selector.h"
|
||||
|
||||
#include "editor/doc/editor_help.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
void PropertySelector::_text_changed(const String &p_newtext) {
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::_sbox_input(const Ref<InputEvent> &p_event) {
|
||||
// Redirect navigational key events to the tree.
|
||||
Ref<InputEventKey> key = p_event;
|
||||
if (key.is_valid()) {
|
||||
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
|
||||
search_options->gui_input(key);
|
||||
search_box->accept_event();
|
||||
|
||||
TreeItem *root = search_options->get_root();
|
||||
if (!root->get_first_child()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *current = search_options->get_selected();
|
||||
|
||||
TreeItem *item = search_options->get_next_selected(root);
|
||||
while (item) {
|
||||
item->deselect(0);
|
||||
item = search_options->get_next_selected(item);
|
||||
}
|
||||
|
||||
current->select(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertySelector::_update_search() {
|
||||
if (properties) {
|
||||
set_title(TTR("Select Property"));
|
||||
} else if (virtuals_only) {
|
||||
set_title(TTR("Select Virtual Method"));
|
||||
} else {
|
||||
set_title(TTR("Select Method"));
|
||||
}
|
||||
|
||||
search_options->clear();
|
||||
help_bit->set_custom_text(String(), String(), String());
|
||||
|
||||
TreeItem *root = search_options->create_item();
|
||||
|
||||
// Allow using spaces in place of underscores in the search string (makes the search more fault-tolerant).
|
||||
const String search_text = search_box->get_text().replace_char(' ', '_');
|
||||
|
||||
if (properties) {
|
||||
List<PropertyInfo> props;
|
||||
|
||||
if (instance) {
|
||||
instance->get_property_list(&props, true);
|
||||
} else if (type != Variant::NIL) {
|
||||
Variant v;
|
||||
Callable::CallError ce;
|
||||
Variant::construct(type, v, nullptr, 0, ce);
|
||||
|
||||
v.get_property_list(&props);
|
||||
} else {
|
||||
Object *obj = ObjectDB::get_instance(script);
|
||||
if (Object::cast_to<Script>(obj)) {
|
||||
props.push_back(PropertyInfo(Variant::NIL, "Script Variables", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY));
|
||||
Object::cast_to<Script>(obj)->get_script_property_list(&props);
|
||||
}
|
||||
|
||||
StringName base = base_type;
|
||||
while (base) {
|
||||
props.push_back(PropertyInfo(Variant::NIL, base, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CATEGORY));
|
||||
ClassDB::get_property_list(base, &props, true);
|
||||
base = ClassDB::get_parent_class(base);
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *category = nullptr;
|
||||
|
||||
bool found = false;
|
||||
for (const PropertyInfo &E : props) {
|
||||
if (E.usage == PROPERTY_USAGE_CATEGORY) {
|
||||
if (category && category->get_first_child() == nullptr) {
|
||||
memdelete(category); //old category was unused
|
||||
}
|
||||
category = search_options->create_item(root);
|
||||
category->set_text(0, E.name);
|
||||
category->set_selectable(0, false);
|
||||
|
||||
Ref<Texture2D> icon;
|
||||
if (E.name == "Script Variables") {
|
||||
icon = search_options->get_editor_theme_icon(SNAME("Script"));
|
||||
} else {
|
||||
icon = EditorNode::get_singleton()->get_class_icon(E.name);
|
||||
}
|
||||
category->set_icon(0, icon);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(E.usage & PROPERTY_USAGE_EDITOR) && !(E.usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!search_box->get_text().is_empty() && !E.name.containsn(search_text)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!type_filter.is_empty() && !type_filter.has(E.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem *item = search_options->create_item(category ? category : root);
|
||||
item->set_text(0, E.name);
|
||||
item->set_metadata(0, E.name);
|
||||
item->set_icon(0, search_options->get_editor_theme_icon(Variant::get_type_name(E.type)));
|
||||
|
||||
if (!found && !search_box->get_text().is_empty() && E.name.containsn(search_text)) {
|
||||
item->select(0);
|
||||
found = true;
|
||||
} else if (!found && search_box->get_text().is_empty() && E.name == selected) {
|
||||
item->select(0);
|
||||
found = true;
|
||||
}
|
||||
|
||||
item->set_selectable(0, true);
|
||||
|
||||
_create_subproperties(item, E.type);
|
||||
item->set_collapsed(true);
|
||||
}
|
||||
|
||||
if (category && category->get_first_child() == nullptr) {
|
||||
memdelete(category); //old category was unused
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// As we call this while adding items, defer until list is completely populated.
|
||||
callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
|
||||
}
|
||||
|
||||
} else {
|
||||
List<MethodInfo> methods;
|
||||
|
||||
if (type != Variant::NIL) {
|
||||
Variant v;
|
||||
Callable::CallError ce;
|
||||
Variant::construct(type, v, nullptr, 0, ce);
|
||||
v.get_method_list(&methods);
|
||||
} else {
|
||||
Ref<Script> script_ref = ObjectDB::get_ref<Script>(script);
|
||||
if (script_ref.is_valid()) {
|
||||
if (script_ref->is_built_in()) {
|
||||
script_ref->reload(true);
|
||||
}
|
||||
|
||||
List<MethodInfo> script_methods;
|
||||
script_ref->get_script_method_list(&script_methods);
|
||||
|
||||
methods.push_back(MethodInfo("*Script Methods")); // TODO: Split by inheritance.
|
||||
|
||||
for (const MethodInfo &mi : script_methods) {
|
||||
if (mi.name.begins_with("@")) {
|
||||
// GH-92782. GDScript inline setters/getters are historically present in `get_method_list()`
|
||||
// and can be called using `Object.call()`. However, these functions are meant to be internal
|
||||
// and their names are not valid identifiers, so let's hide them from the user.
|
||||
continue;
|
||||
}
|
||||
methods.push_back(mi);
|
||||
}
|
||||
}
|
||||
|
||||
StringName base = base_type;
|
||||
while (base) {
|
||||
methods.push_back(MethodInfo("*" + String(base)));
|
||||
ClassDB::get_method_list(base, &methods, true, true);
|
||||
base = ClassDB::get_parent_class(base);
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *category = nullptr;
|
||||
|
||||
bool found = false;
|
||||
bool script_methods = false;
|
||||
|
||||
for (MethodInfo &mi : methods) {
|
||||
if (mi.name.begins_with("*")) {
|
||||
if (category && category->get_first_child() == nullptr) {
|
||||
memdelete(category); //old category was unused
|
||||
}
|
||||
category = search_options->create_item(root);
|
||||
category->set_text(0, mi.name.replace_first("*", ""));
|
||||
category->set_selectable(0, false);
|
||||
|
||||
Ref<Texture2D> icon;
|
||||
script_methods = false;
|
||||
String rep = mi.name.remove_char('*');
|
||||
if (mi.name == "*Script Methods") {
|
||||
icon = search_options->get_editor_theme_icon(SNAME("Script"));
|
||||
script_methods = true;
|
||||
} else {
|
||||
icon = EditorNode::get_singleton()->get_class_icon(rep);
|
||||
}
|
||||
category->set_icon(0, icon);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = mi.name.get_slicec(':', 0);
|
||||
if (!script_methods && name.begins_with("_") && !(mi.flags & METHOD_FLAG_VIRTUAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (virtuals_only && !(mi.flags & METHOD_FLAG_VIRTUAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!virtuals_only && (mi.flags & METHOD_FLAG_VIRTUAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!search_box->get_text().is_empty() && !name.containsn(search_text)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem *item = search_options->create_item(category ? category : root);
|
||||
|
||||
String desc;
|
||||
if (mi.name.contains_char(':')) {
|
||||
desc = mi.name.get_slicec(':', 1) + " ";
|
||||
mi.name = mi.name.get_slicec(':', 0);
|
||||
} else if (mi.return_val.type != Variant::NIL) {
|
||||
desc = Variant::get_type_name(mi.return_val.type);
|
||||
} else {
|
||||
desc = "void";
|
||||
}
|
||||
|
||||
desc += vformat(" %s(", mi.name);
|
||||
|
||||
for (int64_t i = 0; i < mi.arguments.size(); ++i) {
|
||||
PropertyInfo &arg = mi.arguments.write[i];
|
||||
if (i > 0) {
|
||||
desc += ", ";
|
||||
}
|
||||
|
||||
desc += arg.name;
|
||||
|
||||
if (arg.type == Variant::NIL) {
|
||||
desc += ": Variant";
|
||||
} else if (arg.name.contains_char(':')) {
|
||||
desc += vformat(": %s", arg.name.get_slicec(':', 1));
|
||||
arg.name = arg.name.get_slicec(':', 0);
|
||||
} else {
|
||||
desc += vformat(": %s", Variant::get_type_name(arg.type));
|
||||
}
|
||||
}
|
||||
|
||||
if (mi.flags & METHOD_FLAG_VARARG) {
|
||||
desc += mi.arguments.is_empty() ? "..." : ", ...";
|
||||
}
|
||||
|
||||
desc += ")";
|
||||
|
||||
if (mi.flags & METHOD_FLAG_VARARG) {
|
||||
desc += " vararg";
|
||||
}
|
||||
|
||||
if (mi.flags & METHOD_FLAG_CONST) {
|
||||
desc += " const";
|
||||
}
|
||||
|
||||
if (mi.flags & METHOD_FLAG_VIRTUAL) {
|
||||
desc += " virtual";
|
||||
}
|
||||
|
||||
item->set_text(0, desc);
|
||||
item->set_metadata(0, name);
|
||||
item->set_selectable(0, true);
|
||||
|
||||
if (!found && !search_box->get_text().is_empty() && name.containsn(search_text)) {
|
||||
item->select(0);
|
||||
found = true;
|
||||
} else if (!found && search_box->get_text().is_empty() && name == selected) {
|
||||
item->select(0);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (category && category->get_first_child() == nullptr) {
|
||||
memdelete(category); //old category was unused
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// As we call this while adding items, defer until list is completely populated.
|
||||
callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
|
||||
}
|
||||
}
|
||||
|
||||
get_ok_button()->set_disabled(search_options->get_selected() == nullptr);
|
||||
}
|
||||
|
||||
void PropertySelector::_confirmed() {
|
||||
TreeItem *ti = search_options->get_selected();
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
emit_signal(SNAME("selected"), ti->get_metadata(0));
|
||||
hide();
|
||||
}
|
||||
|
||||
void PropertySelector::_item_selected() {
|
||||
help_bit->set_custom_text(String(), String(), String());
|
||||
|
||||
TreeItem *item = search_options->get_selected();
|
||||
get_ok_button()->set_disabled(item == nullptr);
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
String name = item->get_metadata(0);
|
||||
|
||||
String class_type;
|
||||
if (type != Variant::NIL) {
|
||||
class_type = Variant::get_type_name(type);
|
||||
} else if (!base_type.is_empty()) {
|
||||
class_type = base_type;
|
||||
} else if (instance) {
|
||||
class_type = instance->get_class();
|
||||
}
|
||||
|
||||
String text;
|
||||
while (!class_type.is_empty()) {
|
||||
if (properties) {
|
||||
if (ClassDB::has_property(class_type, name, true)) {
|
||||
help_bit->parse_symbol("property|" + class_type + "|" + name);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (ClassDB::has_method(class_type, name, true)) {
|
||||
help_bit->parse_symbol("method|" + class_type + "|" + name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// It may be from a parent class, keep looking.
|
||||
class_type = ClassDB::get_parent_class(class_type);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertySelector::_hide_requested() {
|
||||
_cancel_pressed(); // From AcceptDialog.
|
||||
}
|
||||
|
||||
void PropertySelector::_create_subproperties(TreeItem *p_parent_item, Variant::Type p_type) {
|
||||
switch (p_type) {
|
||||
case Variant::VECTOR2: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
case Variant::VECTOR2I: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::INT);
|
||||
} break;
|
||||
|
||||
case Variant::RECT2: {
|
||||
_create_subproperty(p_parent_item, "position", Variant::VECTOR2);
|
||||
_create_subproperty(p_parent_item, "size", Variant::VECTOR2);
|
||||
_create_subproperty(p_parent_item, "end", Variant::VECTOR2);
|
||||
} break;
|
||||
|
||||
case Variant::RECT2I: {
|
||||
_create_subproperty(p_parent_item, "position", Variant::VECTOR2I);
|
||||
_create_subproperty(p_parent_item, "size", Variant::VECTOR2I);
|
||||
_create_subproperty(p_parent_item, "end", Variant::VECTOR2I);
|
||||
} break;
|
||||
|
||||
case Variant::VECTOR3: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
case Variant::VECTOR3I: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::INT);
|
||||
} break;
|
||||
|
||||
case Variant::TRANSFORM2D: {
|
||||
_create_subproperty(p_parent_item, "origin", Variant::VECTOR2);
|
||||
_create_subproperty(p_parent_item, "x", Variant::VECTOR2);
|
||||
_create_subproperty(p_parent_item, "y", Variant::VECTOR2);
|
||||
} break;
|
||||
|
||||
case Variant::VECTOR4: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "w", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
case Variant::VECTOR4I: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "w", Variant::INT);
|
||||
} break;
|
||||
|
||||
case Variant::PLANE: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "normal", Variant::VECTOR3);
|
||||
_create_subproperty(p_parent_item, "d", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
case Variant::QUATERNION: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "y", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "z", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "w", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
case Variant::AABB: {
|
||||
_create_subproperty(p_parent_item, "position", Variant::VECTOR3);
|
||||
_create_subproperty(p_parent_item, "size", Variant::VECTOR3);
|
||||
_create_subproperty(p_parent_item, "end", Variant::VECTOR3);
|
||||
} break;
|
||||
|
||||
case Variant::BASIS: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::VECTOR3);
|
||||
_create_subproperty(p_parent_item, "y", Variant::VECTOR3);
|
||||
_create_subproperty(p_parent_item, "z", Variant::VECTOR3);
|
||||
} break;
|
||||
|
||||
case Variant::TRANSFORM3D: {
|
||||
_create_subproperty(p_parent_item, "basis", Variant::BASIS);
|
||||
_create_subproperty(p_parent_item, "origin", Variant::VECTOR3);
|
||||
} break;
|
||||
|
||||
case Variant::PROJECTION: {
|
||||
_create_subproperty(p_parent_item, "x", Variant::VECTOR4);
|
||||
_create_subproperty(p_parent_item, "y", Variant::VECTOR4);
|
||||
_create_subproperty(p_parent_item, "z", Variant::VECTOR4);
|
||||
_create_subproperty(p_parent_item, "w", Variant::VECTOR4);
|
||||
} break;
|
||||
|
||||
case Variant::COLOR: {
|
||||
_create_subproperty(p_parent_item, "r", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "g", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "b", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "a", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "r8", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "g8", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "b8", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "a8", Variant::INT);
|
||||
_create_subproperty(p_parent_item, "h", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "s", Variant::FLOAT);
|
||||
_create_subproperty(p_parent_item, "v", Variant::FLOAT);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertySelector::_create_subproperty(TreeItem *p_parent_item, const String &p_name, Variant::Type p_type) {
|
||||
if (!type_filter.is_empty() && !type_filter.has(p_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = search_options->create_item(p_parent_item);
|
||||
item->set_text(0, p_name);
|
||||
item->set_metadata(0, String(p_parent_item->get_metadata(0)) + ":" + p_name);
|
||||
item->set_icon(0, search_options->get_editor_theme_icon(Variant::get_type_name(p_type)));
|
||||
|
||||
_create_subproperties(item, p_type);
|
||||
}
|
||||
|
||||
void PropertySelector::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
connect(SceneStringName(confirmed), callable_mp(this, &PropertySelector::_confirmed));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
disconnect(SceneStringName(confirmed), callable_mp(this, &PropertySelector::_confirmed));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only) {
|
||||
base_type = p_base;
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = ObjectID();
|
||||
properties = false;
|
||||
instance = nullptr;
|
||||
virtuals_only = p_virtuals_only;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_method_from_script(const Ref<Script> &p_script, const String &p_current) {
|
||||
ERR_FAIL_COND(p_script.is_null());
|
||||
base_type = p_script->get_instance_base_type();
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = p_script->get_instance_id();
|
||||
properties = false;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_method_from_basic_type(Variant::Type p_type, const String &p_current) {
|
||||
ERR_FAIL_COND(p_type == Variant::NIL);
|
||||
base_type = "";
|
||||
selected = p_current;
|
||||
type = p_type;
|
||||
script = ObjectID();
|
||||
properties = false;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_method_from_instance(Object *p_instance, const String &p_current) {
|
||||
base_type = p_instance->get_class();
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = ObjectID();
|
||||
{
|
||||
Ref<Script> scr = p_instance->get_script();
|
||||
if (scr.is_valid()) {
|
||||
script = scr->get_instance_id();
|
||||
}
|
||||
}
|
||||
properties = false;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_property_from_base_type(const String &p_base, const String &p_current) {
|
||||
base_type = p_base;
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = ObjectID();
|
||||
properties = true;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_property_from_script(const Ref<Script> &p_script, const String &p_current) {
|
||||
ERR_FAIL_COND(p_script.is_null());
|
||||
|
||||
base_type = p_script->get_instance_base_type();
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = p_script->get_instance_id();
|
||||
properties = true;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_property_from_basic_type(Variant::Type p_type, const String &p_current) {
|
||||
ERR_FAIL_COND(p_type == Variant::NIL);
|
||||
base_type = "";
|
||||
selected = p_current;
|
||||
type = p_type;
|
||||
script = ObjectID();
|
||||
properties = true;
|
||||
instance = nullptr;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::select_property_from_instance(Object *p_instance, const String &p_current) {
|
||||
base_type = "";
|
||||
selected = p_current;
|
||||
type = Variant::NIL;
|
||||
script = ObjectID();
|
||||
properties = true;
|
||||
instance = p_instance;
|
||||
virtuals_only = false;
|
||||
|
||||
popup_centered_ratio(0.6);
|
||||
search_box->set_text("");
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void PropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) {
|
||||
type_filter = p_type_filter;
|
||||
}
|
||||
|
||||
void PropertySelector::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name")));
|
||||
}
|
||||
|
||||
PropertySelector::PropertySelector() {
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
//set_child_rect(vbc);
|
||||
search_box = memnew(LineEdit);
|
||||
vbc->add_margin_child(TTR("Search:"), search_box);
|
||||
search_box->connect(SceneStringName(text_changed), callable_mp(this, &PropertySelector::_text_changed));
|
||||
search_box->connect(SceneStringName(gui_input), callable_mp(this, &PropertySelector::_sbox_input));
|
||||
search_options = memnew(Tree);
|
||||
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
vbc->add_margin_child(TTR("Matches:"), search_options, true);
|
||||
set_ok_button_text(TTR("Open"));
|
||||
get_ok_button()->set_disabled(true);
|
||||
register_text_enter(search_box);
|
||||
set_hide_on_ok(false);
|
||||
search_options->connect("item_activated", callable_mp(this, &PropertySelector::_confirmed));
|
||||
search_options->connect("cell_selected", callable_mp(this, &PropertySelector::_item_selected));
|
||||
search_options->set_hide_root(true);
|
||||
|
||||
help_bit = memnew(EditorHelpBit);
|
||||
help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
|
||||
help_bit->connect("request_hide", callable_mp(this, &PropertySelector::_hide_requested));
|
||||
vbc->add_margin_child(TTR("Description:"), help_bit);
|
||||
}
|
||||
86
editor/inspector/property_selector.h
Normal file
86
editor/inspector/property_selector.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**************************************************************************/
|
||||
/* property_selector.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class EditorHelpBit;
|
||||
class LineEdit;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
|
||||
class PropertySelector : public ConfirmationDialog {
|
||||
GDCLASS(PropertySelector, ConfirmationDialog);
|
||||
|
||||
LineEdit *search_box = nullptr;
|
||||
Tree *search_options = nullptr;
|
||||
|
||||
void _text_changed(const String &p_newtext);
|
||||
void _sbox_input(const Ref<InputEvent> &p_event);
|
||||
void _update_search();
|
||||
void _confirmed();
|
||||
void _item_selected();
|
||||
void _hide_requested();
|
||||
|
||||
EditorHelpBit *help_bit = nullptr;
|
||||
|
||||
bool properties = false;
|
||||
String selected;
|
||||
Variant::Type type;
|
||||
String base_type;
|
||||
ObjectID script;
|
||||
Object *instance = nullptr;
|
||||
bool virtuals_only = false;
|
||||
|
||||
Vector<Variant::Type> type_filter;
|
||||
|
||||
void _create_subproperties(TreeItem *p_parent_item, Variant::Type p_type);
|
||||
void _create_subproperty(TreeItem *p_parent_item, const String &p_name, Variant::Type p_type);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void select_method_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false);
|
||||
void select_method_from_script(const Ref<Script> &p_script, const String &p_current = "");
|
||||
void select_method_from_basic_type(Variant::Type p_type, const String &p_current = "");
|
||||
void select_method_from_instance(Object *p_instance, const String &p_current = "");
|
||||
|
||||
void select_property_from_base_type(const String &p_base, const String &p_current = "");
|
||||
void select_property_from_script(const Ref<Script> &p_script, const String &p_current = "");
|
||||
void select_property_from_basic_type(Variant::Type p_type, const String &p_current = "");
|
||||
void select_property_from_instance(Object *p_instance, const String &p_current = "");
|
||||
|
||||
void set_type_filter(const Vector<Variant::Type> &p_type_filter);
|
||||
|
||||
PropertySelector();
|
||||
};
|
||||
52
editor/inspector/sub_viewport_preview_editor_plugin.cpp
Normal file
52
editor/inspector/sub_viewport_preview_editor_plugin.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/**************************************************************************/
|
||||
/* sub_viewport_preview_editor_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "sub_viewport_preview_editor_plugin.h"
|
||||
|
||||
#include "scene/main/viewport.h"
|
||||
|
||||
bool EditorInspectorPluginSubViewportPreview::can_handle(Object *p_object) {
|
||||
return Object::cast_to<SubViewport>(p_object) != nullptr;
|
||||
}
|
||||
|
||||
void EditorInspectorPluginSubViewportPreview::parse_begin(Object *p_object) {
|
||||
SubViewport *sub_viewport = Object::cast_to<SubViewport>(p_object);
|
||||
|
||||
TexturePreview *sub_viewport_preview = memnew(TexturePreview(sub_viewport->get_texture(), false));
|
||||
// Otherwise `sub_viewport_preview`'s `texture_display` doesn't update properly when `sub_viewport`'s size changes.
|
||||
sub_viewport->connect("size_changed", callable_mp((CanvasItem *)sub_viewport_preview->get_texture_display(), &CanvasItem::queue_redraw));
|
||||
add_custom_control(sub_viewport_preview);
|
||||
}
|
||||
|
||||
SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin() {
|
||||
Ref<EditorInspectorPluginSubViewportPreview> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
||||
51
editor/inspector/sub_viewport_preview_editor_plugin.h
Normal file
51
editor/inspector/sub_viewport_preview_editor_plugin.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/**************************************************************************/
|
||||
/* sub_viewport_preview_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "editor/scene/texture/texture_editor_plugin.h"
|
||||
|
||||
class EditorInspectorPluginSubViewportPreview : public EditorInspectorPluginTexture {
|
||||
GDCLASS(EditorInspectorPluginSubViewportPreview, EditorInspectorPluginTexture);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual void parse_begin(Object *p_object) override;
|
||||
};
|
||||
|
||||
class SubViewportPreviewEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(SubViewportPreviewEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "SubViewportPreview"; }
|
||||
|
||||
SubViewportPreviewEditorPlugin();
|
||||
};
|
||||
75
editor/inspector/tool_button_editor_plugin.cpp
Normal file
75
editor/inspector/tool_button_editor_plugin.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/**************************************************************************/
|
||||
/* tool_button_editor_plugin.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "tool_button_editor_plugin.h"
|
||||
|
||||
void EditorInspectorToolButtonPlugin::_call_action(const Variant &p_object, const StringName &p_property) {
|
||||
Object *object = p_object.get_validated_object();
|
||||
ERR_FAIL_NULL_MSG(object, vformat(R"(Failed to get property "%s" on a previously freed instance.)", p_property));
|
||||
|
||||
const Variant value = object->get(p_property);
|
||||
ERR_FAIL_COND_MSG(value.get_type() != Variant::CALLABLE, vformat(R"(The value of property "%s" is %s, but Callable was expected.)", p_property, Variant::get_type_name(value.get_type())));
|
||||
|
||||
const Callable callable = value;
|
||||
ERR_FAIL_COND_MSG(!callable.is_valid(), vformat(R"(Tool button action "%s" is an invalid callable.)", callable));
|
||||
|
||||
Variant ret;
|
||||
Callable::CallError ce;
|
||||
callable.callp(nullptr, 0, ret, ce);
|
||||
ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, vformat(R"(Error calling tool button action "%s": %s)", callable, Variant::get_call_error_text(callable.get_method(), nullptr, 0, ce)));
|
||||
}
|
||||
|
||||
bool EditorInspectorToolButtonPlugin::can_handle(Object *p_object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorInspectorToolButtonPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
|
||||
if (p_type != Variant::CALLABLE || p_hint != PROPERTY_HINT_TOOL_BUTTON || !p_usage.has_flag(PROPERTY_USAGE_EDITOR)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const PackedStringArray splits = p_hint_text.rsplit(",", true, 1);
|
||||
const String &hint_text = splits[0]; // Safe since `splits` cannot be empty.
|
||||
const String &hint_icon = splits.size() > 1 ? splits[1] : "Callable";
|
||||
|
||||
EditorInspectorActionButton *action_button = memnew(EditorInspectorActionButton(hint_text, hint_icon));
|
||||
action_button->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);
|
||||
action_button->set_disabled(p_usage & PROPERTY_USAGE_READ_ONLY);
|
||||
action_button->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorToolButtonPlugin::_call_action).bind(p_object, p_path));
|
||||
|
||||
add_custom_control(action_button);
|
||||
return true;
|
||||
}
|
||||
|
||||
ToolButtonEditorPlugin::ToolButtonEditorPlugin() {
|
||||
Ref<EditorInspectorToolButtonPlugin> plugin;
|
||||
plugin.instantiate();
|
||||
add_inspector_plugin(plugin);
|
||||
}
|
||||
53
editor/inspector/tool_button_editor_plugin.h
Normal file
53
editor/inspector/tool_button_editor_plugin.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**************************************************************************/
|
||||
/* tool_button_editor_plugin.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor/inspector/editor_inspector.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
||||
class EditorInspectorToolButtonPlugin : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorToolButtonPlugin, EditorInspectorPlugin);
|
||||
|
||||
void _call_action(const Variant &p_object, const StringName &p_property);
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object) override;
|
||||
virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override;
|
||||
};
|
||||
|
||||
class ToolButtonEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(ToolButtonEditorPlugin, EditorPlugin);
|
||||
|
||||
public:
|
||||
virtual String get_plugin_name() const override { return "ToolButtonEditorPlugin"; }
|
||||
|
||||
ToolButtonEditorPlugin();
|
||||
};
|
||||
Reference in New Issue
Block a user