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

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

6
editor/inspector/SCsub Normal file
View 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")

View 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);
}
}

View 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;
};

View 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;
}

View 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();
};

View 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

File diff suppressed because it is too large Load Diff

View 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();
};

View 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>();
}

View 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;
};

File diff suppressed because it is too large Load Diff

View 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);
};

File diff suppressed because it is too large Load Diff

View 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();
};

View 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")) {}

View 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);
};

View 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;
}

View 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();
};

File diff suppressed because it is too large Load Diff

View 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();
};

View 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();
}

View 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();
};

View 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;
}

View 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;
};

View 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);
}

View 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();
};

View 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);
}

View 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();
};

View 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);
}

View 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;
}
};

View 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);
}

View 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();
};

View 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);
}

View 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();
};

View 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);
}

View 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();
};