feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
148
engine/editor/SCsub
Normal file
148
engine/editor/SCsub
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.editor_sources = []
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
import editor_builders
|
||||
|
||||
import methods
|
||||
|
||||
if env.editor_build:
|
||||
# Generate doc data paths
|
||||
def doc_data_class_path_builder(target, source, env):
|
||||
paths = dict(sorted(source[0].read().items()))
|
||||
data = "\n".join([f'\t{{"{key}", "{value}"}},' for key, value in paths.items()])
|
||||
with methods.generated_wrapper(target) as file:
|
||||
file.write(
|
||||
f"""\
|
||||
static const int _doc_data_class_path_count = {len(paths)};
|
||||
|
||||
struct _DocDataClassPath {{
|
||||
const char *name;
|
||||
const char *path;
|
||||
}};
|
||||
|
||||
static const _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) + 1}] = {{
|
||||
{data}
|
||||
{{nullptr, nullptr}},
|
||||
}};
|
||||
"""
|
||||
)
|
||||
|
||||
env.CommandNoCache("doc_data_class_path.gen.h", env.Value(env.doc_class_path), env.Run(doc_data_class_path_builder))
|
||||
|
||||
# Register exporters
|
||||
def register_exporters_builder(target, source, env):
|
||||
platforms = source[0].read()
|
||||
exp_inc = "\n".join([f'#include "platform/{p}/export/export.h"' for p in platforms])
|
||||
exp_reg = "\n".join([f"\tregister_{p}_exporter();" for p in platforms])
|
||||
exp_type = "\n".join([f"\tregister_{p}_exporter_types();" for p in platforms])
|
||||
with methods.generated_wrapper(target) as file:
|
||||
file.write(
|
||||
f"""\
|
||||
#include "register_exporters.h"
|
||||
|
||||
{exp_inc}
|
||||
|
||||
void register_exporters() {{
|
||||
{exp_reg}
|
||||
}}
|
||||
|
||||
void register_exporter_types() {{
|
||||
{exp_type}
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
gen_exporters = env.CommandNoCache(
|
||||
"register_exporters.gen.cpp", env.Value(env.platform_exporters), env.Run(register_exporters_builder)
|
||||
)
|
||||
for e in env.platform_exporters:
|
||||
# Add all .cpp files in export folder
|
||||
env.add_source_files(env.editor_sources, f"../platform/{e}/export/*.cpp")
|
||||
|
||||
# Core API documentation.
|
||||
docs = []
|
||||
docs += Glob("#doc/classes/*.xml")
|
||||
|
||||
# Module API documentation.
|
||||
module_dirs = []
|
||||
for d in env.doc_class_path.values():
|
||||
if d not in module_dirs:
|
||||
module_dirs.append(d)
|
||||
|
||||
for d in module_dirs:
|
||||
if not os.path.isabs(d):
|
||||
docs += Glob("#" + d + "/*.xml") # Built-in.
|
||||
else:
|
||||
docs += Glob(d + "/*.xml") # Custom.
|
||||
|
||||
docs = sorted(docs)
|
||||
env.Depends("#editor/doc_data_compressed.gen.h", docs)
|
||||
env.CommandNoCache(
|
||||
"#editor/doc_data_compressed.gen.h",
|
||||
docs,
|
||||
env.Run(editor_builders.make_doc_header),
|
||||
)
|
||||
|
||||
# Editor interface and class reference translations incur a significant size
|
||||
# cost for the editor binary (see godot-proposals#3421).
|
||||
# To limit it, we only include translations with a high enough completion
|
||||
# ratio (20% for the editor UI, 10% for the class reference).
|
||||
# Generated with `make include-list` for each resource.
|
||||
|
||||
# Editor translations
|
||||
tlist = glob.glob(env.Dir("#editor/translations/editor").abspath + "/*.po")
|
||||
env.Depends("#editor/editor_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/editor_translations.gen.h",
|
||||
tlist,
|
||||
env.Run(editor_builders.make_editor_translations_header),
|
||||
)
|
||||
|
||||
# Property translations
|
||||
tlist = glob.glob(env.Dir("#editor/translations/properties").abspath + "/*.po")
|
||||
env.Depends("#editor/property_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/property_translations.gen.h",
|
||||
tlist,
|
||||
env.Run(editor_builders.make_property_translations_header),
|
||||
)
|
||||
|
||||
# Documentation translations
|
||||
tlist = glob.glob(env.Dir("#doc/translations").abspath + "/*.po")
|
||||
env.Depends("#editor/doc_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/doc_translations.gen.h",
|
||||
tlist,
|
||||
env.Run(editor_builders.make_doc_translations_header),
|
||||
)
|
||||
|
||||
# Extractable translations
|
||||
tlist = glob.glob(env.Dir("#editor/translations/extractable").abspath + "/*.po")
|
||||
tlist.extend(glob.glob(env.Dir("#editor/translations/extractable").abspath + "/extractable.pot"))
|
||||
env.Depends("#editor/extractable_translations.gen.h", tlist)
|
||||
env.CommandNoCache(
|
||||
"#editor/extractable_translations.gen.h",
|
||||
tlist,
|
||||
env.Run(editor_builders.make_extractable_translations_header),
|
||||
)
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
env.add_source_files(env.editor_sources, gen_exporters)
|
||||
|
||||
SConscript("debugger/SCsub")
|
||||
SConscript("export/SCsub")
|
||||
SConscript("gui/SCsub")
|
||||
SConscript("icons/SCsub")
|
||||
SConscript("import/SCsub")
|
||||
SConscript("plugins/SCsub")
|
||||
SConscript("project_manager/SCsub")
|
||||
SConscript("themes/SCsub")
|
||||
|
||||
lib = env.add_library("editor", env.editor_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
622
engine/editor/action_map_editor.cpp
Normal file
622
engine/editor/action_map_editor.cpp
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
/**************************************************************************/
|
||||
/* action_map_editor.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/action_map_editor.h"
|
||||
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/event_listener_line_edit.h"
|
||||
#include "editor/input_event_configuration_dialog.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
static bool _is_action_name_valid(const String &p_name) {
|
||||
const char32_t *cstr = p_name.get_data();
|
||||
for (int i = 0; cstr[i]; i++) {
|
||||
if (cstr[i] == '/' || cstr[i] == ':' || cstr[i] == '"' ||
|
||||
cstr[i] == '=' || cstr[i] == '\\' || cstr[i] < 32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ActionMapEditor::_event_config_confirmed() {
|
||||
Ref<InputEvent> ev = event_config_dialog->get_event();
|
||||
|
||||
Dictionary new_action = current_action.duplicate();
|
||||
Array events = new_action["events"].duplicate();
|
||||
|
||||
if (current_action_event_index == -1) {
|
||||
// Add new event
|
||||
events.push_back(ev);
|
||||
} else {
|
||||
// Edit existing event
|
||||
events[current_action_event_index] = ev;
|
||||
}
|
||||
|
||||
new_action["events"] = events;
|
||||
emit_signal(SNAME("action_edited"), current_action_name, new_action);
|
||||
}
|
||||
|
||||
void ActionMapEditor::_add_action_pressed() {
|
||||
_add_action(add_edit->get_text());
|
||||
}
|
||||
|
||||
String ActionMapEditor::_check_new_action_name(const String &p_name) {
|
||||
if (p_name.is_empty() || !_is_action_name_valid(p_name)) {
|
||||
return TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or '\"'");
|
||||
}
|
||||
|
||||
if (_has_action(p_name)) {
|
||||
return vformat(TTR("An action with the name '%s' already exists."), p_name);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void ActionMapEditor::_add_edit_text_changed(const String &p_name) {
|
||||
String error = _check_new_action_name(p_name);
|
||||
add_button->set_tooltip_text(error);
|
||||
add_button->set_disabled(!error.is_empty());
|
||||
}
|
||||
|
||||
bool ActionMapEditor::_has_action(const String &p_name) const {
|
||||
for (const ActionInfo &action_info : actions_cache) {
|
||||
if (p_name == action_info.name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActionMapEditor::_add_action(const String &p_name) {
|
||||
String error = _check_new_action_name(p_name);
|
||||
if (!error.is_empty()) {
|
||||
show_message(error);
|
||||
return;
|
||||
}
|
||||
|
||||
add_edit->clear();
|
||||
emit_signal(SNAME("action_added"), p_name);
|
||||
}
|
||||
|
||||
void ActionMapEditor::_action_edited() {
|
||||
TreeItem *ti = action_tree->get_edited();
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action_tree->get_selected_column() == 0) {
|
||||
// Name Edited
|
||||
String new_name = ti->get_text(0);
|
||||
String old_name = ti->get_meta("__name");
|
||||
|
||||
if (new_name == old_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_name.is_empty() || !_is_action_name_valid(new_name)) {
|
||||
ti->set_text(0, old_name);
|
||||
show_message(TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or '\"'"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_has_action(new_name)) {
|
||||
ti->set_text(0, old_name);
|
||||
show_message(vformat(TTR("An action with the name '%s' already exists."), new_name));
|
||||
return;
|
||||
}
|
||||
|
||||
emit_signal(SNAME("action_renamed"), old_name, new_name);
|
||||
} else if (action_tree->get_selected_column() == 1) {
|
||||
// Deadzone Edited
|
||||
String name = ti->get_meta("__name");
|
||||
Dictionary old_action = ti->get_meta("__action");
|
||||
Dictionary new_action = old_action.duplicate();
|
||||
new_action["deadzone"] = ti->get_range(1);
|
||||
|
||||
// Call deferred so that input can finish propagating through tree, allowing re-making of tree to occur.
|
||||
call_deferred(SNAME("emit_signal"), "action_edited", name, new_action);
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMapEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
|
||||
if (p_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemButton option = (ItemButton)p_id;
|
||||
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_item);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case ActionMapEditor::BUTTON_ADD_EVENT: {
|
||||
current_action = item->get_meta("__action");
|
||||
current_action_name = item->get_meta("__name");
|
||||
current_action_event_index = -1;
|
||||
|
||||
event_config_dialog->popup_and_configure(Ref<InputEvent>(), current_action_name);
|
||||
} break;
|
||||
case ActionMapEditor::BUTTON_EDIT_EVENT: {
|
||||
// Action and Action name is located on the parent of the event.
|
||||
current_action = item->get_parent()->get_meta("__action");
|
||||
current_action_name = item->get_parent()->get_meta("__name");
|
||||
|
||||
current_action_event_index = item->get_meta("__index");
|
||||
|
||||
Ref<InputEvent> ie = item->get_meta("__event");
|
||||
if (ie.is_valid()) {
|
||||
event_config_dialog->popup_and_configure(ie, current_action_name);
|
||||
}
|
||||
} break;
|
||||
case ActionMapEditor::BUTTON_REMOVE_ACTION: {
|
||||
// Send removed action name
|
||||
String name = item->get_meta("__name");
|
||||
emit_signal(SNAME("action_removed"), name);
|
||||
} break;
|
||||
case ActionMapEditor::BUTTON_REMOVE_EVENT: {
|
||||
// Remove event and send updated action
|
||||
Dictionary action = item->get_parent()->get_meta("__action").duplicate();
|
||||
String action_name = item->get_parent()->get_meta("__name");
|
||||
|
||||
int event_index = item->get_meta("__index");
|
||||
|
||||
Array events = action["events"].duplicate();
|
||||
events.remove_at(event_index);
|
||||
action["events"] = events;
|
||||
|
||||
emit_signal(SNAME("action_edited"), action_name, action);
|
||||
} break;
|
||||
case ActionMapEditor::BUTTON_REVERT_ACTION: {
|
||||
ERR_FAIL_COND_MSG(!item->has_meta("__action_initial"), "Tree Item for action which can be reverted is expected to have meta value with initial value of action.");
|
||||
|
||||
Dictionary action = item->get_meta("__action_initial").duplicate();
|
||||
String action_name = item->get_meta("__name");
|
||||
|
||||
emit_signal(SNAME("action_edited"), action_name, action);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMapEditor::_tree_item_activated() {
|
||||
TreeItem *item = action_tree->get_selected();
|
||||
|
||||
if (!item || !item->has_meta("__event")) {
|
||||
return;
|
||||
}
|
||||
|
||||
_tree_button_pressed(item, 2, BUTTON_EDIT_EVENT, MouseButton::LEFT);
|
||||
}
|
||||
|
||||
void ActionMapEditor::set_show_builtin_actions(bool p_show) {
|
||||
show_builtin_actions = p_show;
|
||||
show_builtin_actions_checkbutton->set_pressed(p_show);
|
||||
EditorSettings::get_singleton()->set_project_metadata("project_settings", "show_builtin_actions", show_builtin_actions);
|
||||
|
||||
// Prevent unnecessary updates of action list when cache is empty.
|
||||
if (!actions_cache.is_empty()) {
|
||||
update_action_list();
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMapEditor::_search_term_updated(const String &) {
|
||||
update_action_list();
|
||||
}
|
||||
|
||||
void ActionMapEditor::_search_by_event(const Ref<InputEvent> &p_event) {
|
||||
if (p_event.is_null() || (p_event->is_pressed() && !p_event->is_echo())) {
|
||||
update_action_list();
|
||||
}
|
||||
}
|
||||
|
||||
Variant ActionMapEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
|
||||
TreeItem *selected = action_tree->get_selected();
|
||||
if (!selected) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
String name = selected->get_text(0);
|
||||
Label *label = memnew(Label(name));
|
||||
label->set_theme_type_variation("HeaderSmall");
|
||||
label->set_modulate(Color(1, 1, 1, 1.0f));
|
||||
action_tree->set_drag_preview(label);
|
||||
|
||||
Dictionary drag_data;
|
||||
|
||||
if (selected->has_meta("__action")) {
|
||||
drag_data["input_type"] = "action";
|
||||
}
|
||||
|
||||
if (selected->has_meta("__event")) {
|
||||
drag_data["input_type"] = "event";
|
||||
}
|
||||
|
||||
action_tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
|
||||
|
||||
return drag_data;
|
||||
}
|
||||
|
||||
bool ActionMapEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
|
||||
Dictionary d = p_data;
|
||||
if (!d.has("input_type")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TreeItem *selected = action_tree->get_selected();
|
||||
TreeItem *item = action_tree->get_item_at_position(p_point);
|
||||
if (!selected || !item || item == selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow moving an action in-between events.
|
||||
if (d["input_type"] == "action" && item->has_meta("__event")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't allow moving an event to a different action.
|
||||
if (d["input_type"] == "event" && item->get_parent() != selected->get_parent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ActionMapEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
|
||||
if (!can_drop_data_fw(p_point, p_data, p_from)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *selected = action_tree->get_selected();
|
||||
TreeItem *target = action_tree->get_item_at_position(p_point);
|
||||
bool drop_above = action_tree->get_drop_section_at_position(p_point) == -1;
|
||||
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary d = p_data;
|
||||
if (d["input_type"] == "action") {
|
||||
// Change action order.
|
||||
String relative_to = target->get_meta("__name");
|
||||
String action_name = selected->get_meta("__name");
|
||||
emit_signal(SNAME("action_reordered"), action_name, relative_to, drop_above);
|
||||
|
||||
} else if (d["input_type"] == "event") {
|
||||
// Change event order
|
||||
int current_index = selected->get_meta("__index");
|
||||
int target_index = target->get_meta("__index");
|
||||
|
||||
// Construct new events array.
|
||||
Dictionary new_action = selected->get_parent()->get_meta("__action");
|
||||
|
||||
Array events = new_action["events"];
|
||||
Array new_events;
|
||||
|
||||
// The following method was used to perform the array changes since `remove` followed by `insert` was not working properly at time of writing.
|
||||
// Loop thought existing events
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
// If you come across the current index, just skip it, as it has been moved.
|
||||
if (i == current_index) {
|
||||
continue;
|
||||
} else if (i == target_index) {
|
||||
// We are at the target index. If drop above, add selected event there first, then target, so moved event goes on top.
|
||||
if (drop_above) {
|
||||
new_events.push_back(events[current_index]);
|
||||
new_events.push_back(events[target_index]);
|
||||
} else {
|
||||
new_events.push_back(events[target_index]);
|
||||
new_events.push_back(events[current_index]);
|
||||
}
|
||||
} else {
|
||||
new_events.push_back(events[i]);
|
||||
}
|
||||
}
|
||||
|
||||
new_action["events"] = new_events;
|
||||
emit_signal(SNAME("action_edited"), selected->get_parent()->get_meta("__name"), new_action);
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMapEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
action_list_search->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
add_button->set_icon(get_editor_theme_icon(SNAME("Add")));
|
||||
if (!actions_cache.is_empty()) {
|
||||
update_action_list();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMapEditor::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("action_added", PropertyInfo(Variant::STRING, "name")));
|
||||
ADD_SIGNAL(MethodInfo("action_edited", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::DICTIONARY, "new_action")));
|
||||
ADD_SIGNAL(MethodInfo("action_removed", PropertyInfo(Variant::STRING, "name")));
|
||||
ADD_SIGNAL(MethodInfo("action_renamed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
|
||||
ADD_SIGNAL(MethodInfo("action_reordered", PropertyInfo(Variant::STRING, "action_name"), PropertyInfo(Variant::STRING, "relative_to"), PropertyInfo(Variant::BOOL, "before")));
|
||||
ADD_SIGNAL(MethodInfo(SNAME("filter_focused")));
|
||||
ADD_SIGNAL(MethodInfo(SNAME("filter_unfocused")));
|
||||
}
|
||||
|
||||
LineEdit *ActionMapEditor::get_search_box() const {
|
||||
return action_list_search;
|
||||
}
|
||||
|
||||
LineEdit *ActionMapEditor::get_path_box() const {
|
||||
return add_edit;
|
||||
}
|
||||
|
||||
InputEventConfigurationDialog *ActionMapEditor::get_configuration_dialog() {
|
||||
return event_config_dialog;
|
||||
}
|
||||
|
||||
bool ActionMapEditor::_should_display_action(const String &p_name, const Array &p_events) const {
|
||||
const Ref<InputEvent> search_ev = action_list_search_by_event->get_event();
|
||||
bool event_match = true;
|
||||
if (search_ev.is_valid()) {
|
||||
event_match = false;
|
||||
for (int i = 0; i < p_events.size(); ++i) {
|
||||
const Ref<InputEvent> ev = p_events[i];
|
||||
if (ev.is_valid() && ev->is_match(search_ev, true)) {
|
||||
event_match = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event_match && action_list_search->get_text().is_subsequence_ofn(p_name);
|
||||
}
|
||||
|
||||
void ActionMapEditor::update_action_list(const Vector<ActionInfo> &p_action_infos) {
|
||||
if (!p_action_infos.is_empty()) {
|
||||
actions_cache = p_action_infos;
|
||||
}
|
||||
|
||||
action_tree->clear();
|
||||
TreeItem *root = action_tree->create_item();
|
||||
|
||||
for (int i = 0; i < actions_cache.size(); i++) {
|
||||
ActionInfo action_info = actions_cache[i];
|
||||
|
||||
const Array events = action_info.action["events"];
|
||||
if (!_should_display_action(action_info.name, events)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!action_info.editable && !show_builtin_actions) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Variant deadzone = action_info.action["deadzone"];
|
||||
|
||||
// Update Tree...
|
||||
|
||||
TreeItem *action_item = action_tree->create_item(root);
|
||||
ERR_FAIL_NULL(action_item);
|
||||
action_item->set_meta("__action", action_info.action);
|
||||
action_item->set_meta("__name", action_info.name);
|
||||
|
||||
// First Column - Action Name
|
||||
action_item->set_text(0, action_info.name);
|
||||
action_item->set_editable(0, action_info.editable);
|
||||
action_item->set_icon(0, action_info.icon);
|
||||
|
||||
// Second Column - Deadzone
|
||||
action_item->set_editable(1, true);
|
||||
action_item->set_cell_mode(1, TreeItem::CELL_MODE_RANGE);
|
||||
action_item->set_range_config(1, 0.0, 1.0, 0.01);
|
||||
action_item->set_range(1, deadzone);
|
||||
|
||||
// Third column - buttons
|
||||
if (action_info.has_initial) {
|
||||
bool deadzone_eq = action_info.action_initial["deadzone"] == action_info.action["deadzone"];
|
||||
bool events_eq = Shortcut::is_event_array_equal(action_info.action_initial["events"], action_info.action["events"]);
|
||||
bool action_eq = deadzone_eq && events_eq;
|
||||
action_item->set_meta("__action_initial", action_info.action_initial);
|
||||
action_item->add_button(2, action_tree->get_editor_theme_icon(SNAME("ReloadSmall")), BUTTON_REVERT_ACTION, action_eq, action_eq ? TTR("Cannot Revert - Action is same as initial") : TTR("Revert Action"));
|
||||
}
|
||||
action_item->add_button(2, action_tree->get_editor_theme_icon(SNAME("Add")), BUTTON_ADD_EVENT, false, TTR("Add Event"));
|
||||
action_item->add_button(2, action_tree->get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_ACTION, !action_info.editable, action_info.editable ? TTR("Remove Action") : TTR("Cannot Remove Action"));
|
||||
|
||||
action_item->set_custom_bg_color(0, action_tree->get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
|
||||
action_item->set_custom_bg_color(1, action_tree->get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
|
||||
|
||||
for (int evnt_idx = 0; evnt_idx < events.size(); evnt_idx++) {
|
||||
Ref<InputEvent> event = events[evnt_idx];
|
||||
if (event.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem *event_item = action_tree->create_item(action_item);
|
||||
|
||||
// First Column - Text
|
||||
event_item->set_text(0, EventListenerLineEdit::get_event_text(event, true));
|
||||
event_item->set_meta("__event", event);
|
||||
event_item->set_meta("__index", evnt_idx);
|
||||
|
||||
// First Column - Icon
|
||||
Ref<InputEventKey> k = event;
|
||||
if (k.is_valid()) {
|
||||
if (k->get_physical_keycode() == Key::NONE && k->get_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("KeyboardLabel")));
|
||||
} else if (k->get_keycode() != Key::NONE) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("Keyboard")));
|
||||
} else if (k->get_physical_keycode() != Key::NONE) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("KeyboardPhysical")));
|
||||
} else {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("KeyboardError")));
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = event;
|
||||
if (mb.is_valid()) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("Mouse")));
|
||||
}
|
||||
|
||||
Ref<InputEventJoypadButton> jb = event;
|
||||
if (jb.is_valid()) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("JoyButton")));
|
||||
}
|
||||
|
||||
Ref<InputEventJoypadMotion> jm = event;
|
||||
if (jm.is_valid()) {
|
||||
event_item->set_icon(0, action_tree->get_editor_theme_icon(SNAME("JoyAxis")));
|
||||
}
|
||||
|
||||
// Third Column - Buttons
|
||||
event_item->add_button(2, action_tree->get_editor_theme_icon(SNAME("Edit")), BUTTON_EDIT_EVENT, false, TTR("Edit Event"));
|
||||
event_item->add_button(2, action_tree->get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_EVENT, false, TTR("Remove Event"));
|
||||
event_item->set_button_color(2, 0, Color(1, 1, 1, 0.75));
|
||||
event_item->set_button_color(2, 1, Color(1, 1, 1, 0.75));
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI.
|
||||
clear_all_search->set_disabled(action_list_search->get_text().is_empty() && action_list_search_by_event->get_event().is_null());
|
||||
}
|
||||
|
||||
void ActionMapEditor::show_message(const String &p_message) {
|
||||
message->set_text(p_message);
|
||||
message->popup_centered();
|
||||
}
|
||||
|
||||
void ActionMapEditor::use_external_search_box(LineEdit *p_searchbox) {
|
||||
memdelete(action_list_search);
|
||||
action_list_search = p_searchbox;
|
||||
action_list_search->connect(SceneStringName(text_changed), callable_mp(this, &ActionMapEditor::_search_term_updated));
|
||||
}
|
||||
|
||||
void ActionMapEditor::_on_filter_focused() {
|
||||
emit_signal(SNAME("filter_focused"));
|
||||
}
|
||||
|
||||
void ActionMapEditor::_on_filter_unfocused() {
|
||||
emit_signal(SNAME("filter_unfocused"));
|
||||
}
|
||||
|
||||
ActionMapEditor::ActionMapEditor() {
|
||||
// Main Vbox Container
|
||||
VBoxContainer *main_vbox = memnew(VBoxContainer);
|
||||
main_vbox->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
|
||||
add_child(main_vbox);
|
||||
|
||||
HBoxContainer *top_hbox = memnew(HBoxContainer);
|
||||
main_vbox->add_child(top_hbox);
|
||||
|
||||
action_list_search = memnew(LineEdit);
|
||||
action_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
action_list_search->set_placeholder(TTR("Filter by Name"));
|
||||
action_list_search->set_clear_button_enabled(true);
|
||||
action_list_search->connect(SceneStringName(text_changed), callable_mp(this, &ActionMapEditor::_search_term_updated));
|
||||
top_hbox->add_child(action_list_search);
|
||||
|
||||
action_list_search_by_event = memnew(EventListenerLineEdit);
|
||||
action_list_search_by_event->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
action_list_search_by_event->set_stretch_ratio(0.75);
|
||||
action_list_search_by_event->connect("event_changed", callable_mp(this, &ActionMapEditor::_search_by_event));
|
||||
action_list_search_by_event->connect(SceneStringName(focus_entered), callable_mp(this, &ActionMapEditor::_on_filter_focused));
|
||||
action_list_search_by_event->connect(SceneStringName(focus_exited), callable_mp(this, &ActionMapEditor::_on_filter_unfocused));
|
||||
top_hbox->add_child(action_list_search_by_event);
|
||||
|
||||
clear_all_search = memnew(Button);
|
||||
clear_all_search->set_text(TTR("Clear All"));
|
||||
clear_all_search->set_tooltip_text(TTR("Clear all search filters."));
|
||||
clear_all_search->connect(SceneStringName(pressed), callable_mp(action_list_search_by_event, &EventListenerLineEdit::clear_event));
|
||||
clear_all_search->connect(SceneStringName(pressed), callable_mp(action_list_search, &LineEdit::clear));
|
||||
top_hbox->add_child(clear_all_search);
|
||||
|
||||
// Adding Action line edit + button
|
||||
add_hbox = memnew(HBoxContainer);
|
||||
add_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
add_edit = memnew(LineEdit);
|
||||
add_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
add_edit->set_placeholder(TTR("Add New Action"));
|
||||
add_edit->set_clear_button_enabled(true);
|
||||
add_edit->connect(SceneStringName(text_changed), callable_mp(this, &ActionMapEditor::_add_edit_text_changed));
|
||||
add_edit->connect("text_submitted", callable_mp(this, &ActionMapEditor::_add_action));
|
||||
add_hbox->add_child(add_edit);
|
||||
|
||||
add_button = memnew(Button);
|
||||
add_button->set_text(TTR("Add"));
|
||||
add_button->connect(SceneStringName(pressed), callable_mp(this, &ActionMapEditor::_add_action_pressed));
|
||||
add_hbox->add_child(add_button);
|
||||
// Disable the button and set its tooltip.
|
||||
_add_edit_text_changed(add_edit->get_text());
|
||||
|
||||
add_hbox->add_child(memnew(VSeparator));
|
||||
|
||||
show_builtin_actions_checkbutton = memnew(CheckButton);
|
||||
show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions"));
|
||||
show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions));
|
||||
add_hbox->add_child(show_builtin_actions_checkbutton);
|
||||
|
||||
show_builtin_actions = EditorSettings::get_singleton()->get_project_metadata("project_settings", "show_builtin_actions", false);
|
||||
show_builtin_actions_checkbutton->set_pressed(show_builtin_actions);
|
||||
|
||||
main_vbox->add_child(add_hbox);
|
||||
|
||||
// Action Editor Tree
|
||||
action_tree = memnew(Tree);
|
||||
action_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
action_tree->set_columns(3);
|
||||
action_tree->set_hide_root(true);
|
||||
action_tree->set_column_titles_visible(true);
|
||||
action_tree->set_column_title(0, TTR("Action"));
|
||||
action_tree->set_column_clip_content(0, true);
|
||||
action_tree->set_column_title(1, TTR("Deadzone"));
|
||||
action_tree->set_column_expand(1, false);
|
||||
action_tree->set_column_custom_minimum_width(1, 80 * EDSCALE);
|
||||
action_tree->set_column_expand(2, false);
|
||||
action_tree->set_column_custom_minimum_width(2, 50 * EDSCALE);
|
||||
action_tree->connect("item_edited", callable_mp(this, &ActionMapEditor::_action_edited), CONNECT_DEFERRED);
|
||||
action_tree->connect("item_activated", callable_mp(this, &ActionMapEditor::_tree_item_activated));
|
||||
action_tree->connect("button_clicked", callable_mp(this, &ActionMapEditor::_tree_button_pressed));
|
||||
main_vbox->add_child(action_tree);
|
||||
|
||||
SET_DRAG_FORWARDING_GCD(action_tree, ActionMapEditor);
|
||||
|
||||
// Adding event dialog
|
||||
event_config_dialog = memnew(InputEventConfigurationDialog);
|
||||
event_config_dialog->connect(SceneStringName(confirmed), callable_mp(this, &ActionMapEditor::_event_config_confirmed));
|
||||
add_child(event_config_dialog);
|
||||
|
||||
message = memnew(AcceptDialog);
|
||||
add_child(message);
|
||||
}
|
||||
136
engine/editor/action_map_editor.h
Normal file
136
engine/editor/action_map_editor.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**************************************************************************/
|
||||
/* action_map_editor.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ACTION_MAP_EDITOR_H
|
||||
#define ACTION_MAP_EDITOR_H
|
||||
|
||||
#include "scene/gui/control.h"
|
||||
|
||||
class Button;
|
||||
class HBoxContainer;
|
||||
class EventListenerLineEdit;
|
||||
class LineEdit;
|
||||
class CheckButton;
|
||||
class AcceptDialog;
|
||||
class InputEventConfigurationDialog;
|
||||
class Tree;
|
||||
|
||||
class ActionMapEditor : public Control {
|
||||
GDCLASS(ActionMapEditor, Control);
|
||||
|
||||
public:
|
||||
struct ActionInfo {
|
||||
String name;
|
||||
Dictionary action;
|
||||
bool has_initial = false;
|
||||
Dictionary action_initial;
|
||||
|
||||
Ref<Texture2D> icon;
|
||||
bool editable = true;
|
||||
};
|
||||
|
||||
private:
|
||||
enum ItemButton {
|
||||
BUTTON_ADD_EVENT,
|
||||
BUTTON_EDIT_EVENT,
|
||||
BUTTON_REMOVE_ACTION,
|
||||
BUTTON_REMOVE_EVENT,
|
||||
BUTTON_REVERT_ACTION,
|
||||
};
|
||||
|
||||
Vector<ActionInfo> actions_cache;
|
||||
Tree *action_tree = nullptr;
|
||||
|
||||
// Storing which action/event is currently being edited in the InputEventConfigurationDialog.
|
||||
|
||||
Dictionary current_action;
|
||||
String current_action_name;
|
||||
int current_action_event_index = -1;
|
||||
|
||||
// Popups
|
||||
|
||||
InputEventConfigurationDialog *event_config_dialog = nullptr;
|
||||
AcceptDialog *message = nullptr;
|
||||
|
||||
// Filtering and Adding actions
|
||||
|
||||
bool show_builtin_actions = false;
|
||||
CheckButton *show_builtin_actions_checkbutton = nullptr;
|
||||
LineEdit *action_list_search = nullptr;
|
||||
EventListenerLineEdit *action_list_search_by_event = nullptr;
|
||||
Button *clear_all_search = nullptr;
|
||||
|
||||
HBoxContainer *add_hbox = nullptr;
|
||||
LineEdit *add_edit = nullptr;
|
||||
Button *add_button = nullptr;
|
||||
|
||||
void _event_config_confirmed();
|
||||
|
||||
void _add_action_pressed();
|
||||
void _add_edit_text_changed(const String &p_name);
|
||||
String _check_new_action_name(const String &p_name);
|
||||
bool _has_action(const String &p_name) const;
|
||||
void _add_action(const String &p_name);
|
||||
void _action_edited();
|
||||
|
||||
void _tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
void _tree_item_activated();
|
||||
void _search_term_updated(const String &p_search_term);
|
||||
void _search_by_event(const Ref<InputEvent> &p_event);
|
||||
bool _should_display_action(const String &p_name, const Array &p_events) 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 _on_filter_focused();
|
||||
void _on_filter_unfocused();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
LineEdit *get_search_box() const;
|
||||
LineEdit *get_path_box() const;
|
||||
InputEventConfigurationDialog *get_configuration_dialog();
|
||||
|
||||
// Dictionary represents an Action with "events" (Array) and "deadzone" (float) items. Pass with no param to update list from cached action map.
|
||||
void update_action_list(const Vector<ActionInfo> &p_action_infos = Vector<ActionInfo>());
|
||||
void show_message(const String &p_message);
|
||||
|
||||
void set_show_builtin_actions(bool p_show);
|
||||
|
||||
void use_external_search_box(LineEdit *p_searchbox);
|
||||
|
||||
ActionMapEditor();
|
||||
};
|
||||
|
||||
#endif // ACTION_MAP_EDITOR_H
|
||||
1963
engine/editor/animation_bezier_editor.cpp
Normal file
1963
engine/editor/animation_bezier_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
229
engine/editor/animation_bezier_editor.h
Normal file
229
engine/editor/animation_bezier_editor.h
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
/**************************************************************************/
|
||||
/* animation_bezier_editor.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_BEZIER_EDITOR_H
|
||||
#define ANIMATION_BEZIER_EDITOR_H
|
||||
|
||||
#include "animation_track_editor.h"
|
||||
#include "core/templates/hashfuncs.h"
|
||||
|
||||
class ViewPanner;
|
||||
|
||||
class AnimationBezierTrackEdit : public Control {
|
||||
GDCLASS(AnimationBezierTrackEdit, Control);
|
||||
|
||||
enum {
|
||||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_CUT,
|
||||
MENU_KEY_COPY,
|
||||
MENU_KEY_PASTE,
|
||||
MENU_KEY_DELETE,
|
||||
MENU_KEY_SET_HANDLE_FREE,
|
||||
MENU_KEY_SET_HANDLE_LINEAR,
|
||||
MENU_KEY_SET_HANDLE_BALANCED,
|
||||
MENU_KEY_SET_HANDLE_MIRRORED,
|
||||
MENU_KEY_SET_HANDLE_AUTO_BALANCED,
|
||||
MENU_KEY_SET_HANDLE_AUTO_MIRRORED,
|
||||
};
|
||||
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
Node *root = nullptr;
|
||||
Control *play_position = nullptr; //separate control used to draw so updates for only position changed are much faster
|
||||
real_t play_position_pos = 0;
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
int selected_track = 0;
|
||||
|
||||
Vector<Rect2> view_rects;
|
||||
|
||||
Ref<Texture2D> bezier_icon;
|
||||
Ref<Texture2D> bezier_handle_icon;
|
||||
Ref<Texture2D> selected_icon;
|
||||
|
||||
RBMap<int, Rect2> subtracks;
|
||||
|
||||
enum {
|
||||
REMOVE_ICON,
|
||||
LOCK_ICON,
|
||||
SOLO_ICON,
|
||||
VISIBILITY_ICON
|
||||
};
|
||||
|
||||
RBMap<int, RBMap<int, Rect2>> subtrack_icons;
|
||||
HashSet<int> locked_tracks;
|
||||
HashSet<int> hidden_tracks;
|
||||
int solo_track = -1;
|
||||
bool is_filtered = false;
|
||||
|
||||
float track_v_scroll = 0;
|
||||
float track_v_scroll_max = 0;
|
||||
|
||||
float timeline_v_scroll = 0;
|
||||
float timeline_v_zoom = 1;
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
|
||||
void _zoom_changed();
|
||||
|
||||
void _update_locked_tracks_after(int p_track);
|
||||
void _update_hidden_tracks_after(int p_track);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
void _menu_selected(int p_index);
|
||||
|
||||
void _play_position_draw();
|
||||
bool _is_track_displayed(int p_track_index);
|
||||
bool _is_track_curves_displayed(int p_track_index);
|
||||
|
||||
Vector2 insert_at_pos;
|
||||
|
||||
typedef Pair<int, int> IntPair;
|
||||
|
||||
bool moving_selection_attempt = false;
|
||||
float moving_selection_mouse_begin_x = 0.0;
|
||||
IntPair select_single_attempt;
|
||||
bool moving_selection = false;
|
||||
int moving_selection_from_key = 0;
|
||||
int moving_selection_from_track = 0;
|
||||
|
||||
Vector2 moving_selection_offset;
|
||||
|
||||
bool box_selecting_attempt = false;
|
||||
bool box_selecting = false;
|
||||
bool box_selecting_add = false;
|
||||
Vector2 box_selection_from;
|
||||
Vector2 box_selection_to;
|
||||
|
||||
int moving_handle = 0; //0 no move -1 or +1 out, 2 both (drawing only)
|
||||
int moving_handle_key = 0;
|
||||
int moving_handle_track = 0;
|
||||
Vector2 moving_handle_left;
|
||||
Vector2 moving_handle_right;
|
||||
int moving_handle_mode = 0; // value from Animation::HandleMode
|
||||
|
||||
struct PairHasher {
|
||||
static _FORCE_INLINE_ uint32_t hash(const Pair<int, int> &p_value) {
|
||||
int32_t hash = 23;
|
||||
hash = hash * 31 * hash_one_uint64(p_value.first);
|
||||
hash = hash * 31 * hash_one_uint64(p_value.second);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_lefts;
|
||||
HashMap<Pair<int, int>, Vector2, PairHasher> additional_moving_handle_rights;
|
||||
|
||||
void _clear_selection();
|
||||
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
|
||||
void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single);
|
||||
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
|
||||
void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false);
|
||||
|
||||
Vector2 menu_insert_key;
|
||||
|
||||
struct AnimMoveRestore {
|
||||
int track = 0;
|
||||
double time = 0;
|
||||
Variant key;
|
||||
real_t transition = 0;
|
||||
};
|
||||
|
||||
AnimationTrackEditor *editor = nullptr;
|
||||
|
||||
struct EditPoint {
|
||||
Rect2 point_rect;
|
||||
Rect2 in_rect;
|
||||
Rect2 out_rect;
|
||||
int track = 0;
|
||||
int key = 0;
|
||||
};
|
||||
|
||||
Vector<EditPoint> edit_points;
|
||||
|
||||
struct PairCompare {
|
||||
bool operator()(const IntPair &lh, const IntPair &rh) {
|
||||
if (lh.first == rh.first) {
|
||||
return lh.second < rh.second;
|
||||
} else {
|
||||
return lh.first < rh.first;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef RBSet<IntPair, PairCompare> SelectionSet;
|
||||
|
||||
SelectionSet selection;
|
||||
|
||||
Ref<ViewPanner> panner;
|
||||
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
|
||||
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
|
||||
|
||||
void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
|
||||
void _draw_track(int p_track, const Color &p_color);
|
||||
|
||||
float _bezier_h_to_pixel(float p_h);
|
||||
void _zoom_vertically(real_t p_minimum_value, real_t p_maximum_value);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static float get_bezier_key_value(Array p_bezier_key_array);
|
||||
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
Ref<Animation> get_animation() const;
|
||||
|
||||
void set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only);
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
void set_timeline(AnimationTimelineEdit *p_timeline);
|
||||
void set_editor(AnimationTrackEditor *p_editor);
|
||||
void set_root(Node *p_root);
|
||||
void set_filtered(bool p_filtered);
|
||||
void auto_fit_vertically();
|
||||
|
||||
void set_play_position(real_t p_pos);
|
||||
void update_play_position();
|
||||
|
||||
void duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid);
|
||||
void copy_selected_keys(bool p_cut);
|
||||
void paste_keys(real_t p_ofs, bool p_ofs_valid);
|
||||
void delete_selection();
|
||||
|
||||
void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
|
||||
|
||||
AnimationBezierTrackEdit();
|
||||
};
|
||||
|
||||
#endif // ANIMATION_BEZIER_EDITOR_H
|
||||
7717
engine/editor/animation_track_editor.cpp
Normal file
7717
engine/editor/animation_track_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
773
engine/editor/animation_track_editor.h
Normal file
773
engine/editor/animation_track_editor.h
Normal file
|
|
@ -0,0 +1,773 @@
|
|||
/**************************************************************************/
|
||||
/* animation_track_editor.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_TRACK_EDITOR_H
|
||||
#define ANIMATION_TRACK_EDITOR_H
|
||||
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_properties.h"
|
||||
#include "editor/property_selector.h"
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/scroll_bar.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/resources/animation.h"
|
||||
|
||||
class AnimationTrackEditor;
|
||||
class AnimationTrackEdit;
|
||||
class CheckBox;
|
||||
class EditorSpinSlider;
|
||||
class HSlider;
|
||||
class OptionButton;
|
||||
class PanelContainer;
|
||||
class SceneTreeDialog;
|
||||
class SpinBox;
|
||||
class TextureRect;
|
||||
class ViewPanner;
|
||||
|
||||
class AnimationTrackKeyEdit : public Object {
|
||||
GDCLASS(AnimationTrackKeyEdit, Object);
|
||||
|
||||
public:
|
||||
bool setting = false;
|
||||
bool animation_read_only = false;
|
||||
|
||||
Ref<Animation> animation;
|
||||
int track = -1;
|
||||
float key_ofs = 0;
|
||||
Node *root_path = nullptr;
|
||||
|
||||
PropertyInfo hint;
|
||||
NodePath base;
|
||||
bool use_fps = false;
|
||||
AnimationTrackEditor *editor = nullptr;
|
||||
|
||||
bool _hide_script_from_inspector() { return true; }
|
||||
bool _hide_metadata_from_inspector() { return true; }
|
||||
bool _dont_undo_redo() { return true; }
|
||||
|
||||
bool _is_read_only() { return animation_read_only; }
|
||||
|
||||
void notify_change();
|
||||
Node *get_root_path();
|
||||
void set_use_fps(bool p_enable);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _fix_node_path(Variant &value);
|
||||
void _update_obj(const Ref<Animation> &p_anim);
|
||||
void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to);
|
||||
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;
|
||||
};
|
||||
|
||||
class AnimationMultiTrackKeyEdit : public Object {
|
||||
GDCLASS(AnimationMultiTrackKeyEdit, Object);
|
||||
|
||||
public:
|
||||
bool setting = false;
|
||||
bool animation_read_only = false;
|
||||
|
||||
Ref<Animation> animation;
|
||||
|
||||
RBMap<int, List<float>> key_ofs_map;
|
||||
RBMap<int, NodePath> base_map;
|
||||
PropertyInfo hint;
|
||||
|
||||
Node *root_path = nullptr;
|
||||
|
||||
bool use_fps = false;
|
||||
AnimationTrackEditor *editor = nullptr;
|
||||
|
||||
bool _hide_script_from_inspector() { return true; }
|
||||
bool _hide_metadata_from_inspector() { return true; }
|
||||
bool _dont_undo_redo() { return true; }
|
||||
|
||||
bool _is_read_only() { return animation_read_only; }
|
||||
|
||||
void notify_change();
|
||||
Node *get_root_path();
|
||||
void set_use_fps(bool p_enable);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _fix_node_path(Variant &value, NodePath &base);
|
||||
void _update_obj(const Ref<Animation> &p_anim);
|
||||
void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to);
|
||||
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;
|
||||
};
|
||||
|
||||
class AnimationTimelineEdit : public Range {
|
||||
GDCLASS(AnimationTimelineEdit, Range);
|
||||
|
||||
friend class AnimationBezierTrackEdit;
|
||||
friend class AnimationTrackEditor;
|
||||
|
||||
static constexpr float SCROLL_ZOOM_FACTOR_IN = 1.02f; // Zoom factor per mouse scroll in the animation editor when zooming in. The closer to 1.0, the finer the control.
|
||||
static constexpr float SCROLL_ZOOM_FACTOR_OUT = 0.98f; // Zoom factor when zooming out. Similar to SCROLL_ZOOM_FACTOR_IN but less than 1.0.
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
|
||||
AnimationTrackEdit *track_edit = nullptr;
|
||||
int name_limit = 0;
|
||||
Range *zoom = nullptr;
|
||||
Range *h_scroll = nullptr;
|
||||
float play_position_pos = 0.0f;
|
||||
|
||||
HBoxContainer *len_hb = nullptr;
|
||||
EditorSpinSlider *length = nullptr;
|
||||
Button *loop = nullptr;
|
||||
TextureRect *time_icon = nullptr;
|
||||
|
||||
MenuButton *add_track = nullptr;
|
||||
Control *play_position = nullptr; //separate control used to draw so updates for only position changed are much faster
|
||||
HScrollBar *hscroll = nullptr;
|
||||
|
||||
void _zoom_changed(double);
|
||||
void _anim_length_changed(double p_new_len);
|
||||
void _anim_loop_pressed();
|
||||
|
||||
void _play_position_draw();
|
||||
Rect2 hsize_rect;
|
||||
|
||||
bool editing = false;
|
||||
bool use_fps = false;
|
||||
|
||||
Ref<ViewPanner> panner;
|
||||
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
|
||||
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
|
||||
|
||||
bool dragging_timeline = false;
|
||||
bool dragging_hsize = false;
|
||||
float dragging_hsize_from = 0.0f;
|
||||
float dragging_hsize_at = 0.0f;
|
||||
double last_zoom_scale = 1.0;
|
||||
double hscroll_on_zoom_buffer = -1.0;
|
||||
|
||||
Vector2 zoom_scroll_origin;
|
||||
bool zoom_callback_occured = false;
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
void _track_added(int p_track);
|
||||
|
||||
float _get_zoom_scale(double p_zoom_value) const;
|
||||
void _scroll_to_start();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
int get_name_limit() const;
|
||||
int get_buttons_width() const;
|
||||
|
||||
float get_zoom_scale() const;
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
void set_animation(const Ref<Animation> &p_animation, bool p_read_only);
|
||||
void set_track_edit(AnimationTrackEdit *p_track_edit);
|
||||
void set_zoom(Range *p_zoom);
|
||||
Range *get_zoom() const { return zoom; }
|
||||
void auto_fit();
|
||||
|
||||
void set_play_position(float p_pos);
|
||||
float get_play_position() const;
|
||||
void update_play_position();
|
||||
|
||||
void update_values();
|
||||
|
||||
void set_use_fps(bool p_use_fps);
|
||||
bool is_using_fps() const;
|
||||
|
||||
void set_hscroll(HScrollBar *p_hscroll);
|
||||
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
|
||||
AnimationTimelineEdit();
|
||||
};
|
||||
|
||||
class AnimationTrackEdit : public Control {
|
||||
GDCLASS(AnimationTrackEdit, Control);
|
||||
friend class AnimationTimelineEdit;
|
||||
|
||||
enum {
|
||||
MENU_CALL_MODE_CONTINUOUS,
|
||||
MENU_CALL_MODE_DISCRETE,
|
||||
MENU_CALL_MODE_CAPTURE,
|
||||
MENU_INTERPOLATION_NEAREST,
|
||||
MENU_INTERPOLATION_LINEAR,
|
||||
MENU_INTERPOLATION_CUBIC,
|
||||
MENU_INTERPOLATION_LINEAR_ANGLE,
|
||||
MENU_INTERPOLATION_CUBIC_ANGLE,
|
||||
MENU_LOOP_WRAP,
|
||||
MENU_LOOP_CLAMP,
|
||||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_CUT,
|
||||
MENU_KEY_COPY,
|
||||
MENU_KEY_PASTE,
|
||||
MENU_KEY_ADD_RESET,
|
||||
MENU_KEY_DELETE,
|
||||
MENU_USE_BLEND_ENABLED,
|
||||
MENU_USE_BLEND_DISABLED,
|
||||
};
|
||||
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
Popup *path_popup = nullptr;
|
||||
LineEdit *path = nullptr;
|
||||
Node *root = nullptr;
|
||||
Control *play_position = nullptr; //separate control used to draw so updates for only position changed are much faster
|
||||
float play_position_pos = 0.0f;
|
||||
NodePath node_path;
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
int track = 0;
|
||||
|
||||
Rect2 check_rect;
|
||||
Rect2 path_rect;
|
||||
|
||||
Rect2 update_mode_rect;
|
||||
Rect2 interp_mode_rect;
|
||||
Rect2 loop_wrap_rect;
|
||||
Rect2 remove_rect;
|
||||
|
||||
Ref<Texture2D> type_icon;
|
||||
Ref<Texture2D> selected_icon;
|
||||
|
||||
PopupMenu *menu = nullptr;
|
||||
|
||||
bool hovered = false;
|
||||
bool clicking_on_name = false;
|
||||
int hovering_key_idx = -1;
|
||||
|
||||
void _zoom_changed();
|
||||
|
||||
Ref<Texture2D> icon_cache;
|
||||
String path_cache;
|
||||
|
||||
void _menu_selected(int p_index);
|
||||
|
||||
void _path_submitted(const String &p_text);
|
||||
void _play_position_draw();
|
||||
bool _is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const;
|
||||
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
|
||||
|
||||
Ref<Texture2D> _get_key_type_icon() const;
|
||||
|
||||
mutable int dropping_at = 0;
|
||||
float insert_at_pos = 0.0f;
|
||||
bool moving_selection_attempt = false;
|
||||
bool moving_selection_effective = false;
|
||||
float moving_selection_pivot = 0.0f;
|
||||
float moving_selection_mouse_begin_x = 0.0f;
|
||||
int select_single_attempt = -1;
|
||||
bool moving_selection = false;
|
||||
|
||||
bool in_group = false;
|
||||
AnimationTrackEditor *editor = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
public:
|
||||
virtual Variant get_drag_data(const Point2 &p_point) override;
|
||||
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
|
||||
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
|
||||
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
virtual int get_key_height() const;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec);
|
||||
virtual bool is_key_selectable_by_distance() const;
|
||||
virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right);
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
|
||||
virtual void draw_bg(int p_clip_left, int p_clip_right);
|
||||
virtual void draw_fg(int p_clip_left, int p_clip_right);
|
||||
|
||||
//helper
|
||||
void draw_texture_region_clipped(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_region);
|
||||
void draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled = true);
|
||||
|
||||
int get_track() const;
|
||||
Ref<Animation> get_animation() const;
|
||||
AnimationTimelineEdit *get_timeline() const { return timeline; }
|
||||
AnimationTrackEditor *get_editor() const { return editor; }
|
||||
NodePath get_path() const;
|
||||
void set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only);
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
void set_timeline(AnimationTimelineEdit *p_timeline);
|
||||
void set_editor(AnimationTrackEditor *p_editor);
|
||||
void set_root(Node *p_root);
|
||||
|
||||
void set_play_position(float p_pos);
|
||||
void update_play_position();
|
||||
void cancel_drop();
|
||||
|
||||
void set_in_group(bool p_enable);
|
||||
void append_to_selection(const Rect2 &p_box, bool p_deselection);
|
||||
|
||||
AnimationTrackEdit();
|
||||
};
|
||||
|
||||
class AnimationTrackEditPlugin : public RefCounted {
|
||||
GDCLASS(AnimationTrackEditPlugin, RefCounted);
|
||||
|
||||
public:
|
||||
virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage);
|
||||
virtual AnimationTrackEdit *create_audio_track_edit();
|
||||
virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object);
|
||||
};
|
||||
|
||||
class AnimationTrackKeyEdit;
|
||||
class AnimationMultiTrackKeyEdit;
|
||||
class AnimationBezierTrackEdit;
|
||||
|
||||
class AnimationTrackEditGroup : public Control {
|
||||
GDCLASS(AnimationTrackEditGroup, Control);
|
||||
Ref<Texture2D> icon;
|
||||
Vector2 icon_size;
|
||||
String node_name;
|
||||
NodePath node;
|
||||
Node *root = nullptr;
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
|
||||
void _zoom_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
public:
|
||||
void set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node);
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
void set_timeline(AnimationTimelineEdit *p_timeline);
|
||||
void set_root(Node *p_root);
|
||||
|
||||
AnimationTrackEditGroup();
|
||||
};
|
||||
|
||||
class AnimationTrackEditor : public VBoxContainer {
|
||||
GDCLASS(AnimationTrackEditor, VBoxContainer);
|
||||
friend class AnimationTimelineEdit;
|
||||
friend class AnimationBezierTrackEdit;
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
Node *root = nullptr;
|
||||
|
||||
MenuButton *edit = nullptr;
|
||||
|
||||
PanelContainer *main_panel = nullptr;
|
||||
HScrollBar *hscroll = nullptr;
|
||||
ScrollContainer *scroll = nullptr;
|
||||
VBoxContainer *track_vbox = nullptr;
|
||||
AnimationBezierTrackEdit *bezier_edit = nullptr;
|
||||
|
||||
Label *info_message = nullptr;
|
||||
|
||||
AnimationTimelineEdit *timeline = nullptr;
|
||||
HSlider *zoom = nullptr;
|
||||
EditorSpinSlider *step = nullptr;
|
||||
TextureRect *zoom_icon = nullptr;
|
||||
Button *snap = nullptr;
|
||||
Button *bezier_edit_icon = nullptr;
|
||||
OptionButton *snap_mode = nullptr;
|
||||
Button *auto_fit = nullptr;
|
||||
Button *auto_fit_bezier = nullptr;
|
||||
|
||||
Button *imported_anim_warning = nullptr;
|
||||
void _show_imported_anim_warning();
|
||||
|
||||
Button *dummy_player_warning = nullptr;
|
||||
void _show_dummy_player_warning();
|
||||
|
||||
Button *inactive_player_warning = nullptr;
|
||||
void _show_inactive_player_warning();
|
||||
|
||||
void _snap_mode_changed(int p_mode);
|
||||
Vector<AnimationTrackEdit *> track_edits;
|
||||
Vector<AnimationTrackEditGroup *> groups;
|
||||
|
||||
bool animation_changing_awaiting_update = false;
|
||||
void _animation_update(); // Updated by AnimationTrackEditor(this)
|
||||
int _get_track_selected();
|
||||
void _animation_changed();
|
||||
void _update_tracks();
|
||||
void _redraw_tracks();
|
||||
void _redraw_groups();
|
||||
void _check_bezier_exist();
|
||||
|
||||
void _name_limit_changed();
|
||||
void _timeline_changed(float p_new_pos, bool p_timeline_only);
|
||||
void _track_remove_request(int p_track);
|
||||
void _animation_track_remove_request(int p_track, Ref<Animation> p_from_animation);
|
||||
void _track_grab_focus(int p_track);
|
||||
|
||||
void _update_scroll(double);
|
||||
void _update_step(double p_new_step);
|
||||
void _update_length(double p_new_len);
|
||||
void _dropped_track(int p_from_track, int p_to_track);
|
||||
|
||||
void _add_track(int p_type);
|
||||
void _new_track_node_selected(NodePath p_path);
|
||||
void _new_track_property_selected(const String &p_name);
|
||||
|
||||
void _update_step_spinbox();
|
||||
|
||||
PropertySelector *prop_selector = nullptr;
|
||||
PropertySelector *method_selector = nullptr;
|
||||
SceneTreeDialog *pick_track = nullptr;
|
||||
int adding_track_type = 0;
|
||||
NodePath adding_track_path;
|
||||
|
||||
bool keying = false;
|
||||
|
||||
struct InsertData {
|
||||
Animation::TrackType type;
|
||||
NodePath path;
|
||||
int track_idx = 0;
|
||||
float time = FLT_MAX; // Defaults to current timeline position.
|
||||
Variant value;
|
||||
String query;
|
||||
bool advance = false;
|
||||
};
|
||||
|
||||
Label *insert_confirm_text = nullptr;
|
||||
CheckBox *insert_confirm_bezier = nullptr;
|
||||
CheckBox *insert_confirm_reset = nullptr;
|
||||
ConfirmationDialog *insert_confirm = nullptr;
|
||||
bool insert_queue = false;
|
||||
List<InsertData> insert_data;
|
||||
|
||||
void _query_insert(const InsertData &p_id);
|
||||
Ref<Animation> _create_and_get_reset_animation();
|
||||
void _confirm_insert_list();
|
||||
struct TrackIndices {
|
||||
int normal;
|
||||
int reset;
|
||||
|
||||
TrackIndices(const Animation *p_anim = nullptr, const Animation *p_reset_anim = nullptr) {
|
||||
normal = p_anim ? p_anim->get_track_count() : 0;
|
||||
reset = p_reset_anim ? p_reset_anim->get_track_count() : 0;
|
||||
}
|
||||
};
|
||||
TrackIndices _confirm_insert(InsertData p_id, TrackIndices p_next_tracks, bool p_reset_wanted, Ref<Animation> p_reset_anim, bool p_create_beziers);
|
||||
void _insert_track(bool p_reset_wanted, bool p_create_beziers);
|
||||
|
||||
void _root_removed();
|
||||
|
||||
PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val = nullptr);
|
||||
|
||||
Ref<ViewPanner> panner;
|
||||
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
|
||||
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
|
||||
|
||||
void _timeline_value_changed(double);
|
||||
|
||||
float insert_key_from_track_call_ofs = 0.0f;
|
||||
int insert_key_from_track_call_track = 0;
|
||||
void _insert_key_from_track(float p_ofs, int p_track);
|
||||
void _add_method_key(const String &p_method);
|
||||
|
||||
void _fetch_value_track_options(const NodePath &p_path, Animation::UpdateMode *r_update_mode, Animation::InterpolationType *r_interpolation_type, bool *r_loop_wrap);
|
||||
|
||||
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
|
||||
void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos);
|
||||
|
||||
//selection
|
||||
|
||||
struct SelectedKey {
|
||||
int track = 0;
|
||||
int key = 0;
|
||||
bool operator<(const SelectedKey &p_key) const { return track == p_key.track ? key < p_key.key : track < p_key.track; };
|
||||
};
|
||||
|
||||
struct KeyInfo {
|
||||
float pos = 0;
|
||||
};
|
||||
|
||||
RBMap<SelectedKey, KeyInfo> selection;
|
||||
|
||||
bool moving_selection = false;
|
||||
float moving_selection_offset = 0.0f;
|
||||
void _move_selection_begin();
|
||||
void _move_selection(float p_offset);
|
||||
void _move_selection_commit();
|
||||
void _move_selection_cancel();
|
||||
|
||||
AnimationTrackKeyEdit *key_edit = nullptr;
|
||||
AnimationMultiTrackKeyEdit *multi_key_edit = nullptr;
|
||||
void _update_key_edit();
|
||||
void _clear_key_edit();
|
||||
|
||||
Control *box_selection = nullptr;
|
||||
void _box_selection_draw();
|
||||
bool box_selecting = false;
|
||||
Vector2 box_selecting_from;
|
||||
Rect2 box_select_rect;
|
||||
void _scroll_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
Vector<Ref<AnimationTrackEditPlugin>> track_edit_plugins;
|
||||
|
||||
void _toggle_bezier_edit();
|
||||
void _cancel_bezier_edit();
|
||||
void _bezier_edit(int p_for_track);
|
||||
void _bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode = Animation::HANDLE_SET_MODE_NONE);
|
||||
|
||||
////////////// edit menu stuff
|
||||
|
||||
ConfirmationDialog *bake_dialog = nullptr;
|
||||
CheckBox *bake_trs = nullptr;
|
||||
CheckBox *bake_blendshape = nullptr;
|
||||
CheckBox *bake_value = nullptr;
|
||||
SpinBox *bake_fps = nullptr;
|
||||
|
||||
ConfirmationDialog *optimize_dialog = nullptr;
|
||||
SpinBox *optimize_velocity_error = nullptr;
|
||||
SpinBox *optimize_angular_error = nullptr;
|
||||
SpinBox *optimize_precision_error = nullptr;
|
||||
|
||||
ConfirmationDialog *cleanup_dialog = nullptr;
|
||||
CheckBox *cleanup_keys_with_trimming_head = nullptr;
|
||||
CheckBox *cleanup_keys_with_trimming_end = nullptr;
|
||||
CheckBox *cleanup_keys = nullptr;
|
||||
CheckBox *cleanup_tracks = nullptr;
|
||||
CheckBox *cleanup_all = nullptr;
|
||||
|
||||
ConfirmationDialog *scale_dialog = nullptr;
|
||||
SpinBox *scale = nullptr;
|
||||
|
||||
ConfirmationDialog *ease_dialog = nullptr;
|
||||
OptionButton *transition_selection = nullptr;
|
||||
OptionButton *ease_selection = nullptr;
|
||||
SpinBox *ease_fps = nullptr;
|
||||
|
||||
void _select_all_tracks_for_copy();
|
||||
|
||||
void _edit_menu_about_to_popup();
|
||||
void _edit_menu_pressed(int p_option);
|
||||
int last_menu_track_opt = 0;
|
||||
|
||||
void _cleanup_animation(Ref<Animation> p_animation);
|
||||
|
||||
void _anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track);
|
||||
|
||||
void _anim_copy_keys(bool p_cut);
|
||||
|
||||
bool _is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type);
|
||||
|
||||
void _anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track);
|
||||
|
||||
void _view_group_toggle();
|
||||
Button *view_group = nullptr;
|
||||
Button *selected_filter = nullptr;
|
||||
|
||||
void _auto_fit();
|
||||
void _auto_fit_bezier();
|
||||
|
||||
void _selection_changed();
|
||||
|
||||
ConfirmationDialog *track_copy_dialog = nullptr;
|
||||
Tree *track_copy_select = nullptr;
|
||||
|
||||
struct TrackClipboard {
|
||||
NodePath full_path;
|
||||
NodePath base_path;
|
||||
Animation::TrackType track_type = Animation::TYPE_ANIMATION;
|
||||
Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC_ANGLE;
|
||||
Animation::UpdateMode update_mode = Animation::UPDATE_CAPTURE;
|
||||
Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
|
||||
bool loop_wrap = false;
|
||||
bool enabled = false;
|
||||
bool use_blend = false;
|
||||
|
||||
struct Key {
|
||||
float time = 0;
|
||||
float transition = 0;
|
||||
Variant value;
|
||||
};
|
||||
Vector<Key> keys;
|
||||
};
|
||||
|
||||
struct KeyClipboard {
|
||||
int top_track;
|
||||
|
||||
struct Key {
|
||||
Animation::TrackType track_type;
|
||||
int track;
|
||||
float time = 0;
|
||||
float transition = 0;
|
||||
Variant value;
|
||||
};
|
||||
Vector<Key> keys;
|
||||
};
|
||||
|
||||
Vector<TrackClipboard> track_clipboard;
|
||||
KeyClipboard key_clipboard;
|
||||
|
||||
void _set_key_clipboard(int p_top_track, float p_top_time, RBMap<SelectedKey, KeyInfo> &p_keymap);
|
||||
void _insert_animation_key(NodePath p_path, const Variant &p_value);
|
||||
|
||||
void _pick_track_filter_text_changed(const String &p_newtext);
|
||||
void _pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates);
|
||||
void _pick_track_filter_input(const Ref<InputEvent> &p_ie);
|
||||
|
||||
double snap_unit;
|
||||
void _update_snap_unit();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
// Public for use with callable_mp.
|
||||
void _clear_selection(bool p_update = false);
|
||||
void _key_selected(int p_key, bool p_single, int p_track);
|
||||
void _key_deselected(int p_key, int p_track);
|
||||
|
||||
enum {
|
||||
EDIT_COPY_TRACKS,
|
||||
EDIT_COPY_TRACKS_CONFIRM,
|
||||
EDIT_PASTE_TRACKS,
|
||||
EDIT_CUT_KEYS,
|
||||
EDIT_COPY_KEYS,
|
||||
EDIT_PASTE_KEYS,
|
||||
EDIT_SCALE_SELECTION,
|
||||
EDIT_SCALE_FROM_CURSOR,
|
||||
EDIT_SCALE_CONFIRM,
|
||||
EDIT_SET_START_OFFSET,
|
||||
EDIT_SET_END_OFFSET,
|
||||
EDIT_EASE_SELECTION,
|
||||
EDIT_EASE_CONFIRM,
|
||||
EDIT_DUPLICATE_SELECTED_KEYS,
|
||||
EDIT_DUPLICATE_SELECTION,
|
||||
EDIT_DUPLICATE_TRANSPOSED,
|
||||
EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR,
|
||||
EDIT_MOVE_LAST_SELECTED_KEY_TO_CURSOR,
|
||||
EDIT_ADD_RESET_KEY,
|
||||
EDIT_DELETE_SELECTION,
|
||||
EDIT_GOTO_NEXT_STEP,
|
||||
EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY, // Next step without updating animation.
|
||||
EDIT_GOTO_PREV_STEP,
|
||||
EDIT_APPLY_RESET,
|
||||
EDIT_BAKE_ANIMATION,
|
||||
EDIT_BAKE_ANIMATION_CONFIRM,
|
||||
EDIT_OPTIMIZE_ANIMATION,
|
||||
EDIT_OPTIMIZE_ANIMATION_CONFIRM,
|
||||
EDIT_CLEAN_UP_ANIMATION,
|
||||
EDIT_CLEAN_UP_ANIMATION_CONFIRM
|
||||
};
|
||||
|
||||
void add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
|
||||
void remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
|
||||
|
||||
void set_animation(const Ref<Animation> &p_anim, bool p_read_only);
|
||||
Ref<Animation> get_current_animation() const;
|
||||
void set_root(Node *p_root);
|
||||
Node *get_root() const;
|
||||
void update_keying();
|
||||
bool has_keying() const;
|
||||
|
||||
Dictionary get_state() const;
|
||||
void set_state(const Dictionary &p_state);
|
||||
|
||||
void cleanup();
|
||||
|
||||
void set_anim_pos(float p_pos);
|
||||
void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
|
||||
void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
|
||||
void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value);
|
||||
bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type);
|
||||
void make_insert_queue();
|
||||
void commit_insert_queue();
|
||||
|
||||
void show_select_node_warning(bool p_show);
|
||||
void show_dummy_player_warning(bool p_show);
|
||||
void show_inactive_player_warning(bool p_show);
|
||||
|
||||
bool is_key_selected(int p_track, int p_key) const;
|
||||
bool is_selection_active() const;
|
||||
bool is_key_clipboard_active() const;
|
||||
bool is_moving_selection() const;
|
||||
bool is_snap_enabled() const;
|
||||
bool can_add_reset_key() const;
|
||||
float get_moving_selection_offset() const;
|
||||
float snap_time(float p_value, bool p_relative = false);
|
||||
bool is_grouping_tracks();
|
||||
|
||||
/** If `p_from_mouse_event` is `true`, handle Shift key presses for precise snapping. */
|
||||
void goto_prev_step(bool p_from_mouse_event);
|
||||
|
||||
/** If `p_from_mouse_event` is `true`, handle Shift key presses for precise snapping. */
|
||||
void goto_next_step(bool p_from_mouse_event, bool p_timeline_only = false);
|
||||
|
||||
MenuButton *get_edit_menu();
|
||||
AnimationTrackEditor();
|
||||
~AnimationTrackEditor();
|
||||
};
|
||||
|
||||
// AnimationTrackKeyEditEditorPlugin
|
||||
|
||||
class AnimationTrackKeyEditEditor : public EditorProperty {
|
||||
GDCLASS(AnimationTrackKeyEditEditor, EditorProperty);
|
||||
|
||||
Ref<Animation> animation;
|
||||
int track = -1;
|
||||
real_t key_ofs = 0.0;
|
||||
bool use_fps = false;
|
||||
|
||||
EditorSpinSlider *spinner = nullptr;
|
||||
|
||||
struct KeyDataCache {
|
||||
real_t time = 0.0;
|
||||
float transition = 0.0;
|
||||
Variant value;
|
||||
} key_data_cache;
|
||||
|
||||
void _time_edit_entered();
|
||||
void _time_edit_exited();
|
||||
|
||||
public:
|
||||
AnimationTrackKeyEditEditor(Ref<Animation> p_animation, int p_track, real_t p_key_ofs, bool p_use_fps);
|
||||
~AnimationTrackKeyEditEditor();
|
||||
};
|
||||
|
||||
#endif // ANIMATION_TRACK_EDITOR_H
|
||||
1385
engine/editor/animation_track_editor_plugins.cpp
Normal file
1385
engine/editor/animation_track_editor_plugins.cpp
Normal file
File diff suppressed because it is too large
Load diff
177
engine/editor/animation_track_editor_plugins.h
Normal file
177
engine/editor/animation_track_editor_plugins.h
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/**************************************************************************/
|
||||
/* animation_track_editor_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_TRACK_EDITOR_PLUGINS_H
|
||||
#define ANIMATION_TRACK_EDITOR_PLUGINS_H
|
||||
|
||||
#include "editor/animation_track_editor.h"
|
||||
|
||||
class AnimationTrackEditBool : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditBool, AnimationTrackEdit);
|
||||
Ref<Texture2D> icon_checked;
|
||||
Ref<Texture2D> icon_unchecked;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditColor : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditColor, AnimationTrackEdit);
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditAudio : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditAudio, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
void _preview_changed(ObjectID p_which);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
|
||||
AnimationTrackEditAudio();
|
||||
};
|
||||
|
||||
class AnimationTrackEditSpriteFrame : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditSpriteFrame, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
bool is_coords = false;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
void set_as_coords();
|
||||
|
||||
AnimationTrackEditSpriteFrame() {}
|
||||
};
|
||||
|
||||
class AnimationTrackEditSubAnim : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditSubAnim, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
};
|
||||
|
||||
class AnimationTrackEditTypeAudio : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditTypeAudio, AnimationTrackEdit);
|
||||
|
||||
void _preview_changed(ObjectID p_which);
|
||||
|
||||
bool len_resizing = false;
|
||||
bool len_resizing_start = false;
|
||||
int len_resizing_index = 0;
|
||||
float len_resizing_from_px = 0.0f;
|
||||
float len_resizing_rel = 0.0f;
|
||||
bool over_drag_position = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
|
||||
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
|
||||
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
|
||||
|
||||
AnimationTrackEditTypeAudio();
|
||||
};
|
||||
|
||||
class AnimationTrackEditTypeAnimation : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditTypeAnimation, AnimationTrackEdit);
|
||||
|
||||
ObjectID id;
|
||||
|
||||
public:
|
||||
virtual int get_key_height() const override;
|
||||
virtual Rect2 get_key_rect(int p_index, float p_pixels_sec) override;
|
||||
virtual bool is_key_selectable_by_distance() const override;
|
||||
virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) override;
|
||||
|
||||
void set_node(Object *p_object);
|
||||
AnimationTrackEditTypeAnimation();
|
||||
};
|
||||
|
||||
class AnimationTrackEditVolumeDB : public AnimationTrackEdit {
|
||||
GDCLASS(AnimationTrackEditVolumeDB, AnimationTrackEdit);
|
||||
|
||||
public:
|
||||
virtual void draw_bg(int p_clip_left, int p_clip_right) override;
|
||||
virtual void draw_fg(int p_clip_left, int p_clip_right) override;
|
||||
virtual int get_key_height() const override;
|
||||
virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) override;
|
||||
};
|
||||
|
||||
class AnimationTrackEditDefaultPlugin : public AnimationTrackEditPlugin {
|
||||
GDCLASS(AnimationTrackEditDefaultPlugin, AnimationTrackEditPlugin);
|
||||
|
||||
public:
|
||||
virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) override;
|
||||
virtual AnimationTrackEdit *create_audio_track_edit() override;
|
||||
virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object) override;
|
||||
};
|
||||
|
||||
#endif // ANIMATION_TRACK_EDITOR_PLUGINS_H
|
||||
254
engine/editor/audio_stream_preview.cpp
Normal file
254
engine/editor/audio_stream_preview.cpp
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/**************************************************************************/
|
||||
/* audio_stream_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 "audio_stream_preview.h"
|
||||
|
||||
/////////////////////
|
||||
|
||||
float AudioStreamPreview::get_length() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
float AudioStreamPreview::get_max(float p_time, float p_time_next) const {
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int max = preview.size() / 2;
|
||||
if (max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int time_from = p_time / length * max;
|
||||
int time_to = p_time_next / length * max;
|
||||
time_from = CLAMP(time_from, 0, max - 1);
|
||||
time_to = CLAMP(time_to, 0, max - 1);
|
||||
|
||||
if (time_to <= time_from) {
|
||||
time_to = time_from + 1;
|
||||
}
|
||||
|
||||
uint8_t vmax = 0;
|
||||
|
||||
for (int i = time_from; i < time_to; i++) {
|
||||
uint8_t v = preview[i * 2 + 1];
|
||||
if (i == 0 || v > vmax) {
|
||||
vmax = v;
|
||||
}
|
||||
}
|
||||
|
||||
return (vmax / 255.0) * 2.0 - 1.0;
|
||||
}
|
||||
|
||||
float AudioStreamPreview::get_min(float p_time, float p_time_next) const {
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int max = preview.size() / 2;
|
||||
if (max == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int time_from = p_time / length * max;
|
||||
int time_to = p_time_next / length * max;
|
||||
time_from = CLAMP(time_from, 0, max - 1);
|
||||
time_to = CLAMP(time_to, 0, max - 1);
|
||||
|
||||
if (time_to <= time_from) {
|
||||
time_to = time_from + 1;
|
||||
}
|
||||
|
||||
uint8_t vmin = 255;
|
||||
|
||||
for (int i = time_from; i < time_to; i++) {
|
||||
uint8_t v = preview[i * 2];
|
||||
if (i == 0 || v < vmin) {
|
||||
vmin = v;
|
||||
}
|
||||
}
|
||||
|
||||
return (vmin / 255.0) * 2.0 - 1.0;
|
||||
}
|
||||
|
||||
AudioStreamPreview::AudioStreamPreview() {
|
||||
length = 0;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
void AudioStreamPreviewGenerator::_update_emit(ObjectID p_id) {
|
||||
emit_signal(SNAME("preview_updated"), p_id);
|
||||
}
|
||||
|
||||
void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) {
|
||||
Preview *preview = static_cast<Preview *>(p_preview);
|
||||
|
||||
float muxbuff_chunk_s = 0.25;
|
||||
|
||||
int mixbuff_chunk_frames = AudioServer::get_singleton()->get_mix_rate() * muxbuff_chunk_s;
|
||||
|
||||
Vector<AudioFrame> mix_chunk;
|
||||
mix_chunk.resize(mixbuff_chunk_frames);
|
||||
|
||||
int frames_total = AudioServer::get_singleton()->get_mix_rate() * preview->preview->length;
|
||||
int frames_todo = frames_total;
|
||||
|
||||
preview->playback->start();
|
||||
|
||||
while (frames_todo) {
|
||||
int ofs_write = uint64_t(frames_total - frames_todo) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
|
||||
int to_read = MIN(frames_todo, mixbuff_chunk_frames);
|
||||
int to_write = uint64_t(to_read) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total);
|
||||
to_write = MIN(to_write, (preview->preview->preview.size() / 2) - ofs_write);
|
||||
|
||||
preview->playback->mix(mix_chunk.ptrw(), 1.0, to_read);
|
||||
|
||||
for (int i = 0; i < to_write; i++) {
|
||||
float max = -1000;
|
||||
float min = 1000;
|
||||
int from = uint64_t(i) * to_read / to_write;
|
||||
int to = (uint64_t(i) + 1) * to_read / to_write;
|
||||
to = MIN(to, to_read);
|
||||
from = MIN(from, to_read - 1);
|
||||
if (to == from) {
|
||||
to = from + 1;
|
||||
}
|
||||
|
||||
for (int j = from; j < to; j++) {
|
||||
max = MAX(max, mix_chunk[j].left);
|
||||
max = MAX(max, mix_chunk[j].right);
|
||||
|
||||
min = MIN(min, mix_chunk[j].left);
|
||||
min = MIN(min, mix_chunk[j].right);
|
||||
}
|
||||
|
||||
uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255);
|
||||
uint8_t pto = CLAMP((max * 0.5 + 0.5) * 255, 0, 255);
|
||||
|
||||
preview->preview->preview.write[(ofs_write + i) * 2 + 0] = pfrom;
|
||||
preview->preview->preview.write[(ofs_write + i) * 2 + 1] = pto;
|
||||
}
|
||||
|
||||
frames_todo -= to_read;
|
||||
callable_mp(singleton, &AudioStreamPreviewGenerator::_update_emit).call_deferred(preview->id);
|
||||
}
|
||||
|
||||
preview->preview->version++;
|
||||
|
||||
preview->playback->stop();
|
||||
|
||||
preview->generating.clear();
|
||||
}
|
||||
|
||||
Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref<AudioStream> &p_stream) {
|
||||
ERR_FAIL_COND_V(p_stream.is_null(), Ref<AudioStreamPreview>());
|
||||
|
||||
if (previews.has(p_stream->get_instance_id())) {
|
||||
return previews[p_stream->get_instance_id()].preview;
|
||||
}
|
||||
|
||||
//no preview exists
|
||||
|
||||
previews[p_stream->get_instance_id()] = Preview();
|
||||
|
||||
Preview *preview = &previews[p_stream->get_instance_id()];
|
||||
preview->base_stream = p_stream;
|
||||
preview->playback = preview->base_stream->instantiate_playback();
|
||||
preview->generating.set();
|
||||
preview->id = p_stream->get_instance_id();
|
||||
|
||||
float len_s = preview->base_stream->get_length();
|
||||
if (len_s == 0) {
|
||||
len_s = 60 * 5; //five minutes
|
||||
}
|
||||
|
||||
int frames = AudioServer::get_singleton()->get_mix_rate() * len_s;
|
||||
|
||||
Vector<uint8_t> maxmin;
|
||||
int pw = frames / 20;
|
||||
maxmin.resize(pw * 2);
|
||||
{
|
||||
uint8_t *ptr = maxmin.ptrw();
|
||||
for (int i = 0; i < pw * 2; i++) {
|
||||
ptr[i] = 127;
|
||||
}
|
||||
}
|
||||
|
||||
preview->preview.instantiate();
|
||||
preview->preview->preview = maxmin;
|
||||
preview->preview->length = len_s;
|
||||
|
||||
if (preview->playback.is_valid()) {
|
||||
preview->thread = memnew(Thread);
|
||||
preview->thread->set_name("AudioStreamPreviewGenerator");
|
||||
preview->thread->start(_preview_thread, preview);
|
||||
}
|
||||
|
||||
return preview->preview;
|
||||
}
|
||||
|
||||
void AudioStreamPreviewGenerator::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("generate_preview", "stream"), &AudioStreamPreviewGenerator::generate_preview);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("preview_updated", PropertyInfo(Variant::INT, "obj_id")));
|
||||
}
|
||||
|
||||
AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = nullptr;
|
||||
|
||||
void AudioStreamPreviewGenerator::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_PROCESS: {
|
||||
List<ObjectID> to_erase;
|
||||
for (KeyValue<ObjectID, Preview> &E : previews) {
|
||||
if (!E.value.generating.is_set()) {
|
||||
if (E.value.thread) {
|
||||
E.value.thread->wait_to_finish();
|
||||
memdelete(E.value.thread);
|
||||
E.value.thread = nullptr;
|
||||
}
|
||||
if (!ObjectDB::get_instance(E.key)) { //no longer in use, get rid of preview
|
||||
to_erase.push_back(E.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (to_erase.front()) {
|
||||
previews.erase(to_erase.front()->get());
|
||||
to_erase.pop_front();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
AudioStreamPreviewGenerator::AudioStreamPreviewGenerator() {
|
||||
singleton = this;
|
||||
set_process(true);
|
||||
}
|
||||
108
engine/editor/audio_stream_preview.h
Normal file
108
engine/editor/audio_stream_preview.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**************************************************************************/
|
||||
/* audio_stream_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef AUDIO_STREAM_PREVIEW_H
|
||||
#define AUDIO_STREAM_PREVIEW_H
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
|
||||
class AudioStreamPreview : public RefCounted {
|
||||
GDCLASS(AudioStreamPreview, RefCounted);
|
||||
friend class AudioStream;
|
||||
Vector<uint8_t> preview;
|
||||
float length;
|
||||
|
||||
friend class AudioStreamPreviewGenerator;
|
||||
uint64_t version = 1;
|
||||
|
||||
public:
|
||||
uint64_t get_version() const { return version; }
|
||||
float get_length() const;
|
||||
float get_max(float p_time, float p_time_next) const;
|
||||
float get_min(float p_time, float p_time_next) const;
|
||||
|
||||
AudioStreamPreview();
|
||||
};
|
||||
|
||||
class AudioStreamPreviewGenerator : public Node {
|
||||
GDCLASS(AudioStreamPreviewGenerator, Node);
|
||||
|
||||
static AudioStreamPreviewGenerator *singleton;
|
||||
|
||||
struct Preview {
|
||||
Ref<AudioStreamPreview> preview;
|
||||
Ref<AudioStream> base_stream;
|
||||
Ref<AudioStreamPlayback> playback;
|
||||
SafeFlag generating;
|
||||
ObjectID id;
|
||||
Thread *thread = nullptr;
|
||||
|
||||
// Needed for the bookkeeping of the Map
|
||||
void operator=(const Preview &p_rhs) {
|
||||
preview = p_rhs.preview;
|
||||
base_stream = p_rhs.base_stream;
|
||||
playback = p_rhs.playback;
|
||||
generating.set_to(generating.is_set());
|
||||
id = p_rhs.id;
|
||||
thread = p_rhs.thread;
|
||||
}
|
||||
Preview(const Preview &p_rhs) {
|
||||
preview = p_rhs.preview;
|
||||
base_stream = p_rhs.base_stream;
|
||||
playback = p_rhs.playback;
|
||||
generating.set_to(generating.is_set());
|
||||
id = p_rhs.id;
|
||||
thread = p_rhs.thread;
|
||||
}
|
||||
Preview() {}
|
||||
};
|
||||
|
||||
HashMap<ObjectID, Preview> previews;
|
||||
|
||||
static void _preview_thread(void *p_preview);
|
||||
|
||||
void _update_emit(ObjectID p_id);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static AudioStreamPreviewGenerator *get_singleton() { return singleton; }
|
||||
|
||||
Ref<AudioStreamPreview> generate_preview(const Ref<AudioStream> &p_stream);
|
||||
|
||||
AudioStreamPreviewGenerator();
|
||||
};
|
||||
|
||||
#endif // AUDIO_STREAM_PREVIEW_H
|
||||
1869
engine/editor/code_editor.cpp
Normal file
1869
engine/editor/code_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
294
engine/editor/code_editor.h
Normal file
294
engine/editor/code_editor.h
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/**************************************************************************/
|
||||
/* code_editor.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CODE_EDITOR_H
|
||||
#define CODE_EDITOR_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/code_edit.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/main/timer.h"
|
||||
|
||||
class MenuButton;
|
||||
|
||||
class GotoLineDialog : public ConfirmationDialog {
|
||||
GDCLASS(GotoLineDialog, ConfirmationDialog);
|
||||
|
||||
Label *line_label = nullptr;
|
||||
LineEdit *line = nullptr;
|
||||
|
||||
CodeEdit *text_editor = nullptr;
|
||||
|
||||
virtual void ok_pressed() override;
|
||||
|
||||
public:
|
||||
void popup_find_line(CodeEdit *p_edit);
|
||||
int get_line() const;
|
||||
|
||||
GotoLineDialog();
|
||||
};
|
||||
|
||||
class CodeTextEditor;
|
||||
|
||||
class FindReplaceBar : public HBoxContainer {
|
||||
GDCLASS(FindReplaceBar, HBoxContainer);
|
||||
|
||||
enum SearchMode {
|
||||
SEARCH_CURRENT,
|
||||
SEARCH_NEXT,
|
||||
SEARCH_PREV,
|
||||
};
|
||||
|
||||
LineEdit *search_text = nullptr;
|
||||
Label *matches_label = nullptr;
|
||||
Button *find_prev = nullptr;
|
||||
Button *find_next = nullptr;
|
||||
CheckBox *case_sensitive = nullptr;
|
||||
CheckBox *whole_words = nullptr;
|
||||
TextureButton *hide_button = nullptr;
|
||||
|
||||
LineEdit *replace_text = nullptr;
|
||||
Button *replace = nullptr;
|
||||
Button *replace_all = nullptr;
|
||||
CheckBox *selection_only = nullptr;
|
||||
|
||||
VBoxContainer *vbc_lineedit = nullptr;
|
||||
HBoxContainer *hbc_button_replace = nullptr;
|
||||
HBoxContainer *hbc_option_replace = nullptr;
|
||||
|
||||
CodeTextEditor *base_text_editor = nullptr;
|
||||
CodeEdit *text_editor = nullptr;
|
||||
|
||||
uint32_t flags = 0;
|
||||
|
||||
int result_line = 0;
|
||||
int result_col = 0;
|
||||
int results_count = -1;
|
||||
int results_count_to_current = -1;
|
||||
|
||||
bool replace_all_mode = false;
|
||||
bool preserve_cursor = false;
|
||||
|
||||
void _get_search_from(int &r_line, int &r_col, SearchMode p_search_mode);
|
||||
void _update_results_count();
|
||||
void _update_matches_display();
|
||||
|
||||
void _show_search(bool p_with_replace, bool p_show_only);
|
||||
void _hide_bar(bool p_force_focus = false);
|
||||
|
||||
void _editor_text_changed();
|
||||
void _search_options_changed(bool p_pressed);
|
||||
void _search_text_changed(const String &p_text);
|
||||
void _search_text_submitted(const String &p_text);
|
||||
void _replace_text_submitted(const String &p_text);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void unhandled_input(const Ref<InputEvent> &p_event) override;
|
||||
void _focus_lost();
|
||||
|
||||
void _update_flags(bool p_direction_backwards);
|
||||
|
||||
bool _search(uint32_t p_flags, int p_from_line, int p_from_col);
|
||||
|
||||
void _replace();
|
||||
void _replace_all();
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
String get_search_text() const;
|
||||
String get_replace_text() const;
|
||||
|
||||
bool is_case_sensitive() const;
|
||||
bool is_whole_words() const;
|
||||
bool is_selection_only() const;
|
||||
void set_error(const String &p_label);
|
||||
|
||||
void set_text_edit(CodeTextEditor *p_text_editor);
|
||||
|
||||
void popup_search(bool p_show_only = false);
|
||||
void popup_replace();
|
||||
|
||||
bool search_current();
|
||||
bool search_prev();
|
||||
bool search_next();
|
||||
|
||||
bool needs_to_count_results = true;
|
||||
bool line_col_changed_for_result = false;
|
||||
|
||||
FindReplaceBar();
|
||||
};
|
||||
|
||||
typedef void (*CodeTextEditorCodeCompleteFunc)(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced);
|
||||
|
||||
class CodeTextEditor : public VBoxContainer {
|
||||
GDCLASS(CodeTextEditor, VBoxContainer);
|
||||
|
||||
CodeEdit *text_editor = nullptr;
|
||||
FindReplaceBar *find_replace_bar = nullptr;
|
||||
HBoxContainer *status_bar = nullptr;
|
||||
|
||||
Button *toggle_scripts_button = nullptr;
|
||||
Button *error_button = nullptr;
|
||||
Button *warning_button = nullptr;
|
||||
|
||||
MenuButton *zoom_button = nullptr;
|
||||
Label *line_and_col_txt = nullptr;
|
||||
Label *indentation_txt = nullptr;
|
||||
|
||||
Label *info = nullptr;
|
||||
Timer *idle = nullptr;
|
||||
bool code_complete_enabled = true;
|
||||
Timer *code_complete_timer = nullptr;
|
||||
int code_complete_timer_line = 0;
|
||||
|
||||
float zoom_factor = 1.0f;
|
||||
|
||||
Label *error = nullptr;
|
||||
int error_line;
|
||||
int error_column;
|
||||
|
||||
Dictionary previous_state;
|
||||
|
||||
void _update_text_editor_theme();
|
||||
void _update_font_ligatures();
|
||||
void _complete_request();
|
||||
Ref<Texture2D> _get_completion_icon(const ScriptLanguage::CodeCompletionOption &p_option);
|
||||
|
||||
virtual void input(const Ref<InputEvent> &event) override;
|
||||
void _text_editor_gui_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
Color completion_font_color;
|
||||
Color completion_string_color;
|
||||
Color completion_string_name_color;
|
||||
Color completion_node_path_color;
|
||||
Color completion_comment_color;
|
||||
Color completion_doc_comment_color;
|
||||
CodeTextEditorCodeCompleteFunc code_complete_func;
|
||||
void *code_complete_ud = nullptr;
|
||||
|
||||
void _zoom_in();
|
||||
void _zoom_out();
|
||||
void _zoom_to(float p_zoom_factor);
|
||||
|
||||
void _error_button_pressed();
|
||||
void _warning_button_pressed();
|
||||
void _set_show_errors_panel(bool p_show);
|
||||
void _set_show_warnings_panel(bool p_show);
|
||||
void _error_pressed(const Ref<InputEvent> &p_event);
|
||||
|
||||
void _zoom_popup_id_pressed(int p_idx);
|
||||
|
||||
void _toggle_scripts_pressed();
|
||||
|
||||
protected:
|
||||
virtual void _load_theme_settings() {}
|
||||
virtual void _validate_script() {}
|
||||
virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {}
|
||||
|
||||
void _text_changed_idle_timeout();
|
||||
void _code_complete_timer_timeout();
|
||||
void _text_changed();
|
||||
void _line_col_changed();
|
||||
void _notification(int);
|
||||
static void _bind_methods();
|
||||
|
||||
bool is_warnings_panel_opened = false;
|
||||
bool is_errors_panel_opened = false;
|
||||
|
||||
public:
|
||||
void trim_trailing_whitespace();
|
||||
void trim_final_newlines();
|
||||
void insert_final_newline();
|
||||
|
||||
enum CaseStyle {
|
||||
UPPER,
|
||||
LOWER,
|
||||
CAPITALIZE,
|
||||
};
|
||||
void convert_case(CaseStyle p_case);
|
||||
|
||||
void set_indent_using_spaces(bool p_use_spaces);
|
||||
|
||||
/// Toggle inline comment on currently selected lines, or on current line if nothing is selected,
|
||||
/// by adding or removing comment delimiter
|
||||
void toggle_inline_comment(const String &delimiter);
|
||||
|
||||
void goto_line(int p_line);
|
||||
void goto_line_selection(int p_line, int p_begin, int p_end);
|
||||
void goto_line_centered(int p_line);
|
||||
void set_executing_line(int p_line);
|
||||
void clear_executing_line();
|
||||
|
||||
Variant get_edit_state();
|
||||
void set_edit_state(const Variant &p_state);
|
||||
Variant get_navigation_state();
|
||||
Variant get_previous_state();
|
||||
void store_previous_state();
|
||||
|
||||
void set_error_count(int p_error_count);
|
||||
void set_warning_count(int p_warning_count);
|
||||
|
||||
void update_editor_settings();
|
||||
void set_error(const String &p_error);
|
||||
void set_error_pos(int p_line, int p_column);
|
||||
Point2i get_error_pos() const;
|
||||
void update_line_and_column() { _line_col_changed(); }
|
||||
CodeEdit *get_text_editor() { return text_editor; }
|
||||
FindReplaceBar *get_find_replace_bar() { return find_replace_bar; }
|
||||
void set_find_replace_bar(FindReplaceBar *p_bar);
|
||||
void remove_find_replace_bar();
|
||||
virtual void apply_code() {}
|
||||
virtual void goto_error();
|
||||
|
||||
void toggle_bookmark();
|
||||
void goto_next_bookmark();
|
||||
void goto_prev_bookmark();
|
||||
void remove_all_bookmarks();
|
||||
|
||||
void set_zoom_factor(float p_zoom_factor);
|
||||
float get_zoom_factor();
|
||||
|
||||
void set_code_complete_func(CodeTextEditorCodeCompleteFunc p_code_complete_func, void *p_ud);
|
||||
|
||||
void validate_script();
|
||||
|
||||
void show_toggle_scripts_button();
|
||||
void update_toggle_scripts_button();
|
||||
|
||||
CodeTextEditor();
|
||||
};
|
||||
|
||||
#endif // CODE_EDITOR_H
|
||||
1652
engine/editor/connections_dialog.cpp
Normal file
1652
engine/editor/connections_dialog.cpp
Normal file
File diff suppressed because it is too large
Load diff
275
engine/editor/connections_dialog.h
Normal file
275
engine/editor/connections_dialog.h
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/**************************************************************************/
|
||||
/* connections_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CONNECTIONS_DIALOG_H
|
||||
#define CONNECTIONS_DIALOG_H
|
||||
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class Button;
|
||||
class CheckBox;
|
||||
class ConnectDialogBinds;
|
||||
class EditorInspector;
|
||||
class Label;
|
||||
class LineEdit;
|
||||
class OptionButton;
|
||||
class PopupMenu;
|
||||
class SceneTreeEditor;
|
||||
class SpinBox;
|
||||
|
||||
class ConnectDialog : public ConfirmationDialog {
|
||||
GDCLASS(ConnectDialog, ConfirmationDialog);
|
||||
|
||||
public:
|
||||
struct ConnectionData {
|
||||
Node *source = nullptr;
|
||||
Node *target = nullptr;
|
||||
StringName signal;
|
||||
StringName method;
|
||||
uint32_t flags = 0;
|
||||
int unbinds = 0;
|
||||
Vector<Variant> binds;
|
||||
|
||||
ConnectionData() {}
|
||||
|
||||
ConnectionData(const Connection &p_connection) {
|
||||
source = Object::cast_to<Node>(p_connection.signal.get_object());
|
||||
signal = p_connection.signal.get_name();
|
||||
target = Object::cast_to<Node>(p_connection.callable.get_object());
|
||||
flags = p_connection.flags;
|
||||
|
||||
Callable base_callable;
|
||||
if (p_connection.callable.is_custom()) {
|
||||
CallableCustomBind *ccb = dynamic_cast<CallableCustomBind *>(p_connection.callable.get_custom());
|
||||
if (ccb) {
|
||||
binds = ccb->get_binds();
|
||||
base_callable = ccb->get_callable();
|
||||
}
|
||||
|
||||
CallableCustomUnbind *ccu = dynamic_cast<CallableCustomUnbind *>(p_connection.callable.get_custom());
|
||||
if (ccu) {
|
||||
unbinds = ccu->get_unbinds();
|
||||
base_callable = ccu->get_callable();
|
||||
}
|
||||
} else {
|
||||
base_callable = p_connection.callable;
|
||||
}
|
||||
method = base_callable.get_method();
|
||||
}
|
||||
|
||||
Callable get_callable() const {
|
||||
if (unbinds > 0) {
|
||||
return Callable(target, method).unbind(unbinds);
|
||||
} else if (!binds.is_empty()) {
|
||||
const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * binds.size());
|
||||
for (int i = 0; i < binds.size(); i++) {
|
||||
argptrs[i] = &binds[i];
|
||||
}
|
||||
return Callable(target, method).bindp(argptrs, binds.size());
|
||||
} else {
|
||||
return Callable(target, method);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Label *connect_to_label = nullptr;
|
||||
LineEdit *from_signal = nullptr;
|
||||
LineEdit *filter_nodes = nullptr;
|
||||
Node *source = nullptr;
|
||||
ConnectionData source_connection_data;
|
||||
StringName signal;
|
||||
PackedStringArray signal_args;
|
||||
LineEdit *dst_method = nullptr;
|
||||
ConnectDialogBinds *cdbinds = nullptr;
|
||||
bool edit_mode = false;
|
||||
bool first_popup = true;
|
||||
NodePath dst_path;
|
||||
VBoxContainer *vbc_right = nullptr;
|
||||
SceneTreeEditor *tree = nullptr;
|
||||
AcceptDialog *error = nullptr;
|
||||
|
||||
Button *open_method_tree = nullptr;
|
||||
AcceptDialog *method_popup = nullptr;
|
||||
Tree *method_tree = nullptr;
|
||||
Label *empty_tree_label = nullptr;
|
||||
LineEdit *method_search = nullptr;
|
||||
CheckButton *script_methods_only = nullptr;
|
||||
CheckButton *compatible_methods_only = nullptr;
|
||||
|
||||
SpinBox *unbind_count = nullptr;
|
||||
EditorInspector *bind_editor = nullptr;
|
||||
OptionButton *type_list = nullptr;
|
||||
CheckBox *deferred = nullptr;
|
||||
CheckBox *one_shot = nullptr;
|
||||
CheckButton *advanced = nullptr;
|
||||
Vector<Control *> bind_controls;
|
||||
|
||||
Label *warning_label = nullptr;
|
||||
Label *error_label = nullptr;
|
||||
|
||||
void ok_pressed() override;
|
||||
void _cancel_pressed();
|
||||
void _item_activated();
|
||||
void _tree_node_selected();
|
||||
void _focus_currently_connected();
|
||||
|
||||
void _method_selected();
|
||||
void _create_method_tree_items(const List<MethodInfo> &p_methods, TreeItem *p_parent_item);
|
||||
List<MethodInfo> _filter_method_list(const List<MethodInfo> &p_methods, const MethodInfo &p_signal, const String &p_search_string) const;
|
||||
void _update_method_tree();
|
||||
void _method_check_button_pressed(const CheckButton *p_button);
|
||||
void _open_method_popup();
|
||||
|
||||
void _unbind_count_changed(double p_count);
|
||||
void _add_bind();
|
||||
void _remove_bind();
|
||||
void _advanced_pressed();
|
||||
void _update_ok_enabled();
|
||||
void _update_warning_label();
|
||||
|
||||
protected:
|
||||
virtual void _post_popup() override;
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static StringName generate_method_callback_name(Node *p_source, const String &p_signal_name, Node *p_target);
|
||||
Node *get_source() const;
|
||||
ConnectionData get_source_connection_data() const;
|
||||
StringName get_signal_name() const;
|
||||
PackedStringArray get_signal_args() const;
|
||||
NodePath get_dst_path() const;
|
||||
void set_dst_node(Node *p_node);
|
||||
StringName get_dst_method_name() const;
|
||||
void set_dst_method(const StringName &p_method);
|
||||
int get_unbinds() const;
|
||||
Vector<Variant> get_binds() const;
|
||||
String get_signature(const MethodInfo &p_method, PackedStringArray *r_arg_names = nullptr);
|
||||
|
||||
bool get_deferred() const;
|
||||
bool get_one_shot() const;
|
||||
bool is_editing() const;
|
||||
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
void init(const ConnectionData &p_cd, const PackedStringArray &p_signal_args, bool p_edit = false);
|
||||
|
||||
void popup_dialog(const String &p_for_signal);
|
||||
ConnectDialog();
|
||||
~ConnectDialog();
|
||||
};
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
// Custom `Tree` needed to use `EditorHelpBit` to display signal documentation.
|
||||
class ConnectionsDockTree : public Tree {
|
||||
virtual Control *make_custom_tooltip(const String &p_text) const;
|
||||
};
|
||||
|
||||
class ConnectionsDock : public VBoxContainer {
|
||||
GDCLASS(ConnectionsDock, VBoxContainer);
|
||||
|
||||
enum TreeItemType {
|
||||
TREE_ITEM_TYPE_ROOT,
|
||||
TREE_ITEM_TYPE_CLASS,
|
||||
TREE_ITEM_TYPE_SIGNAL,
|
||||
TREE_ITEM_TYPE_CONNECTION,
|
||||
};
|
||||
|
||||
// Right-click context menu options.
|
||||
enum ClassMenuOption {
|
||||
CLASS_MENU_OPEN_DOCS,
|
||||
};
|
||||
enum SignalMenuOption {
|
||||
SIGNAL_MENU_CONNECT,
|
||||
SIGNAL_MENU_DISCONNECT_ALL,
|
||||
SIGNAL_MENU_COPY_NAME,
|
||||
SIGNAL_MENU_OPEN_DOCS,
|
||||
};
|
||||
enum SlotMenuOption {
|
||||
SLOT_MENU_EDIT,
|
||||
SLOT_MENU_GO_TO_METHOD,
|
||||
SLOT_MENU_DISCONNECT,
|
||||
};
|
||||
|
||||
Node *selected_node = nullptr;
|
||||
ConnectionsDockTree *tree = nullptr;
|
||||
|
||||
ConfirmationDialog *disconnect_all_dialog = nullptr;
|
||||
ConnectDialog *connect_dialog = nullptr;
|
||||
Button *connect_button = nullptr;
|
||||
PopupMenu *class_menu = nullptr;
|
||||
String class_menu_doc_class_name;
|
||||
PopupMenu *signal_menu = nullptr;
|
||||
PopupMenu *slot_menu = nullptr;
|
||||
LineEdit *search_box = nullptr;
|
||||
|
||||
void _filter_changed(const String &p_text);
|
||||
|
||||
void _make_or_edit_connection();
|
||||
void _connect(const ConnectDialog::ConnectionData &p_cd);
|
||||
void _disconnect(const ConnectDialog::ConnectionData &p_cd);
|
||||
void _disconnect_all();
|
||||
|
||||
void _tree_item_selected();
|
||||
void _tree_item_activated();
|
||||
TreeItemType _get_item_type(const TreeItem &p_item) const;
|
||||
bool _is_connection_inherited(Connection &p_connection);
|
||||
|
||||
void _open_connection_dialog(TreeItem &p_item);
|
||||
void _open_edit_connection_dialog(TreeItem &p_item);
|
||||
void _go_to_method(TreeItem &p_item);
|
||||
|
||||
void _handle_class_menu_option(int p_option);
|
||||
void _class_menu_about_to_popup();
|
||||
void _handle_signal_menu_option(int p_option);
|
||||
void _signal_menu_about_to_popup();
|
||||
void _handle_slot_menu_option(int p_option);
|
||||
void _slot_menu_about_to_popup();
|
||||
void _tree_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _close();
|
||||
|
||||
protected:
|
||||
void _connect_pressed();
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_node(Node *p_node);
|
||||
void update_tree();
|
||||
|
||||
ConnectionsDock();
|
||||
~ConnectionsDock();
|
||||
};
|
||||
|
||||
#endif // CONNECTIONS_DIALOG_H
|
||||
824
engine/editor/create_dialog.cpp
Normal file
824
engine/editor/create_dialog.cpp
Normal file
|
|
@ -0,0 +1,824 @@
|
|||
/**************************************************************************/
|
||||
/* create_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 "create_dialog.h"
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/editor_feature_profile.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_paths.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
|
||||
void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const String &p_current_type, const String &p_current_name) {
|
||||
_fill_type_list();
|
||||
|
||||
icon_fallback = search_options->has_theme_icon(base_type, EditorStringName(EditorIcons)) ? base_type : "Object";
|
||||
|
||||
if (p_dont_clear) {
|
||||
search_box->select_all();
|
||||
} else {
|
||||
search_box->clear();
|
||||
}
|
||||
|
||||
if (p_replace_mode) {
|
||||
search_box->set_text(p_current_type);
|
||||
}
|
||||
|
||||
search_box->grab_focus();
|
||||
_update_search();
|
||||
|
||||
if (p_replace_mode) {
|
||||
set_title(vformat(TTR("Change Type of \"%s\""), p_current_name));
|
||||
set_ok_button_text(TTR("Change"));
|
||||
} else {
|
||||
set_title(vformat(TTR("Create New %s"), base_type));
|
||||
set_ok_button_text(TTR("Create"));
|
||||
}
|
||||
|
||||
_load_favorites_and_history();
|
||||
_save_and_update_favorite_list();
|
||||
|
||||
// Restore valid window bounds or pop up at default size.
|
||||
Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "create_new_node", Rect2());
|
||||
if (saved_size != Rect2()) {
|
||||
popup(saved_size);
|
||||
} else {
|
||||
popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::_fill_type_list() {
|
||||
List<StringName> complete_type_list;
|
||||
ClassDB::get_class_list(&complete_type_list);
|
||||
ScriptServer::get_global_class_list(&complete_type_list);
|
||||
|
||||
EditorData &ed = EditorNode::get_editor_data();
|
||||
|
||||
for (List<StringName>::Element *I = complete_type_list.front(); I; I = I->next()) {
|
||||
StringName type = I->get();
|
||||
if (!_should_hide_type(type)) {
|
||||
type_list.push_back(type);
|
||||
|
||||
if (!ed.get_custom_types().has(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Vector<EditorData::CustomType> &ct = ed.get_custom_types()[type];
|
||||
for (int i = 0; i < ct.size(); i++) {
|
||||
custom_type_parents[ct[i].name] = type;
|
||||
custom_type_indices[ct[i].name] = i;
|
||||
type_list.push_back(ct[i].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
type_list.sort_custom<StringName::AlphCompare>();
|
||||
}
|
||||
|
||||
bool CreateDialog::_is_type_preferred(const String &p_type) const {
|
||||
if (ClassDB::class_exists(p_type)) {
|
||||
return ClassDB::is_parent_class(p_type, preferred_search_result_type);
|
||||
}
|
||||
|
||||
return EditorNode::get_editor_data().script_class_is_parent(p_type, preferred_search_result_type);
|
||||
}
|
||||
|
||||
bool CreateDialog::_is_class_disabled_by_feature_profile(const StringName &p_class) const {
|
||||
Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
|
||||
|
||||
return !profile.is_null() && profile->is_class_disabled(p_class);
|
||||
}
|
||||
|
||||
bool CreateDialog::_should_hide_type(const StringName &p_type) const {
|
||||
if (_is_class_disabled_by_feature_profile(p_type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_base_type_node && p_type.operator String().begins_with("Editor")) {
|
||||
return true; // Do not show editor nodes.
|
||||
}
|
||||
|
||||
if (ClassDB::class_exists(p_type)) {
|
||||
if (!ClassDB::can_instantiate(p_type) || ClassDB::is_virtual(p_type)) {
|
||||
return true; // Can't create abstract or virtual class.
|
||||
}
|
||||
|
||||
if (!ClassDB::is_parent_class(p_type, base_type)) {
|
||||
return true; // Wrong inheritance.
|
||||
}
|
||||
|
||||
if (!ClassDB::is_class_exposed(p_type)) {
|
||||
return true; // Unexposed types.
|
||||
}
|
||||
|
||||
for (const StringName &E : type_blacklist) {
|
||||
if (ClassDB::is_parent_class(p_type, E)) {
|
||||
return true; // Parent type is blacklisted.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ScriptServer::is_global_class(p_type)) {
|
||||
return true;
|
||||
}
|
||||
if (!EditorNode::get_editor_data().script_class_is_parent(p_type, base_type)) {
|
||||
return true; // Wrong inheritance.
|
||||
}
|
||||
|
||||
StringName native_type = ScriptServer::get_global_class_native_base(p_type);
|
||||
if (ClassDB::class_exists(native_type) && !ClassDB::can_instantiate(native_type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String script_path = ScriptServer::get_global_class_path(p_type);
|
||||
if (script_path.begins_with("res://addons/")) {
|
||||
int i = script_path.find("/", 13); // 13 is length of "res://addons/".
|
||||
while (i > -1) {
|
||||
const String plugin_path = script_path.substr(0, i).path_join("plugin.cfg");
|
||||
if (FileAccess::exists(plugin_path)) {
|
||||
return !EditorNode::get_singleton()->is_addon_plugin_enabled(plugin_path);
|
||||
}
|
||||
i = script_path.find("/", i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CreateDialog::_update_search() {
|
||||
search_options->clear();
|
||||
search_options_types.clear();
|
||||
|
||||
TreeItem *root = search_options->create_item();
|
||||
root->set_text(0, base_type);
|
||||
root->set_icon(0, search_options->get_editor_theme_icon(icon_fallback));
|
||||
search_options_types[base_type] = root;
|
||||
_configure_search_option_item(root, base_type, ClassDB::class_exists(base_type) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE);
|
||||
|
||||
const String search_text = search_box->get_text();
|
||||
bool empty_search = search_text.is_empty();
|
||||
|
||||
float highest_score = 0.0f;
|
||||
StringName best_match;
|
||||
|
||||
for (List<StringName>::Element *I = type_list.front(); I; I = I->next()) {
|
||||
StringName candidate = I->get();
|
||||
if (empty_search || search_text.is_subsequence_ofn(candidate)) {
|
||||
_add_type(candidate, ClassDB::class_exists(candidate) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE);
|
||||
|
||||
// Determine the best match for an non-empty search.
|
||||
if (!empty_search) {
|
||||
float score = _score_type(candidate.operator String().get_slicec(' ', 0), search_text);
|
||||
if (score > highest_score) {
|
||||
highest_score = score;
|
||||
best_match = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select the best result.
|
||||
if (empty_search) {
|
||||
select_type(base_type);
|
||||
} else if (best_match != StringName()) {
|
||||
select_type(best_match);
|
||||
} else {
|
||||
favorite->set_disabled(true);
|
||||
help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]")));
|
||||
get_ok_button()->set_disabled(true);
|
||||
search_options->deselect_all();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::_add_type(const StringName &p_type, TypeCategory p_type_category) {
|
||||
if (search_options_types.has(p_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TypeCategory inherited_type = TypeCategory::OTHER_TYPE;
|
||||
|
||||
StringName inherits;
|
||||
if (p_type_category == TypeCategory::CPP_TYPE) {
|
||||
inherits = ClassDB::get_parent_class(p_type);
|
||||
inherited_type = TypeCategory::CPP_TYPE;
|
||||
} else {
|
||||
if (p_type_category == TypeCategory::PATH_TYPE || ScriptServer::is_global_class(p_type)) {
|
||||
Ref<Script> scr;
|
||||
if (p_type_category == TypeCategory::PATH_TYPE) {
|
||||
ERR_FAIL_COND(!ResourceLoader::exists(p_type, "Script"));
|
||||
scr = ResourceLoader::load(p_type, "Script");
|
||||
} else {
|
||||
scr = EditorNode::get_editor_data().script_class_load_script(p_type);
|
||||
}
|
||||
ERR_FAIL_COND(scr.is_null());
|
||||
|
||||
Ref<Script> base = scr->get_base_script();
|
||||
if (base.is_null()) {
|
||||
// Must be a native base type.
|
||||
StringName extends = scr->get_instance_base_type();
|
||||
if (extends == StringName()) {
|
||||
// Not a valid script (has compile errors), we therefore ignore it as it can not be instantiated anyway (when selected).
|
||||
return;
|
||||
}
|
||||
|
||||
inherits = extends;
|
||||
inherited_type = TypeCategory::CPP_TYPE;
|
||||
} else {
|
||||
inherits = base->get_global_name();
|
||||
|
||||
if (inherits == StringName()) {
|
||||
inherits = base->get_path();
|
||||
inherited_type = TypeCategory::PATH_TYPE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inherits = custom_type_parents[p_type];
|
||||
if (ClassDB::class_exists(inherits)) {
|
||||
inherited_type = TypeCategory::CPP_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen, but just in case...
|
||||
ERR_FAIL_COND(inherits == StringName());
|
||||
|
||||
_add_type(inherits, inherited_type);
|
||||
|
||||
TreeItem *item = search_options->create_item(search_options_types[inherits]);
|
||||
search_options_types[p_type] = item;
|
||||
_configure_search_option_item(item, p_type, p_type_category);
|
||||
}
|
||||
|
||||
void CreateDialog::_configure_search_option_item(TreeItem *r_item, const StringName &p_type, TypeCategory p_type_category) {
|
||||
bool script_type = ScriptServer::is_global_class(p_type);
|
||||
bool is_abstract = false;
|
||||
if (p_type_category == TypeCategory::CPP_TYPE) {
|
||||
r_item->set_text(0, p_type);
|
||||
} else if (p_type_category == TypeCategory::PATH_TYPE) {
|
||||
r_item->set_text(0, "\"" + p_type + "\"");
|
||||
} else if (script_type) {
|
||||
r_item->set_metadata(0, p_type);
|
||||
r_item->set_text(0, p_type);
|
||||
String script_path = ScriptServer::get_global_class_path(p_type);
|
||||
r_item->set_suffix(0, "(" + script_path.get_file() + ")");
|
||||
|
||||
Ref<Script> scr = ResourceLoader::load(script_path, "Script");
|
||||
ERR_FAIL_COND(!scr.is_valid());
|
||||
is_abstract = scr->is_abstract();
|
||||
} else {
|
||||
r_item->set_metadata(0, custom_type_parents[p_type]);
|
||||
r_item->set_text(0, p_type);
|
||||
}
|
||||
|
||||
bool can_instantiate = (p_type_category == TypeCategory::CPP_TYPE && ClassDB::can_instantiate(p_type)) ||
|
||||
(p_type_category == TypeCategory::OTHER_TYPE && !is_abstract);
|
||||
bool instantiable = can_instantiate && !(ClassDB::class_exists(p_type) && ClassDB::is_virtual(p_type));
|
||||
|
||||
r_item->set_meta(SNAME("__instantiable"), instantiable);
|
||||
|
||||
r_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_type));
|
||||
if (!instantiable) {
|
||||
r_item->set_custom_color(0, search_options->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
|
||||
}
|
||||
|
||||
HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(p_type);
|
||||
|
||||
bool is_deprecated = (class_doc && class_doc->value.is_deprecated);
|
||||
bool is_experimental = (class_doc && class_doc->value.is_experimental);
|
||||
|
||||
if (is_deprecated) {
|
||||
r_item->add_button(0, get_editor_theme_icon("StatusError"), 0, false, TTR("This class is marked as deprecated."));
|
||||
} else if (is_experimental) {
|
||||
r_item->add_button(0, get_editor_theme_icon("NodeWarning"), 0, false, TTR("This class is marked as experimental."));
|
||||
}
|
||||
|
||||
if (!search_box->get_text().is_empty()) {
|
||||
r_item->set_collapsed(false);
|
||||
} else {
|
||||
// Don't collapse the root node or an abstract node on the first tree level.
|
||||
bool should_collapse = p_type != base_type && (r_item->get_parent()->get_text(0) != base_type || can_instantiate);
|
||||
|
||||
if (should_collapse && bool(EDITOR_GET("docks/scene_tree/start_create_dialog_fully_expanded"))) {
|
||||
should_collapse = false; // Collapse all nodes anyway.
|
||||
}
|
||||
r_item->set_collapsed(should_collapse);
|
||||
}
|
||||
|
||||
const String &description = DTR(class_doc ? class_doc->value.brief_description : "");
|
||||
r_item->set_tooltip_text(0, description);
|
||||
|
||||
if (p_type_category == TypeCategory::OTHER_TYPE && !script_type) {
|
||||
Ref<Texture2D> icon = EditorNode::get_editor_data().get_custom_types()[custom_type_parents[p_type]][custom_type_indices[p_type]].icon;
|
||||
if (icon.is_valid()) {
|
||||
r_item->set_icon(0, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float CreateDialog::_score_type(const String &p_type, const String &p_search) const {
|
||||
if (p_type == p_search) {
|
||||
// Always favor an exact match (case-sensitive), since clicking a favorite will set the search text to the type.
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float inverse_length = 1.f / float(p_type.length());
|
||||
|
||||
// Favor types where search term is a substring close to the start of the type.
|
||||
float w = 0.5f;
|
||||
int pos = p_type.findn(p_search);
|
||||
float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
|
||||
|
||||
// Favor shorter items: they resemble the search term more.
|
||||
w = 0.9f;
|
||||
score *= (1 - w) + w * MIN(1.0f, p_search.length() * inverse_length);
|
||||
|
||||
score *= _is_type_preferred(p_type) ? 1.0f : 0.9f;
|
||||
|
||||
// Add score for being a favorite type.
|
||||
score *= favorite_list.has(p_type) ? 1.0f : 0.8f;
|
||||
|
||||
// Look through at most 5 recent items
|
||||
bool in_recent = false;
|
||||
constexpr int RECENT_COMPLETION_SIZE = 5;
|
||||
for (int i = 0; i < MIN(RECENT_COMPLETION_SIZE - 1, recent->get_item_count()); i++) {
|
||||
if (recent->get_item_text(i) == p_type) {
|
||||
in_recent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
score *= in_recent ? 1.0f : 0.9f;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
void CreateDialog::_cleanup() {
|
||||
type_list.clear();
|
||||
favorite_list.clear();
|
||||
favorites->clear();
|
||||
recent->clear();
|
||||
custom_type_parents.clear();
|
||||
custom_type_indices.clear();
|
||||
}
|
||||
|
||||
void CreateDialog::_confirmed() {
|
||||
String selected_item = get_selected_type();
|
||||
if (selected_item.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *selected = search_options->get_selected();
|
||||
if (!selected->get_meta("__instantiable", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("create_recent." + base_type), FileAccess::WRITE);
|
||||
if (f.is_valid()) {
|
||||
f->store_line(selected_item);
|
||||
|
||||
constexpr int RECENT_HISTORY_SIZE = 15;
|
||||
for (int i = 0; i < MIN(RECENT_HISTORY_SIZE - 1, recent->get_item_count()); i++) {
|
||||
if (recent->get_item_text(i) != selected_item) {
|
||||
f->store_line(recent->get_item_text(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To prevent, emitting an error from the transient window (shader dialog for example) hide this dialog before emitting the "create" signal.
|
||||
hide();
|
||||
|
||||
emit_signal(SNAME("create"));
|
||||
_cleanup();
|
||||
}
|
||||
|
||||
void CreateDialog::_text_changed(const String &p_newtext) {
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void CreateDialog::_sbox_input(const Ref<InputEvent> &p_ie) {
|
||||
Ref<InputEventKey> k = p_ie;
|
||||
if (k.is_valid() && k->is_pressed()) {
|
||||
switch (k->get_keycode()) {
|
||||
case Key::UP:
|
||||
case Key::DOWN:
|
||||
case Key::PAGEUP:
|
||||
case Key::PAGEDOWN: {
|
||||
search_options->gui_input(k);
|
||||
search_box->accept_event();
|
||||
} break;
|
||||
case Key::SPACE: {
|
||||
TreeItem *ti = search_options->get_selected();
|
||||
if (ti) {
|
||||
ti->set_collapsed(!ti->is_collapsed());
|
||||
}
|
||||
search_box->accept_event();
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
connect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
disconnect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (is_visible()) {
|
||||
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible.
|
||||
search_box->select_all();
|
||||
} else {
|
||||
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
|
||||
search_options->add_theme_constant_override("icon_max_width", icon_width);
|
||||
favorites->add_theme_constant_override("icon_max_width", icon_width);
|
||||
recent->set_fixed_icon_size(Size2(icon_width, icon_width));
|
||||
|
||||
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
favorite->set_icon(get_editor_theme_icon(SNAME("Favorites")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
|
||||
if (!search_options_types.has(p_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *to_select = search_options_types[p_type];
|
||||
to_select->select(0);
|
||||
search_options->scroll_to_item(to_select, p_center_on_item);
|
||||
|
||||
help_bit->parse_symbol("class|" + p_type + "|");
|
||||
|
||||
favorite->set_disabled(false);
|
||||
favorite->set_pressed(favorite_list.has(p_type));
|
||||
|
||||
if (to_select->get_meta("__instantiable", true)) {
|
||||
get_ok_button()->set_disabled(false);
|
||||
get_ok_button()->set_tooltip_text(String());
|
||||
} else {
|
||||
get_ok_button()->set_disabled(true);
|
||||
get_ok_button()->set_tooltip_text(TTR("The selected class can't be instantiated."));
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::select_base() {
|
||||
if (search_options_types.is_empty()) {
|
||||
_update_search();
|
||||
}
|
||||
select_type(base_type, false);
|
||||
}
|
||||
|
||||
String CreateDialog::get_selected_type() {
|
||||
TreeItem *selected = search_options->get_selected();
|
||||
if (!selected) {
|
||||
return String();
|
||||
}
|
||||
|
||||
return selected->get_text(0);
|
||||
}
|
||||
|
||||
void CreateDialog::set_base_type(const String &p_base) {
|
||||
base_type = p_base;
|
||||
is_base_type_node = ClassDB::is_parent_class(p_base, "Node");
|
||||
}
|
||||
|
||||
Variant CreateDialog::instantiate_selected() {
|
||||
TreeItem *selected = search_options->get_selected();
|
||||
|
||||
if (!selected) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Variant md = selected->get_metadata(0);
|
||||
Variant obj;
|
||||
if (md.get_type() != Variant::NIL) {
|
||||
String custom = md;
|
||||
if (ScriptServer::is_global_class(custom)) {
|
||||
obj = EditorNode::get_editor_data().script_class_instance(custom);
|
||||
Node *n = Object::cast_to<Node>(obj);
|
||||
if (n) {
|
||||
n->set_name(custom);
|
||||
}
|
||||
} else {
|
||||
obj = EditorNode::get_editor_data().instantiate_custom_type(selected->get_text(0), custom);
|
||||
}
|
||||
} else {
|
||||
obj = ClassDB::instantiate(selected->get_text(0));
|
||||
}
|
||||
EditorNode::get_editor_data().instantiate_object_properties(obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void CreateDialog::_item_selected() {
|
||||
String name = get_selected_type();
|
||||
select_type(name, false);
|
||||
}
|
||||
|
||||
void CreateDialog::_hide_requested() {
|
||||
_cancel_pressed(); // From AcceptDialog.
|
||||
}
|
||||
|
||||
void CreateDialog::cancel_pressed() {
|
||||
_cleanup();
|
||||
}
|
||||
|
||||
void CreateDialog::_favorite_toggled() {
|
||||
TreeItem *item = search_options->get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = item->get_text(0);
|
||||
|
||||
if (favorite_list.has(name)) {
|
||||
favorite_list.erase(name);
|
||||
favorite->set_pressed(false);
|
||||
} else {
|
||||
favorite_list.push_back(name);
|
||||
favorite->set_pressed(true);
|
||||
}
|
||||
|
||||
_save_and_update_favorite_list();
|
||||
}
|
||||
|
||||
void CreateDialog::_history_selected(int p_idx) {
|
||||
search_box->set_text(recent->get_item_text(p_idx).get_slicec(' ', 0));
|
||||
favorites->deselect_all();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void CreateDialog::_favorite_selected() {
|
||||
TreeItem *item = favorites->get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
search_box->set_text(item->get_text(0).get_slicec(' ', 0));
|
||||
recent->deselect_all();
|
||||
_update_search();
|
||||
}
|
||||
|
||||
void CreateDialog::_history_activated(int p_idx) {
|
||||
_history_selected(p_idx);
|
||||
_confirmed();
|
||||
}
|
||||
|
||||
void CreateDialog::_favorite_activated() {
|
||||
_favorite_selected();
|
||||
_confirmed();
|
||||
}
|
||||
|
||||
Variant CreateDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
|
||||
TreeItem *ti = favorites->get_item_at_position(p_point);
|
||||
if (ti) {
|
||||
Dictionary d;
|
||||
d["type"] = "create_favorite_drag";
|
||||
d["class"] = ti->get_text(0);
|
||||
|
||||
Button *tb = memnew(Button);
|
||||
tb->set_flat(true);
|
||||
tb->set_icon(ti->get_icon(0));
|
||||
tb->set_text(ti->get_text(0));
|
||||
favorites->set_drag_preview(tb);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
return Variant();
|
||||
}
|
||||
|
||||
bool CreateDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
|
||||
Dictionary d = p_data;
|
||||
if (d.has("type") && String(d["type"]) == "create_favorite_drag") {
|
||||
favorites->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CreateDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
|
||||
Dictionary d = p_data;
|
||||
|
||||
TreeItem *ti = favorites->get_item_at_position(p_point);
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
|
||||
String drop_at = ti->get_text(0);
|
||||
int ds = favorites->get_drop_section_at_position(p_point);
|
||||
|
||||
int drop_idx = favorite_list.find(drop_at);
|
||||
if (drop_idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String type = d["class"];
|
||||
|
||||
int from_idx = favorite_list.find(type);
|
||||
if (from_idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (drop_idx == from_idx) {
|
||||
ds = -1; //cause it will be gone
|
||||
} else if (drop_idx > from_idx) {
|
||||
drop_idx--;
|
||||
}
|
||||
|
||||
favorite_list.remove_at(from_idx);
|
||||
|
||||
if (ds < 0) {
|
||||
favorite_list.insert(drop_idx, type);
|
||||
} else {
|
||||
if (drop_idx >= favorite_list.size() - 1) {
|
||||
favorite_list.push_back(type);
|
||||
} else {
|
||||
favorite_list.insert(drop_idx + 1, type);
|
||||
}
|
||||
}
|
||||
|
||||
_save_and_update_favorite_list();
|
||||
}
|
||||
|
||||
void CreateDialog::_save_and_update_favorite_list() {
|
||||
favorites->clear();
|
||||
TreeItem *root = favorites->create_item();
|
||||
|
||||
{
|
||||
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites." + base_type), FileAccess::WRITE);
|
||||
if (f.is_valid()) {
|
||||
for (int i = 0; i < favorite_list.size(); i++) {
|
||||
String l = favorite_list[i];
|
||||
String name = l.get_slicec(' ', 0);
|
||||
if (!EditorNode::get_editor_data().is_type_recognized(name)) {
|
||||
continue;
|
||||
}
|
||||
f->store_line(l);
|
||||
|
||||
if (_is_class_disabled_by_feature_profile(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TreeItem *ti = favorites->create_item(root);
|
||||
ti->set_text(0, l);
|
||||
ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit_signal(SNAME("favorites_updated"));
|
||||
}
|
||||
|
||||
void CreateDialog::_load_favorites_and_history() {
|
||||
String dir = EditorPaths::get_singleton()->get_project_settings_dir();
|
||||
Ref<FileAccess> f = FileAccess::open(dir.path_join("create_recent." + base_type), FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
while (!f->eof_reached()) {
|
||||
String l = f->get_line().strip_edges();
|
||||
String name = l.get_slicec(' ', 0);
|
||||
|
||||
if (EditorNode::get_editor_data().is_type_recognized(name) && !_is_class_disabled_by_feature_profile(name)) {
|
||||
recent->add_item(l, EditorNode::get_singleton()->get_class_icon(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f = FileAccess::open(dir.path_join("favorites." + base_type), FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
while (!f->eof_reached()) {
|
||||
String l = f->get_line().strip_edges();
|
||||
|
||||
if (!l.is_empty()) {
|
||||
favorite_list.push_back(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("create"));
|
||||
ADD_SIGNAL(MethodInfo("favorites_updated"));
|
||||
}
|
||||
|
||||
CreateDialog::CreateDialog() {
|
||||
base_type = "Object";
|
||||
preferred_search_result_type = "";
|
||||
|
||||
type_blacklist.insert("PluginScript"); // PluginScript must be initialized before use, which is not possible here.
|
||||
type_blacklist.insert("ScriptCreateDialog"); // This is an exposed editor Node that doesn't have an Editor prefix.
|
||||
|
||||
HSplitContainer *hsc = memnew(HSplitContainer);
|
||||
add_child(hsc);
|
||||
|
||||
VSplitContainer *vsc = memnew(VSplitContainer);
|
||||
hsc->add_child(vsc);
|
||||
|
||||
VBoxContainer *fav_vb = memnew(VBoxContainer);
|
||||
fav_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
|
||||
fav_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vsc->add_child(fav_vb);
|
||||
|
||||
favorites = memnew(Tree);
|
||||
favorites->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
favorites->set_hide_root(true);
|
||||
favorites->set_hide_folding(true);
|
||||
favorites->set_allow_reselect(true);
|
||||
favorites->connect("cell_selected", callable_mp(this, &CreateDialog::_favorite_selected));
|
||||
favorites->connect("item_activated", callable_mp(this, &CreateDialog::_favorite_activated));
|
||||
favorites->add_theme_constant_override("draw_guides", 1);
|
||||
SET_DRAG_FORWARDING_GCD(favorites, CreateDialog);
|
||||
fav_vb->add_margin_child(TTR("Favorites:"), favorites, true);
|
||||
|
||||
VBoxContainer *rec_vb = memnew(VBoxContainer);
|
||||
vsc->add_child(rec_vb);
|
||||
rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
|
||||
rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
recent = memnew(ItemList);
|
||||
recent->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
rec_vb->add_margin_child(TTR("Recent:"), recent, true);
|
||||
recent->set_allow_reselect(true);
|
||||
recent->connect(SceneStringName(item_selected), callable_mp(this, &CreateDialog::_history_selected));
|
||||
recent->connect("item_activated", callable_mp(this, &CreateDialog::_history_activated));
|
||||
recent->add_theme_constant_override("draw_guides", 1);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
vbc->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
|
||||
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
hsc->add_child(vbc);
|
||||
|
||||
search_box = memnew(LineEdit);
|
||||
search_box->set_clear_button_enabled(true);
|
||||
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
search_box->connect(SceneStringName(text_changed), callable_mp(this, &CreateDialog::_text_changed));
|
||||
search_box->connect(SceneStringName(gui_input), callable_mp(this, &CreateDialog::_sbox_input));
|
||||
|
||||
HBoxContainer *search_hb = memnew(HBoxContainer);
|
||||
search_hb->add_child(search_box);
|
||||
|
||||
favorite = memnew(Button);
|
||||
favorite->set_toggle_mode(true);
|
||||
favorite->set_tooltip_text(TTR("(Un)favorite selected item."));
|
||||
favorite->connect(SceneStringName(pressed), callable_mp(this, &CreateDialog::_favorite_toggled));
|
||||
search_hb->add_child(favorite);
|
||||
vbc->add_margin_child(TTR("Search:"), search_hb);
|
||||
|
||||
search_options = memnew(Tree);
|
||||
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
search_options->connect("item_activated", callable_mp(this, &CreateDialog::_confirmed));
|
||||
search_options->connect("cell_selected", callable_mp(this, &CreateDialog::_item_selected));
|
||||
vbc->add_margin_child(TTR("Matches:"), search_options, true);
|
||||
|
||||
help_bit = memnew(EditorHelpBit);
|
||||
help_bit->set_content_height_limits(64 * EDSCALE, 64 * EDSCALE);
|
||||
help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested));
|
||||
vbc->add_margin_child(TTR("Description:"), help_bit);
|
||||
|
||||
register_text_enter(search_box);
|
||||
set_hide_on_ok(false);
|
||||
set_clamp_to_embedder(true);
|
||||
}
|
||||
126
engine/editor/create_dialog.h
Normal file
126
engine/editor/create_dialog.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**************************************************************************/
|
||||
/* create_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CREATE_DIALOG_H
|
||||
#define CREATE_DIALOG_H
|
||||
|
||||
#include "editor/editor_help.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class CreateDialog : public ConfirmationDialog {
|
||||
GDCLASS(CreateDialog, ConfirmationDialog);
|
||||
|
||||
enum TypeCategory {
|
||||
CPP_TYPE,
|
||||
PATH_TYPE,
|
||||
OTHER_TYPE
|
||||
};
|
||||
|
||||
LineEdit *search_box = nullptr;
|
||||
Tree *search_options = nullptr;
|
||||
|
||||
String base_type;
|
||||
bool is_base_type_node = false;
|
||||
String icon_fallback;
|
||||
String preferred_search_result_type;
|
||||
|
||||
Button *favorite = nullptr;
|
||||
Vector<String> favorite_list;
|
||||
Tree *favorites = nullptr;
|
||||
ItemList *recent = nullptr;
|
||||
EditorHelpBit *help_bit = nullptr;
|
||||
|
||||
HashMap<String, TreeItem *> search_options_types;
|
||||
HashMap<String, String> custom_type_parents;
|
||||
HashMap<String, int> custom_type_indices;
|
||||
List<StringName> type_list;
|
||||
HashSet<StringName> type_blacklist;
|
||||
|
||||
void _update_search();
|
||||
bool _should_hide_type(const StringName &p_type) const;
|
||||
void _add_type(const StringName &p_type, TypeCategory p_type_category);
|
||||
void _configure_search_option_item(TreeItem *r_item, const StringName &p_type, TypeCategory p_type_category);
|
||||
float _score_type(const String &p_type, const String &p_search) const;
|
||||
bool _is_type_preferred(const String &p_type) const;
|
||||
|
||||
void _fill_type_list();
|
||||
void _cleanup();
|
||||
|
||||
void _sbox_input(const Ref<InputEvent> &p_ie);
|
||||
void _text_changed(const String &p_newtext);
|
||||
void select_type(const String &p_type, bool p_center_on_item = true);
|
||||
void _item_selected();
|
||||
void _hide_requested();
|
||||
|
||||
void _confirmed();
|
||||
virtual void cancel_pressed() override;
|
||||
|
||||
void _favorite_toggled();
|
||||
|
||||
void _history_selected(int p_idx);
|
||||
void _favorite_selected();
|
||||
|
||||
void _history_activated(int p_idx);
|
||||
void _favorite_activated();
|
||||
|
||||
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);
|
||||
|
||||
bool _is_class_disabled_by_feature_profile(const StringName &p_class) const;
|
||||
void _load_favorites_and_history();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
void _save_and_update_favorite_list();
|
||||
|
||||
public:
|
||||
Variant instantiate_selected();
|
||||
String get_selected_type();
|
||||
|
||||
void set_base_type(const String &p_base);
|
||||
String get_base_type() const { return base_type; }
|
||||
void select_base();
|
||||
|
||||
void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; }
|
||||
String get_preferred_search_result_type() { return preferred_search_result_type; }
|
||||
|
||||
void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_current_type = "", const String &p_current_name = "");
|
||||
|
||||
CreateDialog();
|
||||
};
|
||||
|
||||
#endif // CREATE_DIALOG_H
|
||||
7
engine/editor/debugger/SCsub
Normal file
7
engine/editor/debugger/SCsub
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
|
||||
SConscript("debug_adapter/SCsub")
|
||||
5
engine/editor/debugger/debug_adapter/SCsub
Normal file
5
engine/editor/debugger/debug_adapter/SCsub
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
636
engine/editor/debugger/debug_adapter/debug_adapter_parser.cpp
Normal file
636
engine/editor/debugger/debug_adapter/debug_adapter_parser.cpp
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_parser.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 "debug_adapter_parser.h"
|
||||
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/debugger/script_editor_debugger.h"
|
||||
#include "editor/export/editor_export_platform.h"
|
||||
#include "editor/gui/editor_run_bar.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
|
||||
void DebugAdapterParser::_bind_methods() {
|
||||
// Requests
|
||||
ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize);
|
||||
ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect);
|
||||
ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch);
|
||||
ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach);
|
||||
ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart);
|
||||
ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate);
|
||||
ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::req_configurationDone);
|
||||
ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause);
|
||||
ClassDB::bind_method(D_METHOD("req_continue", "params"), &DebugAdapterParser::req_continue);
|
||||
ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads);
|
||||
ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace);
|
||||
ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints);
|
||||
ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations);
|
||||
ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes);
|
||||
ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables);
|
||||
ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next);
|
||||
ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn);
|
||||
ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate);
|
||||
ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::prepare_base_event() const {
|
||||
Dictionary event;
|
||||
event["type"] = "event";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::prepare_success_response(const Dictionary &p_params) const {
|
||||
Dictionary response;
|
||||
response["type"] = "response";
|
||||
response["request_seq"] = p_params["seq"];
|
||||
response["command"] = p_params["command"];
|
||||
response["success"] = true;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables) const {
|
||||
Dictionary response, body;
|
||||
response["type"] = "response";
|
||||
response["request_seq"] = p_params["seq"];
|
||||
response["command"] = p_params["command"];
|
||||
response["success"] = false;
|
||||
response["body"] = body;
|
||||
|
||||
DAP::Message message;
|
||||
String error, error_desc;
|
||||
switch (err_type) {
|
||||
case DAP::ErrorType::WRONG_PATH:
|
||||
error = "wrong_path";
|
||||
error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
|
||||
break;
|
||||
case DAP::ErrorType::NOT_RUNNING:
|
||||
error = "not_running";
|
||||
error_desc = "Can't attach to a running session since there isn't one.";
|
||||
break;
|
||||
case DAP::ErrorType::TIMEOUT:
|
||||
error = "timeout";
|
||||
error_desc = "Timeout reached while processing a request.";
|
||||
break;
|
||||
case DAP::ErrorType::UNKNOWN_PLATFORM:
|
||||
error = "unknown_platform";
|
||||
error_desc = "The specified platform is unknown.";
|
||||
break;
|
||||
case DAP::ErrorType::MISSING_DEVICE:
|
||||
error = "missing_device";
|
||||
error_desc = "There's no connected device with specified id.";
|
||||
break;
|
||||
case DAP::ErrorType::UNKNOWN:
|
||||
default:
|
||||
error = "unknown";
|
||||
error_desc = "An unknown error has occurred when processing the request.";
|
||||
break;
|
||||
}
|
||||
|
||||
message.id = err_type;
|
||||
message.format = error_desc;
|
||||
message.variables = variables;
|
||||
response["message"] = error;
|
||||
body["error"] = message.to_json();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params);
|
||||
Dictionary args = p_params["arguments"];
|
||||
|
||||
Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer();
|
||||
|
||||
peer->linesStartAt1 = args.get("linesStartAt1", false);
|
||||
peer->columnsStartAt1 = args.get("columnsStartAt1", false);
|
||||
peer->supportsVariableType = args.get("supportsVariableType", false);
|
||||
peer->supportsInvalidatedEvent = args.get("supportsInvalidatedEvent", false);
|
||||
|
||||
DAP::Capabilities caps;
|
||||
response["body"] = caps.to_json();
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->notify_initialized();
|
||||
|
||||
if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) {
|
||||
// Send all current breakpoints
|
||||
List<String> breakpoints;
|
||||
ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
|
||||
for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
|
||||
String breakpoint = E->get();
|
||||
|
||||
String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://"
|
||||
int line = breakpoint.substr(path.size()).to_int();
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true);
|
||||
}
|
||||
} else {
|
||||
// Remove all current breakpoints
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
|
||||
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
|
||||
EditorRunBar::get_singleton()->stop_playing();
|
||||
}
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
|
||||
Dictionary args = p_params["arguments"];
|
||||
if (args.has("project") && !is_valid_path(args["project"])) {
|
||||
Dictionary variables;
|
||||
variables["clientPath"] = args["project"];
|
||||
variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path();
|
||||
return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
|
||||
}
|
||||
|
||||
if (args.has("godot/custom_data")) {
|
||||
DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"];
|
||||
}
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->get_current_peer()->pending_launch = p_params;
|
||||
|
||||
return Dictionary();
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::_launch_process(const Dictionary &p_params) const {
|
||||
Dictionary args = p_params["arguments"];
|
||||
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
|
||||
if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) {
|
||||
dbg->debug_skip_breakpoints();
|
||||
}
|
||||
|
||||
String platform_string = args.get("platform", "host");
|
||||
if (platform_string == "host") {
|
||||
EditorRunBar::get_singleton()->play_main_scene();
|
||||
} else {
|
||||
int device = args.get("device", -1);
|
||||
int idx = -1;
|
||||
if (platform_string == "android") {
|
||||
for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
|
||||
if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (platform_string == "web") {
|
||||
for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
|
||||
if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Web") {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idx == -1) {
|
||||
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
|
||||
}
|
||||
|
||||
EditorRunBar *run_bar = EditorRunBar::get_singleton();
|
||||
Error err = platform_string == "android" ? run_bar->start_native_device(device * 10000 + idx) : run_bar->start_native_device(idx);
|
||||
if (err) {
|
||||
if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
|
||||
return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
|
||||
} else {
|
||||
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false;
|
||||
DebugAdapterProtocol::get_singleton()->notify_process();
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const {
|
||||
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
|
||||
if (!dbg->is_session_active()) {
|
||||
return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING);
|
||||
}
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true;
|
||||
DebugAdapterProtocol::get_singleton()->notify_process();
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
|
||||
// Extract embedded "arguments" so it can be given to req_launch/req_attach
|
||||
Dictionary params = p_params, args;
|
||||
args = params["arguments"];
|
||||
args = args["arguments"];
|
||||
params["arguments"] = args;
|
||||
|
||||
Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : _launch_process(params);
|
||||
if (!response["success"]) {
|
||||
response["command"] = p_params["command"];
|
||||
return response;
|
||||
}
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
|
||||
EditorRunBar::get_singleton()->stop_playing();
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_configurationDone(const Dictionary &p_params) const {
|
||||
Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer();
|
||||
if (!peer->pending_launch.is_empty()) {
|
||||
peer->res_queue.push_back(_launch_process(peer->pending_launch));
|
||||
peer->pending_launch.clear();
|
||||
}
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const {
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_pressed(true);
|
||||
EditorDebuggerNode::get_singleton()->_paused();
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->notify_stopped_paused();
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const {
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false);
|
||||
EditorDebuggerNode::get_singleton()->_paused();
|
||||
|
||||
DebugAdapterProtocol::get_singleton()->notify_continued();
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_threads(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
Array arr;
|
||||
DAP::Thread thread;
|
||||
|
||||
thread.id = 1; // Hardcoded because Godot only supports debugging one thread at the moment
|
||||
thread.name = "Main";
|
||||
arr.push_back(thread.to_json());
|
||||
body["threads"] = arr;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const {
|
||||
if (DebugAdapterProtocol::get_singleton()->_processing_stackdump) {
|
||||
return Dictionary();
|
||||
}
|
||||
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1;
|
||||
bool columns_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->columnsStartAt1;
|
||||
|
||||
Array arr;
|
||||
DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton();
|
||||
for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) {
|
||||
DAP::StackFrame sf = E.key;
|
||||
if (!lines_at_one) {
|
||||
sf.line--;
|
||||
}
|
||||
if (!columns_at_one) {
|
||||
sf.column--;
|
||||
}
|
||||
|
||||
arr.push_back(sf.to_json());
|
||||
}
|
||||
|
||||
body["stackFrames"] = arr;
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
Dictionary args = p_params["arguments"];
|
||||
DAP::Source source;
|
||||
source.from_json(args["source"]);
|
||||
|
||||
bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1;
|
||||
|
||||
if (!is_valid_path(source.path)) {
|
||||
Dictionary variables;
|
||||
variables["clientPath"] = source.path;
|
||||
variables["editorPath"] = ProjectSettings::get_singleton()->get_resource_path();
|
||||
return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
|
||||
}
|
||||
|
||||
// If path contains \, it's a Windows path, so we need to convert it to /, and make the drive letter uppercase
|
||||
if (source.path.contains("\\")) {
|
||||
source.path = source.path.replace("\\", "/");
|
||||
source.path = source.path.substr(0, 1).to_upper() + source.path.substr(1);
|
||||
}
|
||||
|
||||
Array breakpoints = args["breakpoints"], lines;
|
||||
for (int i = 0; i < breakpoints.size(); i++) {
|
||||
DAP::SourceBreakpoint breakpoint;
|
||||
breakpoint.from_json(breakpoints[i]);
|
||||
|
||||
lines.push_back(breakpoint.line + !lines_at_one);
|
||||
}
|
||||
|
||||
Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
|
||||
body["breakpoints"] = updated_breakpoints;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
Dictionary args = p_params["arguments"];
|
||||
|
||||
Array locations;
|
||||
DAP::BreakpointLocation location;
|
||||
location.line = args["line"];
|
||||
if (args.has("endLine")) {
|
||||
location.endLine = args["endLine"];
|
||||
}
|
||||
locations.push_back(location.to_json());
|
||||
|
||||
body["breakpoints"] = locations;
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
Dictionary args = p_params["arguments"];
|
||||
int frame_id = args["frameId"];
|
||||
Array scope_list;
|
||||
|
||||
DAP::StackFrame frame;
|
||||
frame.id = frame_id;
|
||||
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame>::Iterator E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame);
|
||||
if (E) {
|
||||
ERR_FAIL_COND_V(E->value.size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN));
|
||||
List<int>::ConstIterator itr = E->value.begin();
|
||||
for (int i = 0; i < 3; ++itr, ++i) {
|
||||
DAP::Scope scope;
|
||||
scope.variablesReference = *itr;
|
||||
switch (i) {
|
||||
case 0:
|
||||
scope.name = "Locals";
|
||||
scope.presentationHint = "locals";
|
||||
break;
|
||||
case 1:
|
||||
scope.name = "Members";
|
||||
scope.presentationHint = "members";
|
||||
break;
|
||||
case 2:
|
||||
scope.name = "Globals";
|
||||
scope.presentationHint = "globals";
|
||||
}
|
||||
|
||||
scope_list.push_back(scope.to_json());
|
||||
}
|
||||
}
|
||||
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->request_stack_dump(frame_id);
|
||||
DebugAdapterProtocol::get_singleton()->_current_frame = frame_id;
|
||||
|
||||
body["scopes"] = scope_list;
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
|
||||
// If _remaining_vars > 0, the debuggee is still sending a stack dump to the editor.
|
||||
if (DebugAdapterProtocol::get_singleton()->_remaining_vars > 0) {
|
||||
return Dictionary();
|
||||
}
|
||||
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
Dictionary args = p_params["arguments"];
|
||||
int variable_id = args["variablesReference"];
|
||||
|
||||
HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
|
||||
|
||||
if (E) {
|
||||
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
|
||||
for (int i = 0; i < E->value.size(); i++) {
|
||||
Dictionary variable = E->value[i];
|
||||
variable.erase("type");
|
||||
}
|
||||
}
|
||||
body["variables"] = E ? E->value : Array();
|
||||
return response;
|
||||
} else {
|
||||
return Dictionary();
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_next();
|
||||
DebugAdapterProtocol::get_singleton()->_stepping = true;
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_step();
|
||||
DebugAdapterProtocol::get_singleton()->_stepping = true;
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
|
||||
Dictionary response = prepare_success_response(p_params), body;
|
||||
response["body"] = body;
|
||||
|
||||
Dictionary args = p_params["arguments"];
|
||||
|
||||
String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
|
||||
body["result"] = value;
|
||||
body["variablesReference"] = 0;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
|
||||
Dictionary args = p_params["arguments"];
|
||||
|
||||
String msg = args["message"];
|
||||
Array data = args["data"];
|
||||
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
|
||||
|
||||
return prepare_success_response(p_params);
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_initialized() const {
|
||||
Dictionary event = prepare_base_event();
|
||||
event["event"] = "initialized";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_process(const String &p_command) const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "process";
|
||||
event["body"] = body;
|
||||
|
||||
body["name"] = OS::get_singleton()->get_executable_path();
|
||||
body["startMethod"] = p_command;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_terminated() const {
|
||||
Dictionary event = prepare_base_event();
|
||||
event["event"] = "terminated";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_exited(const int &p_exitcode) const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "exited";
|
||||
event["body"] = body;
|
||||
|
||||
body["exitCode"] = p_exitcode;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_stopped() const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "stopped";
|
||||
event["body"] = body;
|
||||
|
||||
body["threadId"] = 1;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_stopped_paused() const {
|
||||
Dictionary event = ev_stopped();
|
||||
Dictionary body = event["body"];
|
||||
|
||||
body["reason"] = "paused";
|
||||
body["description"] = "Paused";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_stopped_exception(const String &p_error) const {
|
||||
Dictionary event = ev_stopped();
|
||||
Dictionary body = event["body"];
|
||||
|
||||
body["reason"] = "exception";
|
||||
body["description"] = "Exception";
|
||||
body["text"] = p_error;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_stopped_breakpoint(const int &p_id) const {
|
||||
Dictionary event = ev_stopped();
|
||||
Dictionary body = event["body"];
|
||||
|
||||
body["reason"] = "breakpoint";
|
||||
body["description"] = "Breakpoint";
|
||||
|
||||
Array breakpoints;
|
||||
breakpoints.push_back(p_id);
|
||||
body["hitBreakpointIds"] = breakpoints;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_stopped_step() const {
|
||||
Dictionary event = ev_stopped();
|
||||
Dictionary body = event["body"];
|
||||
|
||||
body["reason"] = "step";
|
||||
body["description"] = "Breakpoint";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_continued() const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "continued";
|
||||
event["body"] = body;
|
||||
|
||||
body["threadId"] = 1;
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_output(const String &p_message, RemoteDebugger::MessageType p_type) const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "output";
|
||||
event["body"] = body;
|
||||
|
||||
body["category"] = (p_type == RemoteDebugger::MessageType::MESSAGE_TYPE_ERROR) ? "stderr" : "stdout";
|
||||
body["output"] = p_message + "\r\n";
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "breakpoint";
|
||||
event["body"] = body;
|
||||
|
||||
body["reason"] = p_enabled ? "new" : "removed";
|
||||
body["breakpoint"] = p_breakpoint.to_json();
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const {
|
||||
Dictionary event = prepare_base_event(), body;
|
||||
event["event"] = "godot/custom_data";
|
||||
event["body"] = body;
|
||||
|
||||
body["message"] = p_msg;
|
||||
body["data"] = p_data;
|
||||
|
||||
return event;
|
||||
}
|
||||
107
engine/editor/debugger/debug_adapter/debug_adapter_parser.h
Normal file
107
engine/editor/debugger/debug_adapter/debug_adapter_parser.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_parser.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DEBUG_ADAPTER_PARSER_H
|
||||
#define DEBUG_ADAPTER_PARSER_H
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/debugger/remote_debugger.h"
|
||||
#include "debug_adapter_protocol.h"
|
||||
#include "debug_adapter_types.h"
|
||||
|
||||
struct DAPeer;
|
||||
class DebugAdapterProtocol;
|
||||
|
||||
class DebugAdapterParser : public Object {
|
||||
GDCLASS(DebugAdapterParser, Object);
|
||||
|
||||
private:
|
||||
friend DebugAdapterProtocol;
|
||||
|
||||
_FORCE_INLINE_ bool is_valid_path(const String &p_path) const {
|
||||
// If path contains \, it's a Windows path, so we need to convert it to /, and check as case-insensitive.
|
||||
if (p_path.contains("\\")) {
|
||||
String project_path = ProjectSettings::get_singleton()->get_resource_path();
|
||||
String path = p_path.replace("\\", "/");
|
||||
return path.containsn(project_path);
|
||||
}
|
||||
return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path());
|
||||
}
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
Dictionary prepare_base_event() const;
|
||||
Dictionary prepare_success_response(const Dictionary &p_params) const;
|
||||
Dictionary prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables = Dictionary()) const;
|
||||
|
||||
Dictionary ev_stopped() const;
|
||||
|
||||
public:
|
||||
// Requests
|
||||
Dictionary req_initialize(const Dictionary &p_params) const;
|
||||
Dictionary req_launch(const Dictionary &p_params) const;
|
||||
Dictionary req_disconnect(const Dictionary &p_params) const;
|
||||
Dictionary req_attach(const Dictionary &p_params) const;
|
||||
Dictionary req_restart(const Dictionary &p_params) const;
|
||||
Dictionary req_terminate(const Dictionary &p_params) const;
|
||||
Dictionary req_configurationDone(const Dictionary &p_params) const;
|
||||
Dictionary req_pause(const Dictionary &p_params) const;
|
||||
Dictionary req_continue(const Dictionary &p_params) const;
|
||||
Dictionary req_threads(const Dictionary &p_params) const;
|
||||
Dictionary req_stackTrace(const Dictionary &p_params) const;
|
||||
Dictionary req_setBreakpoints(const Dictionary &p_params) const;
|
||||
Dictionary req_breakpointLocations(const Dictionary &p_params) const;
|
||||
Dictionary req_scopes(const Dictionary &p_params) const;
|
||||
Dictionary req_variables(const Dictionary &p_params) const;
|
||||
Dictionary req_next(const Dictionary &p_params) const;
|
||||
Dictionary req_stepIn(const Dictionary &p_params) const;
|
||||
Dictionary req_evaluate(const Dictionary &p_params) const;
|
||||
Dictionary req_godot_put_msg(const Dictionary &p_params) const;
|
||||
|
||||
// Internal requests
|
||||
Dictionary _launch_process(const Dictionary &p_params) const;
|
||||
|
||||
// Events
|
||||
Dictionary ev_initialized() const;
|
||||
Dictionary ev_process(const String &p_command) const;
|
||||
Dictionary ev_terminated() const;
|
||||
Dictionary ev_exited(const int &p_exitcode) const;
|
||||
Dictionary ev_stopped_paused() const;
|
||||
Dictionary ev_stopped_exception(const String &p_error) const;
|
||||
Dictionary ev_stopped_breakpoint(const int &p_id) const;
|
||||
Dictionary ev_stopped_step() const;
|
||||
Dictionary ev_continued() const;
|
||||
Dictionary ev_output(const String &p_message, RemoteDebugger::MessageType p_type) const;
|
||||
Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const;
|
||||
Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const;
|
||||
};
|
||||
|
||||
#endif // DEBUG_ADAPTER_PARSER_H
|
||||
1062
engine/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
Normal file
1062
engine/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
Normal file
File diff suppressed because it is too large
Load diff
156
engine/editor/debugger/debug_adapter/debug_adapter_protocol.h
Normal file
156
engine/editor/debugger/debug_adapter/debug_adapter_protocol.h
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_protocol.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DEBUG_ADAPTER_PROTOCOL_H
|
||||
#define DEBUG_ADAPTER_PROTOCOL_H
|
||||
|
||||
#include "core/io/stream_peer.h"
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
|
||||
#include "debug_adapter_parser.h"
|
||||
#include "debug_adapter_types.h"
|
||||
|
||||
#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
|
||||
#define DAP_MAX_CLIENTS 8
|
||||
|
||||
class DebugAdapterParser;
|
||||
|
||||
struct DAPeer : RefCounted {
|
||||
Ref<StreamPeerTCP> connection;
|
||||
|
||||
uint8_t req_buf[DAP_MAX_BUFFER_SIZE];
|
||||
int req_pos = 0;
|
||||
bool has_header = false;
|
||||
int content_length = 0;
|
||||
List<Dictionary> res_queue;
|
||||
int seq = 0;
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
// Client specific info
|
||||
bool linesStartAt1 = false;
|
||||
bool columnsStartAt1 = false;
|
||||
bool supportsVariableType = false;
|
||||
bool supportsInvalidatedEvent = false;
|
||||
bool supportsCustomData = false;
|
||||
|
||||
// Internal client info
|
||||
bool attached = false;
|
||||
Dictionary pending_launch;
|
||||
|
||||
Error handle_data();
|
||||
Error send_data();
|
||||
String format_output(const Dictionary &p_params) const;
|
||||
};
|
||||
|
||||
class DebugAdapterProtocol : public Object {
|
||||
GDCLASS(DebugAdapterProtocol, Object)
|
||||
|
||||
friend class DebugAdapterParser;
|
||||
|
||||
private:
|
||||
static DebugAdapterProtocol *singleton;
|
||||
DebugAdapterParser *parser = nullptr;
|
||||
|
||||
List<Ref<DAPeer>> clients;
|
||||
Ref<TCPServer> server;
|
||||
|
||||
Error on_client_connected();
|
||||
void on_client_disconnected(const Ref<DAPeer> &p_peer);
|
||||
void on_debug_paused();
|
||||
void on_debug_stopped();
|
||||
void on_debug_output(const String &p_message, int p_type);
|
||||
void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump);
|
||||
void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled);
|
||||
void on_debug_stack_dump(const Array &p_stack_dump);
|
||||
void on_debug_stack_frame_vars(const int &p_size);
|
||||
void on_debug_stack_frame_var(const Array &p_data);
|
||||
void on_debug_data(const String &p_msg, const Array &p_data);
|
||||
|
||||
void reset_current_info();
|
||||
void reset_ids();
|
||||
void reset_stack_info();
|
||||
|
||||
int parse_variant(const Variant &p_var);
|
||||
|
||||
bool _initialized = false;
|
||||
bool _processing_breakpoint = false;
|
||||
bool _stepping = false;
|
||||
bool _processing_stackdump = false;
|
||||
int _remaining_vars = 0;
|
||||
int _current_frame = 0;
|
||||
uint64_t _request_timeout = 1000;
|
||||
bool _sync_breakpoints = false;
|
||||
|
||||
String _current_request;
|
||||
Ref<DAPeer> _current_peer;
|
||||
|
||||
int breakpoint_id = 0;
|
||||
int stackframe_id = 0;
|
||||
int variable_id = 0;
|
||||
List<DAP::Breakpoint> breakpoint_list;
|
||||
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
|
||||
HashMap<int, Array> variable_list;
|
||||
|
||||
public:
|
||||
friend class DebugAdapterServer;
|
||||
|
||||
_FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; }
|
||||
_FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; }
|
||||
|
||||
bool process_message(const String &p_text);
|
||||
|
||||
String get_current_request() const { return _current_request; }
|
||||
Ref<DAPeer> get_current_peer() const { return _current_peer; }
|
||||
|
||||
void notify_initialized();
|
||||
void notify_process();
|
||||
void notify_terminated();
|
||||
void notify_exited(const int &p_exitcode = 0);
|
||||
void notify_stopped_paused();
|
||||
void notify_stopped_exception(const String &p_error);
|
||||
void notify_stopped_breakpoint(const int &p_id);
|
||||
void notify_stopped_step();
|
||||
void notify_continued();
|
||||
void notify_output(const String &p_message, RemoteDebugger::MessageType p_type);
|
||||
void notify_custom_data(const String &p_msg, const Array &p_data);
|
||||
void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled);
|
||||
|
||||
Array update_breakpoints(const String &p_path, const Array &p_lines);
|
||||
|
||||
void poll();
|
||||
Error start(int p_port, const IPAddress &p_bind_ip);
|
||||
void stop();
|
||||
|
||||
DebugAdapterProtocol();
|
||||
~DebugAdapterProtocol();
|
||||
};
|
||||
|
||||
#endif // DEBUG_ADAPTER_PROTOCOL_H
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_server.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 "debug_adapter_server.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "editor/editor_log.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
int DebugAdapterServer::port_override = -1;
|
||||
|
||||
DebugAdapterServer::DebugAdapterServer() {
|
||||
_EDITOR_DEF("network/debug_adapter/remote_port", remote_port);
|
||||
_EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout);
|
||||
_EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints);
|
||||
}
|
||||
|
||||
void DebugAdapterServer::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
start();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
stop();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
// The main loop can be run again during request processing, which modifies internal state of the protocol.
|
||||
// Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished.
|
||||
if (started && !polling) {
|
||||
polling = true;
|
||||
protocol.poll();
|
||||
polling = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
||||
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("network/debug_adapter")) {
|
||||
break;
|
||||
}
|
||||
protocol._request_timeout = EDITOR_GET("network/debug_adapter/request_timeout");
|
||||
protocol._sync_breakpoints = EDITOR_GET("network/debug_adapter/sync_breakpoints");
|
||||
int port = (DebugAdapterServer::port_override > -1) ? DebugAdapterServer::port_override : (int)_EDITOR_GET("network/debug_adapter/remote_port");
|
||||
if (port != remote_port) {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugAdapterServer::start() {
|
||||
remote_port = (DebugAdapterServer::port_override > -1) ? DebugAdapterServer::port_override : (int)_EDITOR_GET("network/debug_adapter/remote_port");
|
||||
if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) {
|
||||
EditorNode::get_log()->add_message("--- Debug adapter server started on port " + itos(remote_port) + " ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
set_process_internal(true);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugAdapterServer::stop() {
|
||||
protocol.stop();
|
||||
started = false;
|
||||
EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
}
|
||||
58
engine/editor/debugger/debug_adapter/debug_adapter_server.h
Normal file
58
engine/editor/debugger/debug_adapter/debug_adapter_server.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DEBUG_ADAPTER_SERVER_H
|
||||
#define DEBUG_ADAPTER_SERVER_H
|
||||
|
||||
#include "debug_adapter_protocol.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
||||
class DebugAdapterServer : public EditorPlugin {
|
||||
GDCLASS(DebugAdapterServer, EditorPlugin);
|
||||
|
||||
DebugAdapterProtocol protocol;
|
||||
|
||||
int remote_port = 6006;
|
||||
bool thread_running = false;
|
||||
bool started = false;
|
||||
bool polling = false;
|
||||
static void thread_func(void *p_userdata);
|
||||
|
||||
private:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static int port_override;
|
||||
DebugAdapterServer();
|
||||
void start();
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // DEBUG_ADAPTER_SERVER_H
|
||||
281
engine/editor/debugger/debug_adapter/debug_adapter_types.h
Normal file
281
engine/editor/debugger/debug_adapter/debug_adapter_types.h
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/**************************************************************************/
|
||||
/* debug_adapter_types.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DEBUG_ADAPTER_TYPES_H
|
||||
#define DEBUG_ADAPTER_TYPES_H
|
||||
|
||||
#include "core/io/json.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
|
||||
namespace DAP {
|
||||
|
||||
enum ErrorType {
|
||||
UNKNOWN,
|
||||
WRONG_PATH,
|
||||
NOT_RUNNING,
|
||||
TIMEOUT,
|
||||
UNKNOWN_PLATFORM,
|
||||
MISSING_DEVICE
|
||||
};
|
||||
|
||||
struct Checksum {
|
||||
String algorithm;
|
||||
String checksum;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["algorithm"] = algorithm;
|
||||
dict["checksum"] = checksum;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Source {
|
||||
private:
|
||||
Array _checksums;
|
||||
|
||||
public:
|
||||
String name;
|
||||
String path;
|
||||
|
||||
void compute_checksums() {
|
||||
ERR_FAIL_COND(path.is_empty());
|
||||
|
||||
// MD5
|
||||
Checksum md5;
|
||||
md5.algorithm = "MD5";
|
||||
md5.checksum = FileAccess::get_md5(path);
|
||||
|
||||
// SHA-256
|
||||
Checksum sha256;
|
||||
sha256.algorithm = "SHA256";
|
||||
sha256.checksum = FileAccess::get_sha256(path);
|
||||
|
||||
_checksums.push_back(md5.to_json());
|
||||
_checksums.push_back(sha256.to_json());
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void from_json(const Dictionary &p_params) {
|
||||
name = p_params["name"];
|
||||
path = p_params["path"];
|
||||
_checksums = p_params["checksums"];
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["name"] = name;
|
||||
dict["path"] = path;
|
||||
dict["checksums"] = _checksums;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Breakpoint {
|
||||
int id = 0;
|
||||
bool verified = false;
|
||||
Source source;
|
||||
int line = 0;
|
||||
|
||||
bool operator==(const Breakpoint &p_other) const {
|
||||
return source.path == p_other.source.path && line == p_other.line;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["id"] = id;
|
||||
dict["verified"] = verified;
|
||||
dict["source"] = source.to_json();
|
||||
dict["line"] = line;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct BreakpointLocation {
|
||||
int line = 0;
|
||||
int endLine = -1;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["line"] = line;
|
||||
if (endLine >= 0) {
|
||||
dict["endLine"] = endLine;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Capabilities {
|
||||
bool supportsConfigurationDoneRequest = true;
|
||||
bool supportsEvaluateForHovers = true;
|
||||
bool supportsSetVariable = true;
|
||||
String supportedChecksumAlgorithms[2] = { "MD5", "SHA256" };
|
||||
bool supportsRestartRequest = true;
|
||||
bool supportsValueFormattingOptions = true;
|
||||
bool supportTerminateDebuggee = true;
|
||||
bool supportSuspendDebuggee = true;
|
||||
bool supportsTerminateRequest = true;
|
||||
bool supportsBreakpointLocationsRequest = true;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["supportsConfigurationDoneRequest"] = supportsConfigurationDoneRequest;
|
||||
dict["supportsEvaluateForHovers"] = supportsEvaluateForHovers;
|
||||
dict["supportsSetVariable"] = supportsSetVariable;
|
||||
dict["supportsRestartRequest"] = supportsRestartRequest;
|
||||
dict["supportsValueFormattingOptions"] = supportsValueFormattingOptions;
|
||||
dict["supportTerminateDebuggee"] = supportTerminateDebuggee;
|
||||
dict["supportSuspendDebuggee"] = supportSuspendDebuggee;
|
||||
dict["supportsTerminateRequest"] = supportsTerminateRequest;
|
||||
dict["supportsBreakpointLocationsRequest"] = supportsBreakpointLocationsRequest;
|
||||
|
||||
Array arr;
|
||||
arr.push_back(supportedChecksumAlgorithms[0]);
|
||||
arr.push_back(supportedChecksumAlgorithms[1]);
|
||||
dict["supportedChecksumAlgorithms"] = arr;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Message {
|
||||
int id = 0;
|
||||
String format;
|
||||
bool sendTelemetry = false; // Just in case :)
|
||||
bool showUser = false;
|
||||
Dictionary variables;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["id"] = id;
|
||||
dict["format"] = format;
|
||||
dict["sendTelemetry"] = sendTelemetry;
|
||||
dict["showUser"] = showUser;
|
||||
dict["variables"] = variables;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Scope {
|
||||
String name;
|
||||
String presentationHint;
|
||||
int variablesReference = 0;
|
||||
bool expensive = false;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["name"] = name;
|
||||
dict["presentationHint"] = presentationHint;
|
||||
dict["variablesReference"] = variablesReference;
|
||||
dict["expensive"] = expensive;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct SourceBreakpoint {
|
||||
int line = 0;
|
||||
|
||||
_FORCE_INLINE_ void from_json(const Dictionary &p_params) {
|
||||
line = p_params["line"];
|
||||
}
|
||||
};
|
||||
|
||||
struct StackFrame {
|
||||
int id = 0;
|
||||
String name;
|
||||
Source source;
|
||||
int line = 0;
|
||||
int column = 0;
|
||||
|
||||
static uint32_t hash(const StackFrame &p_frame) {
|
||||
return hash_murmur3_one_32(p_frame.id);
|
||||
}
|
||||
bool operator==(const StackFrame &p_other) const {
|
||||
return id == p_other.id;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void from_json(const Dictionary &p_params) {
|
||||
id = p_params["id"];
|
||||
name = p_params["name"];
|
||||
source.from_json(p_params["source"]);
|
||||
line = p_params["line"];
|
||||
column = p_params["column"];
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["id"] = id;
|
||||
dict["name"] = name;
|
||||
dict["source"] = source.to_json();
|
||||
dict["line"] = line;
|
||||
dict["column"] = column;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Thread {
|
||||
int id = 0;
|
||||
String name;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["id"] = id;
|
||||
dict["name"] = name;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
struct Variable {
|
||||
String name;
|
||||
String value;
|
||||
String type;
|
||||
int variablesReference = 0;
|
||||
|
||||
_FORCE_INLINE_ Dictionary to_json() const {
|
||||
Dictionary dict;
|
||||
dict["name"] = name;
|
||||
dict["value"] = value;
|
||||
dict["type"] = type;
|
||||
dict["variablesReference"] = variablesReference;
|
||||
|
||||
return dict;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace DAP
|
||||
|
||||
#endif // DEBUG_ADAPTER_TYPES_H
|
||||
287
engine/editor/debugger/editor_debugger_inspector.cpp
Normal file
287
engine/editor/debugger/editor_debugger_inspector.cpp
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_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_debugger_inspector.h"
|
||||
|
||||
#include "core/debugger/debugger_marshalls.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "scene/debugger/scene_debugger.h"
|
||||
|
||||
bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p_value) {
|
||||
if (!prop_values.has(p_name) || String(p_name).begins_with("Constants/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prop_values[p_name] = p_value;
|
||||
emit_signal(SNAME("value_edited"), remote_object_id, p_name, p_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (!prop_values.has(p_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r_ret = prop_values[p_name];
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->clear(); // Sorry, no want category.
|
||||
for (const PropertyInfo &prop : prop_list) {
|
||||
if (prop.name == "script") {
|
||||
// Skip the script property, it's always added by the non-virtual method.
|
||||
continue;
|
||||
}
|
||||
|
||||
p_list->push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
String EditorDebuggerRemoteObject::get_title() {
|
||||
if (remote_object_id.is_valid()) {
|
||||
return vformat(TTR("Remote %s:"), String(type_name)) + " " + itos(remote_object_id);
|
||||
} else {
|
||||
return "<null>";
|
||||
}
|
||||
}
|
||||
|
||||
Variant EditorDebuggerRemoteObject::get_variant(const StringName &p_name) {
|
||||
Variant var;
|
||||
_get(p_name, var);
|
||||
return var;
|
||||
}
|
||||
|
||||
void EditorDebuggerRemoteObject::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObject::get_title);
|
||||
ClassDB::bind_method(D_METHOD("get_variant"), &EditorDebuggerRemoteObject::get_variant);
|
||||
ClassDB::bind_method(D_METHOD("clear"), &EditorDebuggerRemoteObject::clear);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_object_id"), &EditorDebuggerRemoteObject::get_remote_object_id);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("value_edited", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value")));
|
||||
}
|
||||
|
||||
EditorDebuggerInspector::EditorDebuggerInspector() {
|
||||
variables = memnew(EditorDebuggerRemoteObject);
|
||||
}
|
||||
|
||||
EditorDebuggerInspector::~EditorDebuggerInspector() {
|
||||
clear_cache();
|
||||
memdelete(variables);
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id")));
|
||||
ADD_SIGNAL(MethodInfo("object_edited", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value")));
|
||||
ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
connect("object_id_selected", callable_mp(this, &EditorDebuggerInspector::_object_selected));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
edit(variables);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) {
|
||||
emit_signal(SNAME("object_edited"), p_id, p_prop, p_value);
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::_object_selected(ObjectID p_object) {
|
||||
emit_signal(SNAME("object_selected"), p_object);
|
||||
}
|
||||
|
||||
ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
|
||||
EditorDebuggerRemoteObject *debug_obj = nullptr;
|
||||
|
||||
SceneDebuggerObject obj;
|
||||
obj.deserialize(p_arr);
|
||||
ERR_FAIL_COND_V(obj.id.is_null(), ObjectID());
|
||||
|
||||
if (remote_objects.has(obj.id)) {
|
||||
debug_obj = remote_objects[obj.id];
|
||||
} else {
|
||||
debug_obj = memnew(EditorDebuggerRemoteObject);
|
||||
debug_obj->remote_object_id = obj.id;
|
||||
debug_obj->type_name = obj.class_name;
|
||||
remote_objects[obj.id] = debug_obj;
|
||||
debug_obj->connect("value_edited", callable_mp(this, &EditorDebuggerInspector::_object_edited));
|
||||
}
|
||||
|
||||
int old_prop_size = debug_obj->prop_list.size();
|
||||
|
||||
debug_obj->prop_list.clear();
|
||||
int new_props_added = 0;
|
||||
HashSet<String> changed;
|
||||
for (SceneDebuggerObject::SceneDebuggerProperty &property : obj.properties) {
|
||||
PropertyInfo &pinfo = property.first;
|
||||
Variant &var = property.second;
|
||||
|
||||
if (pinfo.type == Variant::OBJECT) {
|
||||
if (var.get_type() == Variant::STRING) {
|
||||
String path = var;
|
||||
if (path.contains("::")) {
|
||||
// built-in resource
|
||||
String base_path = path.get_slice("::", 0);
|
||||
Ref<Resource> dependency = ResourceLoader::load(base_path);
|
||||
if (dependency.is_valid()) {
|
||||
remote_dependencies.insert(dependency);
|
||||
}
|
||||
}
|
||||
var = ResourceLoader::load(path);
|
||||
|
||||
if (pinfo.hint_string == "Script") {
|
||||
if (debug_obj->get_script() != var) {
|
||||
debug_obj->set_script(Ref<RefCounted>());
|
||||
Ref<Script> scr(var);
|
||||
if (!scr.is_null()) {
|
||||
ScriptInstance *scr_instance = scr->placeholder_instance_create(debug_obj);
|
||||
if (scr_instance) {
|
||||
debug_obj->set_script_and_instance(var, scr_instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//always add the property, since props may have been added or removed
|
||||
debug_obj->prop_list.push_back(pinfo);
|
||||
|
||||
if (!debug_obj->prop_values.has(pinfo.name)) {
|
||||
new_props_added++;
|
||||
debug_obj->prop_values[pinfo.name] = var;
|
||||
} else {
|
||||
if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debug_obj->prop_values[pinfo.name], var))) {
|
||||
debug_obj->prop_values[pinfo.name] = var;
|
||||
changed.insert(pinfo.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old_prop_size == debug_obj->prop_list.size() && new_props_added == 0) {
|
||||
//only some may have changed, if so, then update those, if exist
|
||||
for (const String &E : changed) {
|
||||
emit_signal(SNAME("object_property_updated"), debug_obj->remote_object_id, E);
|
||||
}
|
||||
} else {
|
||||
//full update, because props were added or removed
|
||||
debug_obj->update();
|
||||
}
|
||||
return obj.id;
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::clear_cache() {
|
||||
for (const KeyValue<ObjectID, EditorDebuggerRemoteObject *> &E : remote_objects) {
|
||||
EditorNode *editor = EditorNode::get_singleton();
|
||||
if (editor->get_editor_selection_history()->get_current() == E.value->get_instance_id()) {
|
||||
editor->push_item(nullptr);
|
||||
}
|
||||
memdelete(E.value);
|
||||
}
|
||||
remote_objects.clear();
|
||||
remote_dependencies.clear();
|
||||
}
|
||||
|
||||
Object *EditorDebuggerInspector::get_object(ObjectID p_id) {
|
||||
if (remote_objects.has(p_id)) {
|
||||
return remote_objects[p_id];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
|
||||
DebuggerMarshalls::ScriptStackVariable var;
|
||||
var.deserialize(p_array);
|
||||
String n = var.name;
|
||||
Variant v = var.value;
|
||||
|
||||
PropertyHint h = PROPERTY_HINT_NONE;
|
||||
String hs;
|
||||
|
||||
if (var.var_type == Variant::OBJECT && v) {
|
||||
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
|
||||
h = PROPERTY_HINT_OBJECT_ID;
|
||||
hs = "Object";
|
||||
}
|
||||
String type;
|
||||
switch (var.type) {
|
||||
case 0:
|
||||
type = "Locals/";
|
||||
break;
|
||||
case 1:
|
||||
type = "Members/";
|
||||
break;
|
||||
case 2:
|
||||
type = "Globals/";
|
||||
break;
|
||||
default:
|
||||
type = "Unknown/";
|
||||
}
|
||||
|
||||
PropertyInfo pinfo;
|
||||
pinfo.name = type + n;
|
||||
pinfo.type = v.get_type();
|
||||
pinfo.hint = h;
|
||||
pinfo.hint_string = hs;
|
||||
|
||||
variables->prop_list.push_back(pinfo);
|
||||
variables->prop_values[type + n] = v;
|
||||
variables->update();
|
||||
edit(variables);
|
||||
|
||||
// To prevent constantly resizing when using filtering.
|
||||
int size_x = get_size().x;
|
||||
if (size_x > get_custom_minimum_size().x) {
|
||||
set_custom_minimum_size(Size2(size_x, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerInspector::clear_stack_variables() {
|
||||
variables->clear();
|
||||
variables->update();
|
||||
set_custom_minimum_size(Size2(0, 0));
|
||||
}
|
||||
|
||||
String EditorDebuggerInspector::get_stack_variable(const String &p_var) {
|
||||
for (KeyValue<StringName, Variant> &E : variables->prop_values) {
|
||||
String v = E.key.operator String();
|
||||
if (v.get_slice("/", 1) == p_var) {
|
||||
return variables->get_variant(v);
|
||||
}
|
||||
}
|
||||
return String();
|
||||
}
|
||||
97
engine/editor/debugger/editor_debugger_inspector.h
Normal file
97
engine/editor/debugger/editor_debugger_inspector.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_INSPECTOR_H
|
||||
#define EDITOR_DEBUGGER_INSPECTOR_H
|
||||
|
||||
#include "editor/editor_inspector.h"
|
||||
|
||||
class EditorDebuggerRemoteObject : public Object {
|
||||
GDCLASS(EditorDebuggerRemoteObject, Object);
|
||||
|
||||
protected:
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
ObjectID remote_object_id;
|
||||
String type_name;
|
||||
List<PropertyInfo> prop_list;
|
||||
HashMap<StringName, Variant> prop_values;
|
||||
|
||||
ObjectID get_remote_object_id() { return remote_object_id; };
|
||||
String get_title();
|
||||
|
||||
Variant get_variant(const StringName &p_name);
|
||||
|
||||
void clear() {
|
||||
prop_list.clear();
|
||||
prop_values.clear();
|
||||
}
|
||||
|
||||
void update() { notify_property_list_changed(); }
|
||||
|
||||
EditorDebuggerRemoteObject() {}
|
||||
};
|
||||
|
||||
class EditorDebuggerInspector : public EditorInspector {
|
||||
GDCLASS(EditorDebuggerInspector, EditorInspector);
|
||||
|
||||
private:
|
||||
ObjectID inspected_object_id;
|
||||
HashMap<ObjectID, EditorDebuggerRemoteObject *> remote_objects;
|
||||
HashSet<Ref<Resource>> remote_dependencies;
|
||||
EditorDebuggerRemoteObject *variables = nullptr;
|
||||
|
||||
void _object_selected(ObjectID p_object);
|
||||
void _object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EditorDebuggerInspector();
|
||||
~EditorDebuggerInspector();
|
||||
|
||||
// Remote Object cache
|
||||
ObjectID add_object(const Array &p_arr);
|
||||
Object *get_object(ObjectID p_id);
|
||||
void clear_cache();
|
||||
|
||||
// Stack Dump variables
|
||||
String get_stack_variable(const String &p_var);
|
||||
void add_stack_variable(const Array &p_arr);
|
||||
void clear_stack_variables();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DEBUGGER_INSPECTOR_H
|
||||
839
engine/editor/debugger/editor_debugger_node.cpp
Normal file
839
engine/editor/debugger/editor_debugger_node.cpp
Normal file
|
|
@ -0,0 +1,839 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_node.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_debugger_node.h"
|
||||
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "editor/debugger/editor_debugger_tree.h"
|
||||
#include "editor/debugger/script_editor_debugger.h"
|
||||
#include "editor/editor_log.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_bottom_panel.h"
|
||||
#include "editor/gui/editor_run_bar.h"
|
||||
#include "editor/inspector_dock.h"
|
||||
#include "editor/plugins/editor_debugger_plugin.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
#include "editor/scene_tree_dock.h"
|
||||
#include "editor/themes/editor_theme_manager.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
template <typename Func>
|
||||
void _for_all(TabContainer *p_node, const Func &p_func) {
|
||||
for (int i = 0; i < p_node->get_tab_count(); i++) {
|
||||
ScriptEditorDebugger *dbg = Object::cast_to<ScriptEditorDebugger>(p_node->get_tab_control(i));
|
||||
ERR_FAIL_NULL(dbg);
|
||||
p_func(dbg);
|
||||
}
|
||||
}
|
||||
|
||||
EditorDebuggerNode *EditorDebuggerNode::singleton = nullptr;
|
||||
|
||||
EditorDebuggerNode::EditorDebuggerNode() {
|
||||
if (!singleton) {
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_LEFT));
|
||||
add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_RIGHT));
|
||||
|
||||
tabs = memnew(TabContainer);
|
||||
tabs->set_tabs_visible(false);
|
||||
tabs->connect("tab_changed", callable_mp(this, &EditorDebuggerNode::_debugger_changed));
|
||||
add_child(tabs);
|
||||
|
||||
Ref<StyleBoxEmpty> empty;
|
||||
empty.instantiate();
|
||||
tabs->add_theme_style_override(SceneStringName(panel), empty);
|
||||
|
||||
auto_switch_remote_scene_tree = EDITOR_GET("debugger/auto_switch_to_remote_scene_tree");
|
||||
_add_debugger();
|
||||
|
||||
// Remote scene tree
|
||||
remote_scene_tree = memnew(EditorDebuggerTree);
|
||||
remote_scene_tree->connect("object_selected", callable_mp(this, &EditorDebuggerNode::_remote_object_requested));
|
||||
remote_scene_tree->connect("save_node", callable_mp(this, &EditorDebuggerNode::_save_node_requested));
|
||||
remote_scene_tree->connect("button_clicked", callable_mp(this, &EditorDebuggerNode::_remote_tree_button_pressed));
|
||||
SceneTreeDock::get_singleton()->add_remote_tree_editor(remote_scene_tree);
|
||||
SceneTreeDock::get_singleton()->connect("remote_tree_selected", callable_mp(this, &EditorDebuggerNode::request_remote_tree));
|
||||
|
||||
remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval");
|
||||
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
|
||||
|
||||
EditorRunBar::get_singleton()->get_pause_button()->connect(SceneStringName(pressed), callable_mp(this, &EditorDebuggerNode::_paused));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
|
||||
ScriptEditorDebugger *node = memnew(ScriptEditorDebugger);
|
||||
|
||||
int id = tabs->get_tab_count();
|
||||
node->connect("stop_requested", callable_mp(this, &EditorDebuggerNode::_debugger_wants_stop).bind(id));
|
||||
node->connect("stopped", callable_mp(this, &EditorDebuggerNode::_debugger_stopped).bind(id));
|
||||
node->connect("stack_frame_selected", callable_mp(this, &EditorDebuggerNode::_stack_frame_selected).bind(id));
|
||||
node->connect("error_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id));
|
||||
node->connect("breakpoint_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id));
|
||||
node->connect("clear_execution", callable_mp(this, &EditorDebuggerNode::_clear_execution));
|
||||
node->connect("breaked", callable_mp(this, &EditorDebuggerNode::_breaked).bind(id));
|
||||
node->connect("remote_tree_updated", callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id));
|
||||
node->connect("remote_object_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_updated).bind(id));
|
||||
node->connect("remote_object_property_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id));
|
||||
node->connect("remote_object_requested", callable_mp(this, &EditorDebuggerNode::_remote_object_requested).bind(id));
|
||||
node->connect("set_breakpoint", callable_mp(this, &EditorDebuggerNode::_breakpoint_set_in_tree).bind(id));
|
||||
node->connect("clear_breakpoints", callable_mp(this, &EditorDebuggerNode::_breakpoints_cleared_in_tree).bind(id));
|
||||
node->connect("errors_cleared", callable_mp(this, &EditorDebuggerNode::_update_errors));
|
||||
|
||||
if (tabs->get_tab_count() > 0) {
|
||||
get_debugger(0)->clear_style();
|
||||
}
|
||||
|
||||
tabs->add_child(node);
|
||||
|
||||
node->set_name("Session " + itos(tabs->get_tab_count()));
|
||||
if (tabs->get_tab_count() > 1) {
|
||||
node->clear_style();
|
||||
tabs->set_tabs_visible(true);
|
||||
tabs->add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("DebuggerPanel"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
if (!debugger_plugins.is_empty()) {
|
||||
for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) {
|
||||
plugin->create_session(node);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_stack_frame_selected(int p_debugger) {
|
||||
const ScriptEditorDebugger *dbg = get_debugger(p_debugger);
|
||||
ERR_FAIL_NULL(dbg);
|
||||
if (dbg != get_current_debugger()) {
|
||||
return;
|
||||
}
|
||||
_text_editor_stack_goto(dbg);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) {
|
||||
Ref<Script> s = ResourceLoader::load(p_file);
|
||||
emit_signal(SNAME("goto_script_line"), s, p_line - 1);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_debugger) {
|
||||
String file = p_debugger->get_stack_script_file();
|
||||
if (file.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (file.is_resource_file()) {
|
||||
stack_script = ResourceLoader::load(file);
|
||||
} else {
|
||||
// If the script is built-in, it can be opened only if the scene is loaded in memory.
|
||||
int i = file.find("::");
|
||||
int j = file.rfind("(", i);
|
||||
if (j > -1) { // If the script is named, the string is "name (file)", so we need to extract the path.
|
||||
file = file.substr(j + 1, file.find(")", i) - j - 1);
|
||||
}
|
||||
Ref<PackedScene> ps = ResourceLoader::load(file.get_slice("::", 0));
|
||||
stack_script = ResourceLoader::load(file);
|
||||
}
|
||||
const int line = p_debugger->get_stack_script_line() - 1;
|
||||
emit_signal(SNAME("goto_script_line"), stack_script, line);
|
||||
emit_signal(SNAME("set_execution"), stack_script, line);
|
||||
stack_script.unref(); // Why?!?
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_text_editor_stack_clear(const ScriptEditorDebugger *p_debugger) {
|
||||
String file = p_debugger->get_stack_script_file();
|
||||
if (file.is_empty()) {
|
||||
return;
|
||||
}
|
||||
if (file.is_resource_file()) {
|
||||
stack_script = ResourceLoader::load(file);
|
||||
} else {
|
||||
// If the script is built-in, it can be opened only if the scene is loaded in memory.
|
||||
int i = file.find("::");
|
||||
int j = file.rfind("(", i);
|
||||
if (j > -1) { // If the script is named, the string is "name (file)", so we need to extract the path.
|
||||
file = file.substr(j + 1, file.find(")", i) - j - 1);
|
||||
}
|
||||
Ref<PackedScene> ps = ResourceLoader::load(file.get_slice("::", 0));
|
||||
stack_script = ResourceLoader::load(file);
|
||||
}
|
||||
emit_signal(SNAME("clear_execution"), stack_script);
|
||||
stack_script.unref(); // Why?!?
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_bind_methods() {
|
||||
// LiveDebug.
|
||||
ClassDB::bind_method("live_debug_create_node", &EditorDebuggerNode::live_debug_create_node);
|
||||
ClassDB::bind_method("live_debug_instantiate_node", &EditorDebuggerNode::live_debug_instantiate_node);
|
||||
ClassDB::bind_method("live_debug_remove_node", &EditorDebuggerNode::live_debug_remove_node);
|
||||
ClassDB::bind_method("live_debug_remove_and_keep_node", &EditorDebuggerNode::live_debug_remove_and_keep_node);
|
||||
ClassDB::bind_method("live_debug_restore_node", &EditorDebuggerNode::live_debug_restore_node);
|
||||
ClassDB::bind_method("live_debug_duplicate_node", &EditorDebuggerNode::live_debug_duplicate_node);
|
||||
ClassDB::bind_method("live_debug_reparent_node", &EditorDebuggerNode::live_debug_reparent_node);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("goto_script_line"));
|
||||
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
|
||||
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
|
||||
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug")));
|
||||
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
|
||||
ADD_SIGNAL(MethodInfo("breakpoint_set_in_tree", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled"), PropertyInfo(Variant::INT, "debugger")));
|
||||
ADD_SIGNAL(MethodInfo("breakpoints_cleared_in_tree", PropertyInfo(Variant::INT, "debugger")));
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::register_undo_redo(UndoRedo *p_undo_redo) {
|
||||
p_undo_redo->set_method_notify_callback(_method_changeds, this);
|
||||
p_undo_redo->set_property_notify_callback(_property_changeds, this);
|
||||
}
|
||||
|
||||
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
|
||||
return Object::cast_to<EditorDebuggerRemoteObject>(ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_current()));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(p_id));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_previous_debugger() const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(tabs->get_previous_tab()));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_current_debugger() const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(tabs->get_current_tab()));
|
||||
}
|
||||
|
||||
ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const {
|
||||
return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(0));
|
||||
}
|
||||
|
||||
String EditorDebuggerNode::get_server_uri() const {
|
||||
ERR_FAIL_COND_V(server.is_null(), "");
|
||||
return server->get_uri();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_keep_open(bool p_keep_open) {
|
||||
keep_open = p_keep_open;
|
||||
if (keep_open) {
|
||||
if (server.is_null() || !server->is_active()) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
bool found = false;
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *p_debugger) {
|
||||
if (p_debugger->is_session_active()) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error EditorDebuggerNode::start(const String &p_uri) {
|
||||
ERR_FAIL_COND_V(!p_uri.contains("://"), ERR_INVALID_PARAMETER);
|
||||
if (keep_open && current_uri == p_uri && server.is_valid()) {
|
||||
return OK;
|
||||
}
|
||||
stop(true);
|
||||
current_uri = p_uri;
|
||||
|
||||
server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://") + 3)));
|
||||
const Error err = server->start(p_uri);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
set_process(true);
|
||||
EditorNode::get_log()->add_message("--- Debugging process started ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::stop(bool p_force) {
|
||||
if (keep_open && !p_force) {
|
||||
return;
|
||||
}
|
||||
current_uri.clear();
|
||||
if (server.is_valid()) {
|
||||
server->stop();
|
||||
EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR);
|
||||
|
||||
if (EditorRunBar::get_singleton()->is_movie_maker_enabled()) {
|
||||
// Request attention in case the user was doing something else when movie recording is finished.
|
||||
DisplayServer::get_singleton()->window_request_attention();
|
||||
}
|
||||
|
||||
server.unref();
|
||||
}
|
||||
// Also close all debugging sessions.
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
if (dbg->is_session_active()) {
|
||||
dbg->_stop_and_notify();
|
||||
}
|
||||
});
|
||||
_break_state_changed();
|
||||
breakpoints.clear();
|
||||
EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY);
|
||||
set_process(false);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
||||
if (!EditorThemeManager::is_generated_theme_outdated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tabs->get_tab_count() > 1) {
|
||||
add_theme_constant_override("margin_left", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_LEFT));
|
||||
add_theme_constant_override("margin_right", -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles))->get_margin(SIDE_RIGHT));
|
||||
|
||||
tabs->add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("DebuggerPanel"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
remote_scene_tree->update_icon_max_width();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
_update_debug_options();
|
||||
initializing = false;
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_PROCESS: {
|
||||
if (!server.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server->is_active()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
server->poll();
|
||||
|
||||
_update_errors();
|
||||
|
||||
// Remote scene tree update
|
||||
remote_scene_tree_timeout -= get_process_delta_time();
|
||||
if (remote_scene_tree_timeout < 0) {
|
||||
remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval");
|
||||
if (remote_scene_tree->is_visible_in_tree()) {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
}
|
||||
|
||||
// Remote inspector update
|
||||
inspect_edited_object_timeout -= get_process_delta_time();
|
||||
if (inspect_edited_object_timeout < 0) {
|
||||
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
get_current_debugger()->request_remote_object(obj->remote_object_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Take connections.
|
||||
if (server->is_connection_available()) {
|
||||
ScriptEditorDebugger *debugger = nullptr;
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
if (debugger || dbg->is_session_active()) {
|
||||
return;
|
||||
}
|
||||
debugger = dbg;
|
||||
});
|
||||
if (debugger == nullptr) {
|
||||
if (tabs->get_tab_count() <= 4) { // Max 4 debugging sessions active.
|
||||
debugger = _add_debugger();
|
||||
} else {
|
||||
// We already have too many sessions, disconnecting new clients to prevent them from hanging.
|
||||
server->take_connection()->close();
|
||||
return; // Can't add, stop here.
|
||||
}
|
||||
}
|
||||
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_disabled(false);
|
||||
// Switch to remote tree view if so desired.
|
||||
auto_switch_remote_scene_tree = (bool)EDITOR_GET("debugger/auto_switch_to_remote_scene_tree");
|
||||
if (auto_switch_remote_scene_tree) {
|
||||
SceneTreeDock::get_singleton()->show_remote_tree();
|
||||
}
|
||||
// Good to go.
|
||||
SceneTreeDock::get_singleton()->show_tab_buttons();
|
||||
debugger->set_editor_remote_tree(remote_scene_tree);
|
||||
debugger->start(server->take_connection());
|
||||
// Send breakpoints.
|
||||
for (const KeyValue<Breakpoint, bool> &E : breakpoints) {
|
||||
const Breakpoint &bp = E.key;
|
||||
debugger->set_breakpoint(bp.source, bp.line, E.value);
|
||||
} // Will arrive too late, how does the regular run work?
|
||||
|
||||
debugger->update_live_edit_root();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_update_errors() {
|
||||
int error_count = 0;
|
||||
int warning_count = 0;
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
error_count += dbg->get_error_count();
|
||||
warning_count += dbg->get_warning_count();
|
||||
});
|
||||
|
||||
if (error_count != last_error_count || warning_count != last_warning_count) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->update_tabs();
|
||||
});
|
||||
|
||||
if (error_count == 0 && warning_count == 0) {
|
||||
debugger_button->set_text(TTR("Debugger"));
|
||||
debugger_button->remove_theme_color_override(SceneStringName(font_color));
|
||||
debugger_button->set_icon(Ref<Texture2D>());
|
||||
} else {
|
||||
debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")");
|
||||
if (error_count >= 1 && warning_count >= 1) {
|
||||
debugger_button->set_icon(get_editor_theme_icon(SNAME("ErrorWarning")));
|
||||
// Use error color to represent the highest level of severity reported.
|
||||
debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
} else if (error_count >= 1) {
|
||||
debugger_button->set_icon(get_editor_theme_icon(SNAME("Error")));
|
||||
debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
} else {
|
||||
debugger_button->set_icon(get_editor_theme_icon(SNAME("Warning")));
|
||||
debugger_button->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
|
||||
}
|
||||
}
|
||||
last_error_count = error_count;
|
||||
last_warning_count = warning_count;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_stopped(int p_id) {
|
||||
ScriptEditorDebugger *dbg = get_debugger(p_id);
|
||||
ERR_FAIL_NULL(dbg);
|
||||
|
||||
bool found = false;
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *p_debugger) {
|
||||
if (p_debugger->is_session_active()) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false);
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_disabled(true);
|
||||
SceneTreeDock::get_singleton()->hide_remote_tree();
|
||||
SceneTreeDock::get_singleton()->hide_tab_buttons();
|
||||
EditorNode::get_singleton()->notify_all_debug_sessions_exited();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_wants_stop(int p_id) {
|
||||
// Ask editor to kill PID.
|
||||
int pid = get_debugger(p_id)->get_remote_pid();
|
||||
if (pid) {
|
||||
callable_mp(EditorNode::get_singleton(), &EditorNode::stop_child_process).call_deferred(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_debugger_changed(int p_tab) {
|
||||
if (get_inspected_remote_object()) {
|
||||
// Clear inspected object, you can only inspect objects in selected debugger.
|
||||
// Hopefully, in the future, we will have one inspector per debugger.
|
||||
EditorNode::get_singleton()->push_item(nullptr);
|
||||
}
|
||||
|
||||
if (get_previous_debugger()) {
|
||||
_text_editor_stack_clear(get_previous_debugger());
|
||||
}
|
||||
if (remote_scene_tree->is_visible_in_tree()) {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
if (get_current_debugger()->is_breaked()) {
|
||||
_text_editor_stack_goto(get_current_debugger());
|
||||
}
|
||||
|
||||
_break_state_changed();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_script_debug_button(MenuButton *p_button) {
|
||||
script_menu = p_button;
|
||||
script_menu->set_text(TTR("Debug"));
|
||||
script_menu->set_switch_on_hover(true);
|
||||
PopupMenu *p = script_menu->get_popup();
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/step_into"), DEBUG_STEP);
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/step_over"), DEBUG_NEXT);
|
||||
p->add_separator();
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/break"), DEBUG_BREAK);
|
||||
p->add_shortcut(ED_GET_SHORTCUT("debugger/continue"), DEBUG_CONTINUE);
|
||||
p->add_separator();
|
||||
p->add_check_shortcut(ED_GET_SHORTCUT("debugger/debug_with_external_editor"), DEBUG_WITH_EXTERNAL_EDITOR);
|
||||
p->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDebuggerNode::_menu_option));
|
||||
|
||||
_break_state_changed();
|
||||
script_menu->show();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_break_state_changed() {
|
||||
const bool breaked = get_current_debugger()->is_breaked();
|
||||
const bool can_debug = get_current_debugger()->is_debuggable();
|
||||
if (breaked) { // Show debugger.
|
||||
EditorNode::get_bottom_panel()->make_item_visible(this);
|
||||
}
|
||||
|
||||
// Update script menu.
|
||||
if (!script_menu) {
|
||||
return;
|
||||
}
|
||||
PopupMenu *p = script_menu->get_popup();
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_NEXT), !(breaked && can_debug));
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_STEP), !(breaked && can_debug));
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_BREAK), breaked);
|
||||
p->set_item_disabled(p->get_item_index(DEBUG_CONTINUE), !breaked);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_menu_option(int p_id) {
|
||||
switch (p_id) {
|
||||
case DEBUG_NEXT: {
|
||||
debug_next();
|
||||
} break;
|
||||
case DEBUG_STEP: {
|
||||
debug_step();
|
||||
} break;
|
||||
case DEBUG_BREAK: {
|
||||
debug_break();
|
||||
} break;
|
||||
case DEBUG_CONTINUE: {
|
||||
debug_continue();
|
||||
} break;
|
||||
case DEBUG_WITH_EXTERNAL_EDITOR: {
|
||||
bool ischecked = script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR));
|
||||
debug_with_external_editor = !ischecked;
|
||||
script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR), !ischecked);
|
||||
if (!initializing) {
|
||||
EditorSettings::get_singleton()->set_project_metadata("debug_options", "debug_with_external_editor", !ischecked);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_update_debug_options() {
|
||||
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "debug_with_external_editor", false).operator bool()) {
|
||||
_menu_option(DEBUG_WITH_EXTERNAL_EDITOR);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_paused() {
|
||||
const bool paused = EditorRunBar::get_singleton()->get_pause_button()->is_pressed();
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
if (paused && !dbg->is_breaked()) {
|
||||
dbg->debug_break();
|
||||
} else if (!paused && dbg->is_breaked()) {
|
||||
dbg->debug_continue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, const String &p_message, bool p_has_stackdump, int p_debugger) {
|
||||
if (get_current_debugger() != get_debugger(p_debugger)) {
|
||||
if (!p_breaked) {
|
||||
return;
|
||||
}
|
||||
tabs->set_current_tab(p_debugger);
|
||||
}
|
||||
_break_state_changed();
|
||||
EditorRunBar::get_singleton()->get_pause_button()->set_pressed(p_breaked);
|
||||
emit_signal(SNAME("breaked"), p_breaked, p_can_debug);
|
||||
}
|
||||
|
||||
bool EditorDebuggerNode::is_skip_breakpoints() const {
|
||||
return get_current_debugger()->is_skip_breakpoints();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p_enabled) {
|
||||
breakpoints[Breakpoint(p_path, p_line)] = p_enabled;
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->set_breakpoint(p_path, p_line, p_enabled);
|
||||
});
|
||||
|
||||
emit_signal(SNAME("breakpoint_toggled"), p_path, p_line, p_enabled);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_breakpoints(const String &p_path, const Array &p_lines) {
|
||||
for (int i = 0; i < p_lines.size(); i++) {
|
||||
set_breakpoint(p_path, p_lines[i], true);
|
||||
}
|
||||
|
||||
for (const KeyValue<Breakpoint, bool> &E : breakpoints) {
|
||||
Breakpoint b = E.key;
|
||||
if (b.source == p_path && !p_lines.has(b.line)) {
|
||||
set_breakpoint(p_path, b.line, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::reload_all_scripts() {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->reload_all_scripts();
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::reload_scripts(const Vector<String> &p_script_paths) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->reload_scripts(p_script_paths);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::debug_next() {
|
||||
get_current_debugger()->debug_next();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::debug_step() {
|
||||
get_current_debugger()->debug_step();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::debug_break() {
|
||||
get_current_debugger()->debug_break();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::debug_continue() {
|
||||
get_current_debugger()->debug_continue();
|
||||
}
|
||||
|
||||
String EditorDebuggerNode::get_var_value(const String &p_var) const {
|
||||
return get_current_debugger()->get_var_value(p_var);
|
||||
}
|
||||
|
||||
// LiveEdit/Inspector
|
||||
void EditorDebuggerNode::request_remote_tree() {
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_tree_updated(int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
remote_scene_tree->clear();
|
||||
remote_scene_tree->update_scene_tree(get_current_debugger()->get_remote_tree(), p_debugger);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
|
||||
if (p_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_item);
|
||||
ERR_FAIL_NULL(item);
|
||||
|
||||
if (p_id == EditorDebuggerTree::BUTTON_SUBSCENE) {
|
||||
remote_scene_tree->emit_signal(SNAME("open"), item->get_meta("scene_file_path"));
|
||||
} else if (p_id == EditorDebuggerTree::BUTTON_VISIBILITY) {
|
||||
ObjectID obj_id = item->get_metadata(0);
|
||||
ERR_FAIL_COND(obj_id.is_null());
|
||||
get_current_debugger()->update_remote_object(obj_id, "visible", !item->get_meta("visible"));
|
||||
get_current_debugger()->request_remote_tree();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
if (obj->remote_object_id == p_id) {
|
||||
return; // Already being edited
|
||||
}
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->push_item(get_current_debugger()->get_remote_object(p_id));
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) {
|
||||
if (obj->remote_object_id != p_id) {
|
||||
return;
|
||||
}
|
||||
InspectorDock::get_inspector_singleton()->update_property(p_property);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_remote_object_requested(ObjectID p_id, int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
inspect_edited_object_timeout = 0.7; // Temporarily disable timeout to avoid multiple requests.
|
||||
get_current_debugger()->request_remote_object(p_id);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
get_current_debugger()->save_node(p_id, p_file);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_breakpoint_set_in_tree(Ref<RefCounted> p_script, int p_line, bool p_enabled, int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_signal(SNAME("breakpoint_set_in_tree"), p_script, p_line, p_enabled);
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_breakpoints_cleared_in_tree(int p_debugger) {
|
||||
if (p_debugger != tabs->get_current_tab()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_signal(SNAME("breakpoints_cleared_in_tree"));
|
||||
}
|
||||
|
||||
// Remote inspector/edit.
|
||||
void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount) {
|
||||
if (!singleton) {
|
||||
return;
|
||||
}
|
||||
_for_all(singleton->tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->_method_changed(p_base, p_name, p_args, p_argcount);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) {
|
||||
if (!singleton) {
|
||||
return;
|
||||
}
|
||||
_for_all(singleton->tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->_property_changed(p_base, p_property, p_value);
|
||||
});
|
||||
}
|
||||
|
||||
// LiveDebug
|
||||
void EditorDebuggerNode::set_live_debugging(bool p_enabled) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->set_live_debugging(p_enabled);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::update_live_edit_root() {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->update_live_edit_root();
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_create_node(p_parent, p_type, p_name);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_instantiate_node(const NodePath &p_parent, const String &p_path, const String &p_name) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_instantiate_node(p_parent, p_path, p_name);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_remove_node(const NodePath &p_at) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_remove_node(p_at);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_remove_and_keep_node(p_at, p_keep_id);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_restore_node(p_id, p_at, p_at_pos);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_duplicate_node(p_at, p_new_name);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos);
|
||||
});
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::set_camera_override(CameraOverride p_override) {
|
||||
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
|
||||
dbg->set_camera_override(p_override);
|
||||
});
|
||||
camera_override = p_override;
|
||||
}
|
||||
|
||||
EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() {
|
||||
return camera_override;
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
|
||||
ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null.");
|
||||
ERR_FAIL_COND_MSG(debugger_plugins.has(p_plugin), "Debugger plugin already exists.");
|
||||
debugger_plugins.insert(p_plugin);
|
||||
|
||||
Ref<EditorDebuggerPlugin> plugin = p_plugin;
|
||||
for (int i = 0; get_debugger(i); i++) {
|
||||
plugin->create_session(get_debugger(i));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerNode::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) {
|
||||
ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null.");
|
||||
ERR_FAIL_COND_MSG(!debugger_plugins.has(p_plugin), "Debugger plugin doesn't exists.");
|
||||
debugger_plugins.erase(p_plugin);
|
||||
Ref<EditorDebuggerPlugin>(p_plugin)->clear();
|
||||
}
|
||||
|
||||
bool EditorDebuggerNode::plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data) {
|
||||
int session_index = tabs->get_tab_idx_from_control(p_debugger);
|
||||
ERR_FAIL_COND_V(session_index < 0, false);
|
||||
int colon_index = p_message.find_char(':');
|
||||
ERR_FAIL_COND_V_MSG(colon_index < 1, false, "Invalid message received.");
|
||||
|
||||
const String cap = p_message.substr(0, colon_index);
|
||||
bool parsed = false;
|
||||
for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) {
|
||||
if (plugin->has_capture(cap)) {
|
||||
parsed |= plugin->capture(p_message, p_data, session_index);
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
224
engine/editor/debugger/editor_debugger_node.h
Normal file
224
engine/editor/debugger/editor_debugger_node.h
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_node.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_NODE_H
|
||||
#define EDITOR_DEBUGGER_NODE_H
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "editor/debugger/editor_debugger_server.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Button;
|
||||
class DebugAdapterParser;
|
||||
class EditorDebuggerPlugin;
|
||||
class EditorDebuggerTree;
|
||||
class EditorDebuggerRemoteObject;
|
||||
class MenuButton;
|
||||
class ScriptEditorDebugger;
|
||||
class TabContainer;
|
||||
class UndoRedo;
|
||||
|
||||
class EditorDebuggerNode : public MarginContainer {
|
||||
GDCLASS(EditorDebuggerNode, MarginContainer);
|
||||
|
||||
public:
|
||||
enum CameraOverride {
|
||||
OVERRIDE_NONE,
|
||||
OVERRIDE_2D,
|
||||
OVERRIDE_3D_1, // 3D Viewport 1
|
||||
OVERRIDE_3D_2, // 3D Viewport 2
|
||||
OVERRIDE_3D_3, // 3D Viewport 3
|
||||
OVERRIDE_3D_4 // 3D Viewport 4
|
||||
};
|
||||
|
||||
private:
|
||||
enum Options {
|
||||
DEBUG_NEXT,
|
||||
DEBUG_STEP,
|
||||
DEBUG_BREAK,
|
||||
DEBUG_CONTINUE,
|
||||
DEBUG_WITH_EXTERNAL_EDITOR,
|
||||
};
|
||||
|
||||
class Breakpoint {
|
||||
public:
|
||||
String source;
|
||||
int line = 0;
|
||||
|
||||
static uint32_t hash(const Breakpoint &p_val) {
|
||||
uint32_t h = HashMapHasherDefault::hash(p_val.source);
|
||||
return hash_murmur3_one_32(p_val.line, h);
|
||||
}
|
||||
bool operator==(const Breakpoint &p_b) const {
|
||||
return (line == p_b.line && source == p_b.source);
|
||||
}
|
||||
|
||||
bool operator<(const Breakpoint &p_b) const {
|
||||
if (line == p_b.line) {
|
||||
return source < p_b.source;
|
||||
}
|
||||
return line < p_b.line;
|
||||
}
|
||||
|
||||
Breakpoint() {}
|
||||
|
||||
Breakpoint(const String &p_source, int p_line) {
|
||||
line = p_line;
|
||||
source = p_source;
|
||||
}
|
||||
};
|
||||
|
||||
Ref<EditorDebuggerServer> server;
|
||||
TabContainer *tabs = nullptr;
|
||||
Button *debugger_button = nullptr;
|
||||
MenuButton *script_menu = nullptr;
|
||||
|
||||
Ref<Script> stack_script; // Why?!?
|
||||
|
||||
bool initializing = true;
|
||||
int last_error_count = 0;
|
||||
int last_warning_count = 0;
|
||||
|
||||
float inspect_edited_object_timeout = 0;
|
||||
EditorDebuggerTree *remote_scene_tree = nullptr;
|
||||
float remote_scene_tree_timeout = 0.0;
|
||||
bool auto_switch_remote_scene_tree = false;
|
||||
bool debug_with_external_editor = false;
|
||||
bool keep_open = false;
|
||||
String current_uri;
|
||||
|
||||
CameraOverride camera_override = OVERRIDE_NONE;
|
||||
HashMap<Breakpoint, bool, Breakpoint> breakpoints;
|
||||
|
||||
HashSet<Ref<EditorDebuggerPlugin>> debugger_plugins;
|
||||
|
||||
ScriptEditorDebugger *_add_debugger();
|
||||
EditorDebuggerRemoteObject *get_inspected_remote_object();
|
||||
void _update_errors();
|
||||
|
||||
friend class DebuggerEditorPlugin;
|
||||
friend class DebugAdapterParser;
|
||||
static EditorDebuggerNode *singleton;
|
||||
EditorDebuggerNode();
|
||||
|
||||
protected:
|
||||
void _debugger_stopped(int p_id);
|
||||
void _debugger_wants_stop(int p_id);
|
||||
void _debugger_changed(int p_tab);
|
||||
void _remote_tree_updated(int p_debugger);
|
||||
void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
void _remote_object_updated(ObjectID p_id, int p_debugger);
|
||||
void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger);
|
||||
void _remote_object_requested(ObjectID p_id, int p_debugger);
|
||||
void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger);
|
||||
|
||||
void _breakpoint_set_in_tree(Ref<RefCounted> p_script, int p_line, bool p_enabled, int p_debugger);
|
||||
void _breakpoints_cleared_in_tree(int p_debugger);
|
||||
|
||||
void _clear_execution(Ref<RefCounted> p_script) {
|
||||
emit_signal(SNAME("clear_execution"), p_script);
|
||||
}
|
||||
|
||||
void _text_editor_stack_goto(const ScriptEditorDebugger *p_debugger);
|
||||
void _text_editor_stack_clear(const ScriptEditorDebugger *p_debugger);
|
||||
void _stack_frame_selected(int p_debugger);
|
||||
void _error_selected(const String &p_file, int p_line, int p_debugger);
|
||||
void _breaked(bool p_breaked, bool p_can_debug, const String &p_message, bool p_has_stackdump, int p_debugger);
|
||||
void _paused();
|
||||
void _break_state_changed();
|
||||
void _menu_option(int p_id);
|
||||
void _update_debug_options();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorDebuggerNode *get_singleton() { return singleton; }
|
||||
void register_undo_redo(UndoRedo *p_undo_redo);
|
||||
|
||||
ScriptEditorDebugger *get_previous_debugger() const;
|
||||
ScriptEditorDebugger *get_current_debugger() const;
|
||||
ScriptEditorDebugger *get_default_debugger() const;
|
||||
ScriptEditorDebugger *get_debugger(int p_debugger) const;
|
||||
|
||||
void debug_next();
|
||||
void debug_step();
|
||||
void debug_break();
|
||||
void debug_continue();
|
||||
|
||||
void set_script_debug_button(MenuButton *p_button);
|
||||
|
||||
void set_tool_button(Button *p_button) {
|
||||
debugger_button = p_button;
|
||||
}
|
||||
|
||||
String get_var_value(const String &p_var) const;
|
||||
Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this?
|
||||
|
||||
bool get_debug_with_external_editor() { return debug_with_external_editor; }
|
||||
|
||||
bool is_skip_breakpoints() const;
|
||||
void set_breakpoint(const String &p_path, int p_line, bool p_enabled);
|
||||
void set_breakpoints(const String &p_path, const Array &p_lines);
|
||||
void reload_all_scripts();
|
||||
void reload_scripts(const Vector<String> &p_script_paths);
|
||||
|
||||
// Remote inspector/edit.
|
||||
void request_remote_tree();
|
||||
static void _method_changeds(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
|
||||
static void _property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);
|
||||
|
||||
// LiveDebug
|
||||
void set_live_debugging(bool p_enabled);
|
||||
void update_live_edit_root();
|
||||
void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name);
|
||||
void live_debug_instantiate_node(const NodePath &p_parent, const String &p_path, const String &p_name);
|
||||
void live_debug_remove_node(const NodePath &p_at);
|
||||
void live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id);
|
||||
void live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos);
|
||||
void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name);
|
||||
void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos);
|
||||
|
||||
void set_camera_override(CameraOverride p_override);
|
||||
CameraOverride get_camera_override();
|
||||
|
||||
String get_server_uri() const;
|
||||
|
||||
void set_keep_open(bool p_keep_open);
|
||||
Error start(const String &p_uri = "tcp://");
|
||||
void stop(bool p_force = false);
|
||||
|
||||
bool plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data);
|
||||
void add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
|
||||
void remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin);
|
||||
};
|
||||
|
||||
#endif // EDITOR_DEBUGGER_NODE_H
|
||||
143
engine/editor/debugger/editor_debugger_server.cpp
Normal file
143
engine/editor/debugger/editor_debugger_server.cpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_server.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_debugger_server.h"
|
||||
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "editor/editor_log.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
class EditorDebuggerServerTCP : public EditorDebuggerServer {
|
||||
private:
|
||||
Ref<TCPServer> server;
|
||||
String endpoint;
|
||||
|
||||
public:
|
||||
static EditorDebuggerServer *create(const String &p_protocol);
|
||||
|
||||
virtual void poll() override {}
|
||||
virtual String get_uri() const override;
|
||||
virtual Error start(const String &p_uri) override;
|
||||
virtual void stop() override;
|
||||
virtual bool is_active() const override;
|
||||
virtual bool is_connection_available() const override;
|
||||
virtual Ref<RemoteDebuggerPeer> take_connection() override;
|
||||
|
||||
EditorDebuggerServerTCP();
|
||||
};
|
||||
|
||||
EditorDebuggerServer *EditorDebuggerServerTCP::create(const String &p_protocol) {
|
||||
ERR_FAIL_COND_V(p_protocol != "tcp://", nullptr);
|
||||
return memnew(EditorDebuggerServerTCP);
|
||||
}
|
||||
|
||||
EditorDebuggerServerTCP::EditorDebuggerServerTCP() {
|
||||
server.instantiate();
|
||||
}
|
||||
|
||||
String EditorDebuggerServerTCP::get_uri() const {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
Error EditorDebuggerServerTCP::start(const String &p_uri) {
|
||||
// Default host and port
|
||||
String bind_host = (String)EDITOR_GET("network/debug/remote_host");
|
||||
int bind_port = (int)EDITOR_GET("network/debug/remote_port");
|
||||
|
||||
// Optionally override
|
||||
if (!p_uri.is_empty() && p_uri != "tcp://") {
|
||||
String scheme, path;
|
||||
Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
|
||||
ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
// Try listening on ports
|
||||
const int max_attempts = 5;
|
||||
for (int attempt = 1;; ++attempt) {
|
||||
const Error err = server->listen(bind_port, bind_host);
|
||||
if (err == OK) {
|
||||
break;
|
||||
}
|
||||
if (attempt >= max_attempts) {
|
||||
EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, remote debugging unavailable.", bind_port), EditorLog::MSG_TYPE_ERROR);
|
||||
return err;
|
||||
}
|
||||
int last_port = bind_port++;
|
||||
EditorNode::get_log()->add_message(vformat("Cannot listen on port %d, trying %d instead.", last_port, bind_port), EditorLog::MSG_TYPE_WARNING);
|
||||
}
|
||||
|
||||
// Endpoint that the client should connect to
|
||||
endpoint = vformat("tcp://%s:%d", bind_host, bind_port);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorDebuggerServerTCP::stop() {
|
||||
server->stop();
|
||||
}
|
||||
|
||||
bool EditorDebuggerServerTCP::is_active() const {
|
||||
return server->is_listening();
|
||||
}
|
||||
|
||||
bool EditorDebuggerServerTCP::is_connection_available() const {
|
||||
return server->is_listening() && server->is_connection_available();
|
||||
}
|
||||
|
||||
Ref<RemoteDebuggerPeer> EditorDebuggerServerTCP::take_connection() {
|
||||
ERR_FAIL_COND_V(!is_connection_available(), Ref<RemoteDebuggerPeer>());
|
||||
return memnew(RemoteDebuggerPeerTCP(server->take_connection()));
|
||||
}
|
||||
|
||||
/// EditorDebuggerServer
|
||||
HashMap<StringName, EditorDebuggerServer::CreateServerFunc> EditorDebuggerServer::protocols;
|
||||
|
||||
EditorDebuggerServer *EditorDebuggerServer::create(const String &p_protocol) {
|
||||
ERR_FAIL_COND_V(!protocols.has(p_protocol), nullptr);
|
||||
return protocols[p_protocol](p_protocol);
|
||||
}
|
||||
|
||||
void EditorDebuggerServer::register_protocol_handler(const String &p_protocol, CreateServerFunc p_func) {
|
||||
ERR_FAIL_COND(protocols.has(p_protocol));
|
||||
protocols[p_protocol] = p_func;
|
||||
}
|
||||
|
||||
void EditorDebuggerServer::initialize() {
|
||||
register_protocol_handler("tcp://", EditorDebuggerServerTCP::create);
|
||||
}
|
||||
|
||||
void EditorDebuggerServer::deinitialize() {
|
||||
protocols.clear();
|
||||
}
|
||||
60
engine/editor/debugger/editor_debugger_server.h
Normal file
60
engine/editor/debugger/editor_debugger_server.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_SERVER_H
|
||||
#define EDITOR_DEBUGGER_SERVER_H
|
||||
|
||||
#include "core/debugger/remote_debugger_peer.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class EditorDebuggerServer : public RefCounted {
|
||||
public:
|
||||
typedef EditorDebuggerServer *(*CreateServerFunc)(const String &p_uri);
|
||||
|
||||
private:
|
||||
static HashMap<StringName, CreateServerFunc> protocols;
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
static void deinitialize();
|
||||
|
||||
static void register_protocol_handler(const String &p_protocol, CreateServerFunc p_func);
|
||||
static EditorDebuggerServer *create(const String &p_protocol);
|
||||
|
||||
virtual String get_uri() const = 0;
|
||||
virtual void poll() = 0;
|
||||
virtual Error start(const String &p_uri) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual bool is_active() const = 0;
|
||||
virtual bool is_connection_available() const = 0;
|
||||
virtual Ref<RemoteDebuggerPeer> take_connection() = 0;
|
||||
};
|
||||
|
||||
#endif // EDITOR_DEBUGGER_SERVER_H
|
||||
370
engine/editor/debugger/editor_debugger_tree.cpp
Normal file
370
engine/editor/debugger/editor_debugger_tree.cpp
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_tree.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_debugger_tree.h"
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/scene_tree_dock.h"
|
||||
#include "scene/debugger/scene_debugger.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
EditorDebuggerTree::EditorDebuggerTree() {
|
||||
set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
set_allow_rmb_select(true);
|
||||
|
||||
// Popup
|
||||
item_menu = memnew(PopupMenu);
|
||||
item_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDebuggerTree::_item_menu_id_pressed));
|
||||
add_child(item_menu);
|
||||
|
||||
// File Dialog
|
||||
file_dialog = memnew(EditorFileDialog);
|
||||
file_dialog->connect("file_selected", callable_mp(this, &EditorDebuggerTree::_file_selected));
|
||||
add_child(file_dialog);
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
|
||||
connect("cell_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_selected));
|
||||
connect("item_collapsed", callable_mp(this, &EditorDebuggerTree::_scene_tree_folded));
|
||||
connect("item_mouse_selected", callable_mp(this, &EditorDebuggerTree::_scene_tree_rmb_selected));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
update_icon_max_width();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::INT, "debugger")));
|
||||
ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger")));
|
||||
ADD_SIGNAL(MethodInfo("open"));
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_selected() {
|
||||
if (updating_scene_tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
inspected_object_id = uint64_t(item->get_metadata(0));
|
||||
|
||||
emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id);
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) {
|
||||
if (updating_scene_tree) {
|
||||
return;
|
||||
}
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_obj);
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectID id = ObjectID(uint64_t(item->get_metadata(0)));
|
||||
if (unfold_cache.has(id)) {
|
||||
unfold_cache.erase(id);
|
||||
} else {
|
||||
unfold_cache.insert(id);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button) {
|
||||
if (p_button != MouseButton::RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = get_item_at_position(p_position);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
item->select(0);
|
||||
|
||||
item_menu->clear();
|
||||
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE);
|
||||
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
|
||||
item_menu->set_position(get_screen_position() + get_local_mouse_position());
|
||||
item_menu->reset_size();
|
||||
item_menu->popup();
|
||||
}
|
||||
|
||||
/// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first.
|
||||
///
|
||||
/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming
|
||||
/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0.
|
||||
///
|
||||
/// R
|
||||
/// |-A
|
||||
/// | |-B
|
||||
/// | | |-C
|
||||
/// | |
|
||||
/// | |-D
|
||||
/// |
|
||||
/// |-E
|
||||
///
|
||||
void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {
|
||||
updating_scene_tree = true;
|
||||
const String last_path = get_selected_path();
|
||||
const String filter = SceneTreeDock::get_singleton()->get_filter();
|
||||
bool filter_changed = filter != last_filter;
|
||||
TreeItem *scroll_item = nullptr;
|
||||
|
||||
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
|
||||
List<Pair<TreeItem *, int>> parents;
|
||||
for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {
|
||||
TreeItem *parent = nullptr;
|
||||
if (parents.size()) { // Find last parent.
|
||||
Pair<TreeItem *, int> &p = parents.front()->get();
|
||||
parent = p.first;
|
||||
if (!(--p.second)) { // If no child left, remove it.
|
||||
parents.pop_front();
|
||||
}
|
||||
}
|
||||
// Add this node.
|
||||
TreeItem *item = create_item(parent);
|
||||
item->set_text(0, node.name);
|
||||
if (node.scene_file_path.is_empty()) {
|
||||
item->set_tooltip_text(0, node.name + "\n" + TTR("Type:") + " " + node.type_name);
|
||||
} else {
|
||||
item->set_tooltip_text(0, node.name + "\n" + TTR("Instance:") + " " + node.scene_file_path + "\n" + TTR("Type:") + " " + node.type_name);
|
||||
}
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name, "");
|
||||
if (icon.is_valid()) {
|
||||
item->set_icon(0, icon);
|
||||
}
|
||||
item->set_metadata(0, node.id);
|
||||
|
||||
// Set current item as collapsed if necessary (root is never collapsed).
|
||||
if (parent) {
|
||||
if (!unfold_cache.has(node.id)) {
|
||||
item->set_collapsed(true);
|
||||
}
|
||||
}
|
||||
// Select previously selected node.
|
||||
if (debugger_id == p_debugger) { // Can use remote id.
|
||||
if (node.id == inspected_object_id) {
|
||||
item->select(0);
|
||||
if (filter_changed) {
|
||||
scroll_item = item;
|
||||
}
|
||||
}
|
||||
} else { // Must use path
|
||||
if (last_path == _get_path(item)) {
|
||||
updating_scene_tree = false; // Force emission of new selection.
|
||||
item->select(0);
|
||||
if (filter_changed) {
|
||||
scroll_item = item;
|
||||
}
|
||||
updating_scene_tree = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add buttons.
|
||||
const Color remote_button_color = Color(1, 1, 1, 0.8);
|
||||
if (!node.scene_file_path.is_empty()) {
|
||||
String node_scene_file_path = node.scene_file_path;
|
||||
Ref<Texture2D> button_icon = get_editor_theme_icon(SNAME("InstanceOptions"));
|
||||
String tooltip = vformat(TTR("This node has been instantiated from a PackedScene file:\n%s\nClick to open the original file in the Editor."), node_scene_file_path);
|
||||
|
||||
item->set_meta("scene_file_path", node_scene_file_path);
|
||||
item->add_button(0, button_icon, BUTTON_SUBSCENE, false, tooltip);
|
||||
item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
|
||||
}
|
||||
|
||||
if (node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_HAS_VISIBLE_METHOD) {
|
||||
bool node_visible = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE;
|
||||
bool node_visible_in_tree = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE_IN_TREE;
|
||||
Ref<Texture2D> button_icon = get_editor_theme_icon(node_visible ? SNAME("GuiVisibilityVisible") : SNAME("GuiVisibilityHidden"));
|
||||
String tooltip = TTR("Toggle Visibility");
|
||||
|
||||
item->set_meta("visible", node_visible);
|
||||
item->add_button(0, button_icon, BUTTON_VISIBILITY, false, tooltip);
|
||||
if (ClassDB::is_parent_class(node.type_name, "CanvasItem") || ClassDB::is_parent_class(node.type_name, "Node3D")) {
|
||||
item->set_button_color(0, item->get_button_count(0) - 1, node_visible_in_tree ? remote_button_color : Color(1, 1, 1, 0.6));
|
||||
} else {
|
||||
item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Add in front of the parents stack if children are expected.
|
||||
if (node.child_count) {
|
||||
parents.push_front(Pair<TreeItem *, int>(item, node.child_count));
|
||||
} else {
|
||||
// Apply filters.
|
||||
while (parent) {
|
||||
const bool had_siblings = item->get_prev() || item->get_next();
|
||||
if (filter.is_subsequence_ofn(item->get_text(0))) {
|
||||
break; // Filter matches, must survive.
|
||||
}
|
||||
parent->remove_child(item);
|
||||
memdelete(item);
|
||||
if (scroll_item == item) {
|
||||
scroll_item = nullptr;
|
||||
}
|
||||
if (had_siblings) {
|
||||
break; // Parent must survive.
|
||||
}
|
||||
item = parent;
|
||||
parent = item->get_parent();
|
||||
// Check if parent expects more children.
|
||||
for (const Pair<TreeItem *, int> &pair : parents) {
|
||||
if (pair.first == item) {
|
||||
parent = nullptr;
|
||||
break; // Might have more children.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree
|
||||
if (scroll_item) {
|
||||
callable_mp((Tree *)this, &Tree::scroll_to_item).call_deferred(scroll_item, false);
|
||||
}
|
||||
last_filter = filter;
|
||||
updating_scene_tree = false;
|
||||
}
|
||||
|
||||
Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
|
||||
if (get_button_id_at_position(p_point) != -1) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
TreeItem *selected = get_selected();
|
||||
if (!selected) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
String path = selected->get_text(0);
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
TextureRect *tf = memnew(TextureRect);
|
||||
tf->set_texture(selected->get_icon(0));
|
||||
tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
||||
hb->add_child(tf);
|
||||
Label *label = memnew(Label(path));
|
||||
hb->add_child(label);
|
||||
set_drag_preview(hb);
|
||||
|
||||
if (!selected->get_parent() || !selected->get_parent()->get_parent()) {
|
||||
path = ".";
|
||||
} else {
|
||||
while (selected->get_parent()->get_parent() != get_root()) {
|
||||
selected = selected->get_parent();
|
||||
path = selected->get_text(0) + "/" + path;
|
||||
}
|
||||
}
|
||||
|
||||
return vformat("\"%s\"", path);
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::update_icon_max_width() {
|
||||
add_theme_constant_override("icon_max_width", get_theme_constant("class_icon_size", EditorStringName(Editor)));
|
||||
}
|
||||
|
||||
String EditorDebuggerTree::get_selected_path() {
|
||||
if (!get_selected()) {
|
||||
return "";
|
||||
}
|
||||
return _get_path(get_selected());
|
||||
}
|
||||
|
||||
String EditorDebuggerTree::_get_path(TreeItem *p_item) {
|
||||
ERR_FAIL_NULL_V(p_item, "");
|
||||
|
||||
if (p_item->get_parent() == nullptr) {
|
||||
return "/root";
|
||||
}
|
||||
String text = p_item->get_text(0);
|
||||
TreeItem *cur = p_item->get_parent();
|
||||
while (cur) {
|
||||
text = cur->get_text(0) + "/" + text;
|
||||
cur = cur->get_parent();
|
||||
}
|
||||
return "/" + text;
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
|
||||
switch (p_option) {
|
||||
case ITEM_MENU_SAVE_REMOTE_NODE: {
|
||||
file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
|
||||
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
|
||||
List<String> extensions;
|
||||
Ref<PackedScene> sd = memnew(PackedScene);
|
||||
ResourceSaver::get_recognized_extensions(sd, &extensions);
|
||||
file_dialog->clear_filters();
|
||||
for (const String &extension : extensions) {
|
||||
file_dialog->add_filter("*." + extension, extension.to_upper());
|
||||
}
|
||||
|
||||
String filename = get_selected_path().get_file() + "." + extensions.front()->get().to_lower();
|
||||
file_dialog->set_current_path(filename);
|
||||
file_dialog->popup_file_dialog();
|
||||
} break;
|
||||
case ITEM_MENU_COPY_NODE_PATH: {
|
||||
String text = get_selected_path();
|
||||
if (text.is_empty()) {
|
||||
return;
|
||||
} else if (text == "/root") {
|
||||
text = ".";
|
||||
} else {
|
||||
text = text.replace("/root/", "");
|
||||
int slash = text.find("/");
|
||||
if (slash < 0) {
|
||||
text = ".";
|
||||
} else {
|
||||
text = text.substr(slash + 1);
|
||||
}
|
||||
}
|
||||
DisplayServer::get_singleton()->clipboard_set(text);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDebuggerTree::_file_selected(const String &p_file) {
|
||||
if (inspected_object_id.is_null()) {
|
||||
return;
|
||||
}
|
||||
emit_signal(SNAME("save_node"), inspected_object_id, p_file, debugger_id);
|
||||
}
|
||||
83
engine/editor/debugger/editor_debugger_tree.h
Normal file
83
engine/editor/debugger/editor_debugger_tree.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**************************************************************************/
|
||||
/* editor_debugger_tree.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DEBUGGER_TREE_H
|
||||
#define EDITOR_DEBUGGER_TREE_H
|
||||
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class SceneDebuggerTree;
|
||||
class EditorFileDialog;
|
||||
|
||||
class EditorDebuggerTree : public Tree {
|
||||
GDCLASS(EditorDebuggerTree, Tree);
|
||||
|
||||
private:
|
||||
enum ItemMenu {
|
||||
ITEM_MENU_SAVE_REMOTE_NODE,
|
||||
ITEM_MENU_COPY_NODE_PATH,
|
||||
};
|
||||
|
||||
ObjectID inspected_object_id;
|
||||
int debugger_id = 0;
|
||||
bool updating_scene_tree = false;
|
||||
HashSet<ObjectID> unfold_cache;
|
||||
PopupMenu *item_menu = nullptr;
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
String last_filter;
|
||||
|
||||
String _get_path(TreeItem *p_item);
|
||||
void _scene_tree_folded(Object *p_obj);
|
||||
void _scene_tree_selected();
|
||||
void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button);
|
||||
void _item_menu_id_pressed(int p_option);
|
||||
void _file_selected(const String &p_file);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
enum Button {
|
||||
BUTTON_SUBSCENE = 0,
|
||||
BUTTON_VISIBILITY = 1,
|
||||
};
|
||||
|
||||
virtual Variant get_drag_data(const Point2 &p_point) override;
|
||||
|
||||
void update_icon_max_width();
|
||||
String get_selected_path();
|
||||
ObjectID get_selected_object();
|
||||
int get_current_debugger(); // Would love to have one tree for every debugger.
|
||||
void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger);
|
||||
EditorDebuggerTree();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DEBUGGER_TREE_H
|
||||
281
engine/editor/debugger/editor_file_server.cpp
Normal file
281
engine/editor/debugger/editor_file_server.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/**************************************************************************/
|
||||
/* editor_file_server.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_file_server.h"
|
||||
|
||||
#include "../editor_settings.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/export/editor_export_platform.h"
|
||||
|
||||
#define FILESYSTEM_PROTOCOL_VERSION 1
|
||||
#define PASSWORD_LENGTH 32
|
||||
#define MAX_FILE_BUFFER_SIZE 100 * 1024 * 1024 // 100mb max file buffer size (description of files to update, compressed).
|
||||
|
||||
static void _add_file(String f, const uint64_t &p_modified_time, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
|
||||
f = f.replace_first("res://", ""); // remove res://
|
||||
const uint64_t *cached_mt = cached_files.getptr(f);
|
||||
if (cached_mt && *cached_mt == p_modified_time) {
|
||||
// File is good, skip it.
|
||||
cached_files.erase(f); // Erase to mark this file as existing. Remaining files not added to files_to_send will be considered erased here, so they need to be erased in the client too.
|
||||
return;
|
||||
}
|
||||
files_to_send.insert(f, p_modified_time);
|
||||
}
|
||||
|
||||
void EditorFileServer::_scan_files_changed(EditorFileSystemDirectory *efd, const Vector<String> &p_tags, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
|
||||
for (int i = 0; i < efd->get_file_count(); i++) {
|
||||
String f = efd->get_file_path(i);
|
||||
if (FileAccess::exists(f + ".import")) {
|
||||
// is imported, determine what to do
|
||||
// Todo the modified times of remapped files should most likely be kept in EditorFileSystem to speed this up in the future.
|
||||
Ref<ConfigFile> cf;
|
||||
cf.instantiate();
|
||||
Error err = cf->load(f + ".import");
|
||||
|
||||
ERR_CONTINUE(err != OK);
|
||||
{
|
||||
uint64_t mt = FileAccess::get_modified_time(f + ".import");
|
||||
_add_file(f + ".import", mt, files_to_send, cached_files);
|
||||
}
|
||||
|
||||
if (!cf->has_section("remap")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> remaps;
|
||||
cf->get_section_keys("remap", &remaps);
|
||||
|
||||
for (const String &remap : remaps) {
|
||||
if (remap == "path") {
|
||||
String remapped_path = cf->get_value("remap", remap);
|
||||
uint64_t mt = FileAccess::get_modified_time(remapped_path);
|
||||
_add_file(remapped_path, mt, files_to_send, cached_files);
|
||||
} else if (remap.begins_with("path.")) {
|
||||
String feature = remap.get_slice(".", 1);
|
||||
if (p_tags.has(feature)) {
|
||||
String remapped_path = cf->get_value("remap", remap);
|
||||
uint64_t mt = FileAccess::get_modified_time(remapped_path);
|
||||
_add_file(remapped_path, mt, files_to_send, cached_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint64_t mt = efd->get_file_modified_time(i);
|
||||
_add_file(f, mt, files_to_send, cached_files);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < efd->get_subdir_count(); i++) {
|
||||
_scan_files_changed(efd->get_subdir(i), p_tags, files_to_send, cached_files);
|
||||
}
|
||||
}
|
||||
|
||||
static void _add_custom_file(const String &f, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
|
||||
if (!FileAccess::exists(f)) {
|
||||
return;
|
||||
}
|
||||
_add_file(f, FileAccess::get_modified_time(f), files_to_send, cached_files);
|
||||
}
|
||||
|
||||
void EditorFileServer::poll() {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server->is_connection_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<StreamPeerTCP> tcp_peer = server->take_connection();
|
||||
ERR_FAIL_COND(tcp_peer.is_null());
|
||||
|
||||
// Got a connection!
|
||||
EditorProgress pr("updating_remote_file_system", TTR("Updating assets on target device:"), 105);
|
||||
|
||||
pr.step(TTR("Syncing headers"), 0, true);
|
||||
print_verbose("EFS: Connecting taken!");
|
||||
char header[4];
|
||||
Error err = tcp_peer->get_data((uint8_t *)&header, 4);
|
||||
ERR_FAIL_COND(err != OK);
|
||||
ERR_FAIL_COND(header[0] != 'G');
|
||||
ERR_FAIL_COND(header[1] != 'R');
|
||||
ERR_FAIL_COND(header[2] != 'F');
|
||||
ERR_FAIL_COND(header[3] != 'S');
|
||||
|
||||
uint32_t protocol_version = tcp_peer->get_u32();
|
||||
ERR_FAIL_COND(protocol_version != FILESYSTEM_PROTOCOL_VERSION);
|
||||
|
||||
char cpassword[PASSWORD_LENGTH + 1];
|
||||
err = tcp_peer->get_data((uint8_t *)cpassword, PASSWORD_LENGTH);
|
||||
cpassword[PASSWORD_LENGTH] = 0;
|
||||
ERR_FAIL_COND(err != OK);
|
||||
print_verbose("EFS: Got password: " + String(cpassword));
|
||||
ERR_FAIL_COND_MSG(password != cpassword, "Client disconnected because password mismatch.");
|
||||
|
||||
uint32_t tag_count = tcp_peer->get_u32();
|
||||
print_verbose("EFS: Getting tags: " + itos(tag_count));
|
||||
|
||||
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
|
||||
Vector<String> tags;
|
||||
for (uint32_t i = 0; i < tag_count; i++) {
|
||||
String tag = tcp_peer->get_utf8_string();
|
||||
print_verbose("EFS: tag #" + itos(i) + ": " + tag);
|
||||
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
|
||||
tags.push_back(tag);
|
||||
}
|
||||
|
||||
uint32_t file_buffer_decompressed_size = tcp_peer->get_32();
|
||||
HashMap<String, uint64_t> cached_files;
|
||||
|
||||
if (file_buffer_decompressed_size > 0) {
|
||||
pr.step(TTR("Getting remote file system"), 1, true);
|
||||
|
||||
// Got files cached by client.
|
||||
uint32_t file_buffer_size = tcp_peer->get_32();
|
||||
print_verbose("EFS: Getting file buffer: compressed - " + String::humanize_size(file_buffer_size) + " decompressed: " + String::humanize_size(file_buffer_decompressed_size));
|
||||
|
||||
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
|
||||
ERR_FAIL_COND(file_buffer_size > MAX_FILE_BUFFER_SIZE);
|
||||
LocalVector<uint8_t> file_buffer;
|
||||
file_buffer.resize(file_buffer_size);
|
||||
LocalVector<uint8_t> file_buffer_decompressed;
|
||||
file_buffer_decompressed.resize(file_buffer_decompressed_size);
|
||||
|
||||
err = tcp_peer->get_data(file_buffer.ptr(), file_buffer_size);
|
||||
|
||||
pr.step(TTR("Decompressing remote file system"), 2, true);
|
||||
|
||||
ERR_FAIL_COND(err != OK);
|
||||
// Decompress the text with all the files
|
||||
Compression::decompress(file_buffer_decompressed.ptr(), file_buffer_decompressed.size(), file_buffer.ptr(), file_buffer.size(), Compression::MODE_ZSTD);
|
||||
String files_text = String::utf8((const char *)file_buffer_decompressed.ptr(), file_buffer_decompressed.size());
|
||||
Vector<String> files = files_text.split("\n");
|
||||
|
||||
print_verbose("EFS: Total cached files received: " + itos(files.size()));
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
if (files[i].get_slice_count("::") != 2) {
|
||||
continue;
|
||||
}
|
||||
String file = files[i].get_slice("::", 0);
|
||||
uint64_t modified_time = files[i].get_slice("::", 1).to_int();
|
||||
|
||||
cached_files.insert(file, modified_time);
|
||||
}
|
||||
} else {
|
||||
// Client does not have any files stored.
|
||||
}
|
||||
|
||||
pr.step(TTR("Scanning for local changes"), 3, true);
|
||||
|
||||
print_verbose("EFS: Scanning changes:");
|
||||
|
||||
HashMap<String, uint64_t> files_to_send;
|
||||
// Scan files to send.
|
||||
_scan_files_changed(EditorFileSystem::get_singleton()->get_filesystem(), tags, files_to_send, cached_files);
|
||||
// Add forced export files
|
||||
Vector<String> forced_export = EditorExportPlatform::get_forced_export_files();
|
||||
for (int i = 0; i < forced_export.size(); i++) {
|
||||
_add_custom_file(forced_export[i], files_to_send, cached_files);
|
||||
}
|
||||
|
||||
_add_custom_file("res://project.godot", files_to_send, cached_files);
|
||||
// Check which files were removed and also add them
|
||||
for (KeyValue<String, uint64_t> K : cached_files) {
|
||||
if (!files_to_send.has(K.key)) {
|
||||
files_to_send.insert(K.key, 0); //0 means removed
|
||||
}
|
||||
}
|
||||
|
||||
tcp_peer->put_32(files_to_send.size());
|
||||
|
||||
print_verbose("EFS: Sending list of changed files.");
|
||||
pr.step(TTR("Sending list of changed files:"), 4, true);
|
||||
|
||||
// Send list of changed files first, to ensure that if connecting breaks, the client is not found in a broken state.
|
||||
for (KeyValue<String, uint64_t> K : files_to_send) {
|
||||
tcp_peer->put_utf8_string(K.key);
|
||||
tcp_peer->put_64(K.value);
|
||||
}
|
||||
|
||||
print_verbose("EFS: Sending " + itos(files_to_send.size()) + " files.");
|
||||
|
||||
int idx = 0;
|
||||
for (KeyValue<String, uint64_t> K : files_to_send) {
|
||||
pr.step(TTR("Sending file:") + " " + K.key.get_file(), 5 + idx * 100 / files_to_send.size(), false);
|
||||
idx++;
|
||||
|
||||
if (K.value == 0 || !FileAccess::exists("res://" + K.key)) { // File was removed
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<uint8_t> array = FileAccess::_get_file_as_bytes("res://" + K.key);
|
||||
tcp_peer->put_64(array.size());
|
||||
tcp_peer->put_data(array.ptr(), array.size());
|
||||
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
|
||||
}
|
||||
|
||||
tcp_peer->put_data((const uint8_t *)"GEND", 4); // End marker.
|
||||
|
||||
print_verbose("EFS: Done.");
|
||||
}
|
||||
|
||||
void EditorFileServer::start() {
|
||||
if (active) {
|
||||
stop();
|
||||
}
|
||||
port = EDITOR_GET("filesystem/file_server/port");
|
||||
password = EDITOR_GET("filesystem/file_server/password");
|
||||
Error err = server->listen(port);
|
||||
ERR_FAIL_COND_MSG(err != OK, "EditorFileServer: Unable to listen on port " + itos(port));
|
||||
active = true;
|
||||
}
|
||||
|
||||
bool EditorFileServer::is_active() const {
|
||||
return active;
|
||||
}
|
||||
|
||||
void EditorFileServer::stop() {
|
||||
if (active) {
|
||||
server->stop();
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorFileServer::EditorFileServer() {
|
||||
server.instantiate();
|
||||
|
||||
EDITOR_DEF("filesystem/file_server/port", 6010);
|
||||
EDITOR_DEF("filesystem/file_server/password", "");
|
||||
}
|
||||
|
||||
EditorFileServer::~EditorFileServer() {
|
||||
stop();
|
||||
}
|
||||
61
engine/editor/debugger/editor_file_server.h
Normal file
61
engine/editor/debugger/editor_file_server.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* editor_file_server.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_FILE_SERVER_H
|
||||
#define EDITOR_FILE_SERVER_H
|
||||
|
||||
#include "core/io/packet_peer.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
|
||||
class EditorFileServer : public Object {
|
||||
GDCLASS(EditorFileServer, Object);
|
||||
|
||||
Ref<TCPServer> server;
|
||||
String password;
|
||||
int port = 0;
|
||||
bool active = false;
|
||||
void _scan_files_changed(EditorFileSystemDirectory *efd, const Vector<String> &p_tags, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files);
|
||||
|
||||
public:
|
||||
void poll();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool is_active() const;
|
||||
|
||||
EditorFileServer();
|
||||
~EditorFileServer();
|
||||
};
|
||||
|
||||
#endif // EDITOR_FILE_SERVER_H
|
||||
430
engine/editor/debugger/editor_performance_profiler.cpp
Normal file
430
engine/editor/debugger/editor_performance_profiler.cpp
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
/**************************************************************************/
|
||||
/* editor_performance_profiler.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_performance_profiler.h"
|
||||
|
||||
#include "editor/editor_property_name_processor.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "editor/themes/editor_theme_manager.h"
|
||||
#include "main/performance.h"
|
||||
|
||||
EditorPerformanceProfiler::Monitor::Monitor() {}
|
||||
|
||||
EditorPerformanceProfiler::Monitor::Monitor(const String &p_name, const String &p_base, int p_frame_index, Performance::MonitorType p_type, TreeItem *p_item) {
|
||||
type = p_type;
|
||||
item = p_item;
|
||||
frame_index = p_frame_index;
|
||||
name = p_name;
|
||||
base = p_base;
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::Monitor::update_value(float p_value) {
|
||||
ERR_FAIL_NULL(item);
|
||||
String label = EditorPerformanceProfiler::_create_label(p_value, type);
|
||||
String tooltip = label;
|
||||
switch (type) {
|
||||
case Performance::MONITOR_TYPE_MEMORY: {
|
||||
tooltip = label;
|
||||
} break;
|
||||
case Performance::MONITOR_TYPE_TIME: {
|
||||
tooltip = label;
|
||||
} break;
|
||||
default: {
|
||||
tooltip += " " + item->get_text(0);
|
||||
} break;
|
||||
}
|
||||
item->set_text(1, label);
|
||||
item->set_tooltip_text(1, tooltip);
|
||||
|
||||
if (p_value > max) {
|
||||
max = p_value;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::Monitor::reset() {
|
||||
history.clear();
|
||||
max = 0.0f;
|
||||
if (item) {
|
||||
item->set_text(1, "");
|
||||
item->set_tooltip_text(1, "");
|
||||
}
|
||||
}
|
||||
|
||||
String EditorPerformanceProfiler::_create_label(float p_value, Performance::MonitorType p_type) {
|
||||
switch (p_type) {
|
||||
case Performance::MONITOR_TYPE_MEMORY: {
|
||||
return String::humanize_size(p_value);
|
||||
}
|
||||
case Performance::MONITOR_TYPE_TIME: {
|
||||
return TS->format_number(rtos(p_value * 1000).pad_decimals(2)) + " " + TTR("ms");
|
||||
}
|
||||
default: {
|
||||
return TS->format_number(rtos(p_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::_monitor_select() {
|
||||
monitor_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::_monitor_draw() {
|
||||
Vector<StringName> active;
|
||||
for (const KeyValue<StringName, Monitor> &E : monitors) {
|
||||
if (E.value.item->is_checked(0)) {
|
||||
active.push_back(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (active.is_empty()) {
|
||||
info_message->show();
|
||||
return;
|
||||
}
|
||||
|
||||
info_message->hide();
|
||||
|
||||
Ref<StyleBox> graph_style_box = get_theme_stylebox(CoreStringName(normal), SNAME("TextEdit"));
|
||||
Ref<Font> graph_font = get_theme_font(SceneStringName(font), SNAME("TextEdit"));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("TextEdit"));
|
||||
|
||||
int columns = int(Math::ceil(Math::sqrt(float(active.size()))));
|
||||
int rows = int(Math::ceil(float(active.size()) / float(columns)));
|
||||
if (active.size() == 1) {
|
||||
rows = 1;
|
||||
}
|
||||
Size2i cell_size = Size2i(monitor_draw->get_size()) / Size2i(columns, rows);
|
||||
float spacing = float(POINT_SEPARATION) / float(columns);
|
||||
float value_multiplier = EditorThemeManager::is_dark_theme() ? 1.4f : 0.55f;
|
||||
float hue_shift = 1.0f / float(monitors.size());
|
||||
|
||||
for (int i = 0; i < active.size(); i++) {
|
||||
Monitor ¤t = monitors[active[i]];
|
||||
Rect2i rect(Point2i(i % columns, i / columns) * cell_size + Point2i(MARGIN, MARGIN), cell_size - Point2i(MARGIN, MARGIN) * 2);
|
||||
monitor_draw->draw_style_box(graph_style_box, rect);
|
||||
|
||||
rect.position += graph_style_box->get_offset();
|
||||
rect.size -= graph_style_box->get_minimum_size();
|
||||
Color draw_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
|
||||
draw_color.set_hsv(Math::fmod(hue_shift * float(current.frame_index), 0.9f), draw_color.get_s() * 0.9f, draw_color.get_v() * value_multiplier, 0.6f);
|
||||
monitor_draw->draw_string(graph_font, rect.position + Point2(0, graph_font->get_ascent(font_size)), current.item->get_text(0), HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, draw_color);
|
||||
|
||||
draw_color.a = 0.9f;
|
||||
float value_position = rect.size.width - graph_font->get_string_size(current.item->get_text(1), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
|
||||
if (value_position < 0) {
|
||||
value_position = 0;
|
||||
}
|
||||
monitor_draw->draw_string(graph_font, rect.position + Point2(value_position, graph_font->get_ascent(font_size)), current.item->get_text(1), HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, draw_color);
|
||||
|
||||
rect.position.y += graph_font->get_height(font_size);
|
||||
rect.size.height -= graph_font->get_height(font_size);
|
||||
|
||||
int line_count = rect.size.height / (graph_font->get_height(font_size) * 2);
|
||||
if (line_count > 5) {
|
||||
line_count = 5;
|
||||
}
|
||||
if (line_count > 0) {
|
||||
Color horizontal_line_color;
|
||||
horizontal_line_color.set_hsv(draw_color.get_h(), draw_color.get_s() * 0.5f, draw_color.get_v() * 0.5f, 0.3f);
|
||||
monitor_draw->draw_line(rect.position, rect.position + Vector2(rect.size.width, 0), horizontal_line_color, Math::round(EDSCALE));
|
||||
monitor_draw->draw_string(graph_font, rect.position + Vector2(0, graph_font->get_ascent(font_size)), _create_label(current.max, current.type), HORIZONTAL_ALIGNMENT_LEFT, rect.size.width, font_size, horizontal_line_color);
|
||||
|
||||
for (int j = 0; j < line_count; j++) {
|
||||
Vector2 y_offset = Vector2(0, rect.size.height * (1.0f - float(j) / float(line_count)));
|
||||
monitor_draw->draw_line(rect.position + y_offset, rect.position + Vector2(rect.size.width, 0) + y_offset, horizontal_line_color, Math::round(EDSCALE));
|
||||
monitor_draw->draw_string(graph_font, rect.position - Vector2(0, graph_font->get_descent(font_size)) + y_offset, _create_label(current.max * float(j) / float(line_count), current.type), HORIZONTAL_ALIGNMENT_LEFT, rect.size.width, font_size, horizontal_line_color);
|
||||
}
|
||||
}
|
||||
|
||||
float from = rect.size.width;
|
||||
float prev = -1.0f;
|
||||
int count = 0;
|
||||
List<float>::Element *e = current.history.front();
|
||||
|
||||
while (from >= 0 && e) {
|
||||
float m = current.max;
|
||||
float h2 = 0;
|
||||
if (m != 0) {
|
||||
h2 = (e->get() / m);
|
||||
}
|
||||
h2 = (1.0f - h2) * float(rect.size.y);
|
||||
if (e != current.history.front()) {
|
||||
monitor_draw->draw_line(rect.position + Point2(from, h2), rect.position + Point2(from + spacing, prev), draw_color, Math::round(EDSCALE));
|
||||
}
|
||||
|
||||
if (marker_key == active[i] && count == marker_frame) {
|
||||
Color line_color;
|
||||
line_color.set_hsv(draw_color.get_h(), draw_color.get_s() * 0.8f, draw_color.get_v(), 0.5f);
|
||||
monitor_draw->draw_line(rect.position + Point2(from, 0), rect.position + Point2(from, rect.size.y), line_color, Math::round(EDSCALE));
|
||||
|
||||
String label = _create_label(e->get(), current.type);
|
||||
Size2 size = graph_font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
|
||||
Vector2 text_top_left_position = Vector2(from, h2) - (size + Vector2(MARKER_MARGIN, MARKER_MARGIN));
|
||||
if (text_top_left_position.x < 0) {
|
||||
text_top_left_position.x = from + MARKER_MARGIN;
|
||||
}
|
||||
if (text_top_left_position.y < 0) {
|
||||
text_top_left_position.y = h2 + MARKER_MARGIN;
|
||||
}
|
||||
monitor_draw->draw_string(graph_font, rect.position + text_top_left_position + Point2(0, graph_font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_LEFT, rect.size.x, font_size, line_color);
|
||||
}
|
||||
prev = h2;
|
||||
e = e->next();
|
||||
from -= spacing;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::_build_monitor_tree() {
|
||||
HashSet<StringName> monitor_checked;
|
||||
for (KeyValue<StringName, Monitor> &E : monitors) {
|
||||
if (E.value.item && E.value.item->is_checked(0)) {
|
||||
monitor_checked.insert(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
base_map.clear();
|
||||
monitor_tree->get_root()->clear_children();
|
||||
|
||||
for (KeyValue<StringName, Monitor> &E : monitors) {
|
||||
TreeItem *base = _get_monitor_base(E.value.base);
|
||||
TreeItem *item = _create_monitor_item(E.value.name, base);
|
||||
item->set_checked(0, monitor_checked.has(E.key));
|
||||
E.value.item = item;
|
||||
if (!E.value.history.is_empty()) {
|
||||
E.value.update_value(E.value.history.front()->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *EditorPerformanceProfiler::_get_monitor_base(const StringName &p_base_name) {
|
||||
if (base_map.has(p_base_name)) {
|
||||
return base_map[p_base_name];
|
||||
}
|
||||
|
||||
TreeItem *base = monitor_tree->create_item(monitor_tree->get_root());
|
||||
base->set_text(0, EditorPropertyNameProcessor::get_singleton()->process_name(p_base_name, EditorPropertyNameProcessor::get_settings_style()));
|
||||
base->set_editable(0, false);
|
||||
base->set_selectable(0, false);
|
||||
base->set_expand_right(0, true);
|
||||
if (is_inside_tree()) {
|
||||
base->set_custom_font(0, get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
}
|
||||
base_map.insert(p_base_name, base);
|
||||
return base;
|
||||
}
|
||||
|
||||
TreeItem *EditorPerformanceProfiler::_create_monitor_item(const StringName &p_monitor_name, TreeItem *p_base) {
|
||||
TreeItem *item = monitor_tree->create_item(p_base);
|
||||
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_editable(0, true);
|
||||
item->set_selectable(0, false);
|
||||
item->set_selectable(1, false);
|
||||
item->set_text(0, EditorPropertyNameProcessor::get_singleton()->process_name(p_monitor_name, EditorPropertyNameProcessor::get_settings_style()));
|
||||
return item;
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::_marker_input(const Ref<InputEvent> &p_event) {
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
|
||||
Vector<StringName> active;
|
||||
for (KeyValue<StringName, Monitor> &E : monitors) {
|
||||
if (E.value.item->is_checked(0)) {
|
||||
active.push_back(E.key);
|
||||
}
|
||||
}
|
||||
if (active.size() > 0) {
|
||||
int columns = int(Math::ceil(Math::sqrt(float(active.size()))));
|
||||
int rows = int(Math::ceil(float(active.size()) / float(columns)));
|
||||
if (active.size() == 1) {
|
||||
rows = 1;
|
||||
}
|
||||
Size2i cell_size = Size2i(monitor_draw->get_size()) / Size2i(columns, rows);
|
||||
Vector2i index = mb->get_position() / cell_size;
|
||||
Rect2i rect(index * cell_size + Point2i(MARGIN, MARGIN), cell_size - Point2i(MARGIN, MARGIN) * 2);
|
||||
if (rect.has_point(mb->get_position())) {
|
||||
if (index.x + index.y * columns < active.size()) {
|
||||
marker_key = active[index.x + index.y * columns];
|
||||
} else {
|
||||
marker_key = "";
|
||||
}
|
||||
Ref<StyleBox> graph_style_box = get_theme_stylebox(CoreStringName(normal), SNAME("TextEdit"));
|
||||
rect.position += graph_style_box->get_offset();
|
||||
rect.size -= graph_style_box->get_minimum_size();
|
||||
Vector2 point = mb->get_position() - rect.position;
|
||||
if (point.x >= rect.size.x) {
|
||||
marker_frame = 0;
|
||||
} else {
|
||||
int point_sep = 5;
|
||||
float spacing = float(point_sep) / float(columns);
|
||||
marker_frame = (rect.size.x - point.x) / spacing;
|
||||
}
|
||||
monitor_draw->queue_redraw();
|
||||
return;
|
||||
}
|
||||
}
|
||||
marker_key = "";
|
||||
monitor_draw->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::reset() {
|
||||
HashMap<StringName, Monitor>::Iterator E = monitors.begin();
|
||||
while (E != monitors.end()) {
|
||||
HashMap<StringName, Monitor>::Iterator N = E;
|
||||
++N;
|
||||
if (String(E->key).begins_with("custom:")) {
|
||||
monitors.remove(E);
|
||||
} else {
|
||||
E->value.reset();
|
||||
}
|
||||
E = N;
|
||||
}
|
||||
|
||||
_build_monitor_tree();
|
||||
marker_key = "";
|
||||
marker_frame = 0;
|
||||
monitor_draw->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::update_monitors(const Vector<StringName> &p_names) {
|
||||
HashMap<StringName, int> names;
|
||||
for (int i = 0; i < p_names.size(); i++) {
|
||||
names.insert("custom:" + p_names[i], Performance::MONITOR_MAX + i);
|
||||
}
|
||||
|
||||
{
|
||||
HashMap<StringName, Monitor>::Iterator E = monitors.begin();
|
||||
while (E != monitors.end()) {
|
||||
HashMap<StringName, Monitor>::Iterator N = E;
|
||||
++N;
|
||||
if (String(E->key).begins_with("custom:")) {
|
||||
if (!names.has(E->key)) {
|
||||
monitors.remove(E);
|
||||
} else {
|
||||
E->value.frame_index = names[E->key];
|
||||
names.erase(E->key);
|
||||
}
|
||||
}
|
||||
E = N;
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<StringName, int> &E : names) {
|
||||
String name = String(E.key).replace_first("custom:", "");
|
||||
String base = "Custom";
|
||||
if (name.get_slice_count("/") == 2) {
|
||||
base = name.get_slicec('/', 0);
|
||||
name = name.get_slicec('/', 1);
|
||||
}
|
||||
monitors.insert(E.key, Monitor(name, base, E.value, Performance::MONITOR_TYPE_QUANTITY, nullptr));
|
||||
}
|
||||
|
||||
_build_monitor_tree();
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::add_profile_frame(const Vector<float> &p_values) {
|
||||
for (KeyValue<StringName, Monitor> &E : monitors) {
|
||||
float value = 0.0f;
|
||||
if (E.value.frame_index >= 0 && E.value.frame_index < p_values.size()) {
|
||||
value = p_values[E.value.frame_index];
|
||||
}
|
||||
E.value.history.push_front(value);
|
||||
E.value.update_value(value);
|
||||
}
|
||||
marker_frame++;
|
||||
monitor_draw->queue_redraw();
|
||||
}
|
||||
|
||||
List<float> *EditorPerformanceProfiler::get_monitor_data(const StringName &p_name) {
|
||||
if (monitors.has(p_name)) {
|
||||
return &monitors[p_name].history;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EditorPerformanceProfiler::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
for (KeyValue<StringName, TreeItem *> &E : base_map) {
|
||||
E.value->set_custom_font(0, get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
|
||||
}
|
||||
} break;
|
||||
|
||||
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
||||
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) {
|
||||
_build_monitor_tree();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorPerformanceProfiler::EditorPerformanceProfiler() {
|
||||
set_name(TTR("Monitors"));
|
||||
set_split_offset(340 * EDSCALE);
|
||||
|
||||
monitor_tree = memnew(Tree);
|
||||
monitor_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
monitor_tree->set_columns(2);
|
||||
monitor_tree->set_column_title(0, TTR("Monitor"));
|
||||
monitor_tree->set_column_title(1, TTR("Value"));
|
||||
monitor_tree->set_column_titles_visible(true);
|
||||
monitor_tree->connect("item_edited", callable_mp(this, &EditorPerformanceProfiler::_monitor_select));
|
||||
monitor_tree->create_item();
|
||||
monitor_tree->set_hide_root(true);
|
||||
add_child(monitor_tree);
|
||||
|
||||
monitor_draw = memnew(Control);
|
||||
monitor_draw->set_clip_contents(true);
|
||||
monitor_draw->connect(SceneStringName(draw), callable_mp(this, &EditorPerformanceProfiler::_monitor_draw));
|
||||
monitor_draw->connect(SceneStringName(gui_input), callable_mp(this, &EditorPerformanceProfiler::_marker_input));
|
||||
add_child(monitor_draw);
|
||||
|
||||
info_message = memnew(Label);
|
||||
info_message->set_text(TTR("Pick one or more items from the list to display the graph."));
|
||||
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
info_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
info_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
|
||||
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
|
||||
monitor_draw->add_child(info_message);
|
||||
|
||||
for (int i = 0; i < Performance::MONITOR_MAX; i++) {
|
||||
const Performance::Monitor monitor = Performance::Monitor(i);
|
||||
const String path = Performance::get_singleton()->get_monitor_name(monitor);
|
||||
const String base = path.get_slicec('/', 0);
|
||||
const String name = path.get_slicec('/', 1);
|
||||
monitors.insert(path, Monitor(name, base, i, Performance::get_singleton()->get_monitor_type(monitor), nullptr));
|
||||
}
|
||||
|
||||
_build_monitor_tree();
|
||||
}
|
||||
93
engine/editor/debugger/editor_performance_profiler.h
Normal file
93
engine/editor/debugger/editor_performance_profiler.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**************************************************************************/
|
||||
/* editor_performance_profiler.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_PERFORMANCE_PROFILER_H
|
||||
#define EDITOR_PERFORMANCE_PROFILER_H
|
||||
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/rb_map.h"
|
||||
#include "main/performance.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorPerformanceProfiler : public HSplitContainer {
|
||||
GDCLASS(EditorPerformanceProfiler, HSplitContainer);
|
||||
|
||||
private:
|
||||
class Monitor {
|
||||
public:
|
||||
String name;
|
||||
String base;
|
||||
List<float> history;
|
||||
float max = 0.0f;
|
||||
TreeItem *item = nullptr;
|
||||
Performance::MonitorType type = Performance::MONITOR_TYPE_QUANTITY;
|
||||
int frame_index = 0;
|
||||
|
||||
Monitor();
|
||||
Monitor(const String &p_name, const String &p_base, int p_frame_index, Performance::MonitorType p_type, TreeItem *p_item);
|
||||
void update_value(float p_value);
|
||||
void reset();
|
||||
};
|
||||
|
||||
HashMap<StringName, Monitor> monitors;
|
||||
|
||||
HashMap<StringName, TreeItem *> base_map;
|
||||
Tree *monitor_tree = nullptr;
|
||||
Control *monitor_draw = nullptr;
|
||||
Label *info_message = nullptr;
|
||||
StringName marker_key;
|
||||
int marker_frame = 0;
|
||||
const int MARGIN = 4;
|
||||
const int POINT_SEPARATION = 5;
|
||||
const int MARKER_MARGIN = 2;
|
||||
|
||||
static String _create_label(float p_value, Performance::MonitorType p_type);
|
||||
void _monitor_select();
|
||||
void _monitor_draw();
|
||||
void _build_monitor_tree();
|
||||
TreeItem *_get_monitor_base(const StringName &p_base_name);
|
||||
TreeItem *_create_monitor_item(const StringName &p_monitor_name, TreeItem *p_base);
|
||||
void _marker_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void reset();
|
||||
void update_monitors(const Vector<StringName> &p_names);
|
||||
void add_profile_frame(const Vector<float> &p_values);
|
||||
List<float> *get_monitor_data(const StringName &p_name);
|
||||
EditorPerformanceProfiler();
|
||||
};
|
||||
|
||||
#endif // EDITOR_PERFORMANCE_PROFILER_H
|
||||
731
engine/editor/debugger/editor_profiler.cpp
Normal file
731
engine/editor/debugger/editor_profiler.cpp
Normal file
|
|
@ -0,0 +1,731 @@
|
|||
/**************************************************************************/
|
||||
/* editor_profiler.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_profiler.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "editor/themes/editor_theme_manager.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
void EditorProfiler::_make_metric_ptrs(Metric &m) {
|
||||
for (int i = 0; i < m.categories.size(); i++) {
|
||||
m.category_ptrs[m.categories[i].signature] = &m.categories.write[i];
|
||||
for (int j = 0; j < m.categories[i].items.size(); j++) {
|
||||
m.item_ptrs[m.categories[i].items[j].signature] = &m.categories.write[i].items.write[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorProfiler::Metric EditorProfiler::_get_frame_metric(int index) {
|
||||
return frame_metrics[(frame_metrics.size() + last_metric - (total_metrics - 1) + index) % frame_metrics.size()];
|
||||
}
|
||||
|
||||
void EditorProfiler::add_frame_metric(const Metric &p_metric, bool p_final) {
|
||||
++last_metric;
|
||||
if (last_metric >= frame_metrics.size()) {
|
||||
last_metric = 0;
|
||||
}
|
||||
|
||||
total_metrics++;
|
||||
if (total_metrics > frame_metrics.size()) {
|
||||
total_metrics = frame_metrics.size();
|
||||
}
|
||||
|
||||
frame_metrics.write[last_metric] = p_metric;
|
||||
_make_metric_ptrs(frame_metrics.write[last_metric]);
|
||||
|
||||
updating_frame = true;
|
||||
clear_button->set_disabled(false);
|
||||
cursor_metric_edit->set_editable(true);
|
||||
cursor_metric_edit->set_max(p_metric.frame_number);
|
||||
cursor_metric_edit->set_min(_get_frame_metric(0).frame_number);
|
||||
|
||||
if (!seeking) {
|
||||
cursor_metric_edit->set_value(p_metric.frame_number);
|
||||
}
|
||||
|
||||
updating_frame = false;
|
||||
|
||||
if (frame_delay->is_stopped()) {
|
||||
frame_delay->set_wait_time(p_final ? 0.1 : 1);
|
||||
frame_delay->start();
|
||||
}
|
||||
|
||||
if (plot_delay->is_stopped()) {
|
||||
plot_delay->set_wait_time(0.1);
|
||||
plot_delay->start();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::clear() {
|
||||
int metric_size = EDITOR_GET("debugger/profiler_frame_history_size");
|
||||
metric_size = CLAMP(metric_size, 60, 10000);
|
||||
frame_metrics.clear();
|
||||
frame_metrics.resize(metric_size);
|
||||
total_metrics = 0;
|
||||
last_metric = -1;
|
||||
variables->clear();
|
||||
plot_sigs.clear();
|
||||
plot_sigs.insert("physics_frame_time");
|
||||
plot_sigs.insert("category_frame_time");
|
||||
display_internal_profiles->set_visible(EDITOR_GET("debugger/profile_native_calls"));
|
||||
|
||||
updating_frame = true;
|
||||
cursor_metric_edit->set_min(0);
|
||||
cursor_metric_edit->set_max(100); // Doesn't make much sense, but we can't have min == max. Doesn't hurt.
|
||||
cursor_metric_edit->set_value(0);
|
||||
cursor_metric_edit->set_editable(false);
|
||||
updating_frame = false;
|
||||
hover_metric = -1;
|
||||
seeking = false;
|
||||
|
||||
// Ensure button text (start, stop) is correct
|
||||
_update_button_text();
|
||||
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
|
||||
}
|
||||
|
||||
static String _get_percent_txt(float p_value, float p_total) {
|
||||
if (p_total == 0) {
|
||||
p_total = 0.00001;
|
||||
}
|
||||
|
||||
return TS->format_number(String::num((p_value / p_total) * 100, 1)) + TS->percent_sign();
|
||||
}
|
||||
|
||||
String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_calls) {
|
||||
const int dmode = display_mode->get_selected();
|
||||
|
||||
if (dmode == DISPLAY_FRAME_TIME) {
|
||||
return TS->format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + TTR("ms");
|
||||
} else if (dmode == DISPLAY_AVERAGE_TIME) {
|
||||
if (p_calls == 0) {
|
||||
return TS->format_number("0.00") + " " + TTR("ms");
|
||||
} else {
|
||||
return TS->format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + TTR("ms");
|
||||
}
|
||||
} else if (dmode == DISPLAY_FRAME_PERCENT) {
|
||||
return _get_percent_txt(p_time, m.frame_time);
|
||||
} else if (dmode == DISPLAY_PHYSICS_FRAME_PERCENT) {
|
||||
return _get_percent_txt(p_time, m.physics_frame_time);
|
||||
}
|
||||
|
||||
return "err";
|
||||
}
|
||||
|
||||
Color EditorProfiler::_get_color_from_signature(const StringName &p_signature) const {
|
||||
Color bc = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
|
||||
double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF));
|
||||
Color c;
|
||||
c.set_hsv(rot, bc.get_s(), bc.get_v());
|
||||
return c.lerp(get_theme_color(SNAME("base_color"), EditorStringName(Editor)), 0.07);
|
||||
}
|
||||
|
||||
void EditorProfiler::_item_edited() {
|
||||
if (updating_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = variables->get_edited();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
StringName signature = item->get_metadata(0);
|
||||
bool checked = item->is_checked(0);
|
||||
|
||||
if (checked) {
|
||||
plot_sigs.insert(signature);
|
||||
} else {
|
||||
plot_sigs.erase(signature);
|
||||
}
|
||||
|
||||
if (!frame_delay->is_processing()) {
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->start();
|
||||
}
|
||||
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorProfiler::_update_plot() {
|
||||
const int w = graph->get_size().width;
|
||||
const int h = graph->get_size().height;
|
||||
bool reset_texture = false;
|
||||
const int desired_len = w * h * 4;
|
||||
|
||||
if (graph_image.size() != desired_len) {
|
||||
reset_texture = true;
|
||||
graph_image.resize(desired_len);
|
||||
}
|
||||
|
||||
uint8_t *wr = graph_image.ptrw();
|
||||
const Color background_color = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor));
|
||||
|
||||
// Clear the previous frame and set the background color.
|
||||
for (int i = 0; i < desired_len; i += 4) {
|
||||
wr[i + 0] = Math::fast_ftoi(background_color.r * 255);
|
||||
wr[i + 1] = Math::fast_ftoi(background_color.g * 255);
|
||||
wr[i + 2] = Math::fast_ftoi(background_color.b * 255);
|
||||
wr[i + 3] = 255;
|
||||
}
|
||||
|
||||
//find highest value
|
||||
|
||||
const bool use_self = display_time->get_selected() == DISPLAY_SELF_TIME;
|
||||
float highest = 0;
|
||||
|
||||
for (int i = 0; i < total_metrics; i++) {
|
||||
const Metric &m = _get_frame_metric(i);
|
||||
|
||||
for (const StringName &E : plot_sigs) {
|
||||
HashMap<StringName, Metric::Category *>::ConstIterator F = m.category_ptrs.find(E);
|
||||
if (F) {
|
||||
highest = MAX(F->value->total_time, highest);
|
||||
}
|
||||
|
||||
HashMap<StringName, Metric::Category::Item *>::ConstIterator G = m.item_ptrs.find(E);
|
||||
if (G) {
|
||||
if (use_self) {
|
||||
highest = MAX(G->value->self, highest);
|
||||
} else {
|
||||
highest = MAX(G->value->total, highest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (highest > 0) {
|
||||
//means some data exists..
|
||||
highest *= 1.2; //leave some upper room
|
||||
graph_height = highest;
|
||||
|
||||
Vector<int> columnv;
|
||||
columnv.resize(h * 4);
|
||||
|
||||
int *column = columnv.ptrw();
|
||||
|
||||
HashMap<StringName, int> prev_plots;
|
||||
|
||||
for (int i = 0; i < total_metrics * w / frame_metrics.size() - 1; i++) {
|
||||
for (int j = 0; j < h * 4; j++) {
|
||||
column[j] = 0;
|
||||
}
|
||||
|
||||
int current = i * frame_metrics.size() / w;
|
||||
|
||||
for (const StringName &E : plot_sigs) {
|
||||
const Metric &m = _get_frame_metric(current);
|
||||
|
||||
float value = 0;
|
||||
|
||||
HashMap<StringName, Metric::Category *>::ConstIterator F = m.category_ptrs.find(E);
|
||||
if (F) {
|
||||
value = F->value->total_time;
|
||||
}
|
||||
|
||||
HashMap<StringName, Metric::Category::Item *>::ConstIterator G = m.item_ptrs.find(E);
|
||||
if (G) {
|
||||
if (use_self) {
|
||||
value = G->value->self;
|
||||
} else {
|
||||
value = G->value->total;
|
||||
}
|
||||
}
|
||||
|
||||
int plot_pos = CLAMP(int(value * h / highest), 0, h - 1);
|
||||
|
||||
int prev_plot = plot_pos;
|
||||
HashMap<StringName, int>::Iterator H = prev_plots.find(E);
|
||||
if (H) {
|
||||
prev_plot = H->value;
|
||||
H->value = plot_pos;
|
||||
} else {
|
||||
prev_plots[E] = plot_pos;
|
||||
}
|
||||
|
||||
plot_pos = h - plot_pos - 1;
|
||||
prev_plot = h - prev_plot - 1;
|
||||
|
||||
if (prev_plot > plot_pos) {
|
||||
SWAP(prev_plot, plot_pos);
|
||||
}
|
||||
|
||||
Color col = _get_color_from_signature(E);
|
||||
|
||||
for (int j = prev_plot; j <= plot_pos; j++) {
|
||||
column[j * 4 + 0] += Math::fast_ftoi(CLAMP(col.r * 255, 0, 255));
|
||||
column[j * 4 + 1] += Math::fast_ftoi(CLAMP(col.g * 255, 0, 255));
|
||||
column[j * 4 + 2] += Math::fast_ftoi(CLAMP(col.b * 255, 0, 255));
|
||||
column[j * 4 + 3] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < h * 4; j += 4) {
|
||||
const int a = column[j + 3];
|
||||
if (a > 0) {
|
||||
column[j + 0] /= a;
|
||||
column[j + 1] /= a;
|
||||
column[j + 2] /= a;
|
||||
}
|
||||
|
||||
const uint8_t red = uint8_t(column[j + 0]);
|
||||
const uint8_t green = uint8_t(column[j + 1]);
|
||||
const uint8_t blue = uint8_t(column[j + 2]);
|
||||
const bool is_filled = red >= 1 || green >= 1 || blue >= 1;
|
||||
const int widx = ((j >> 2) * w + i) * 4;
|
||||
|
||||
// If the pixel isn't filled by any profiler line, apply the background color instead.
|
||||
wr[widx + 0] = is_filled ? red : Math::fast_ftoi(background_color.r * 255);
|
||||
wr[widx + 1] = is_filled ? green : Math::fast_ftoi(background_color.g * 255);
|
||||
wr[widx + 2] = is_filled ? blue : Math::fast_ftoi(background_color.b * 255);
|
||||
wr[widx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> img = Image::create_from_data(w, h, false, Image::FORMAT_RGBA8, graph_image);
|
||||
|
||||
if (reset_texture) {
|
||||
if (graph_texture.is_null()) {
|
||||
graph_texture.instantiate();
|
||||
}
|
||||
graph_texture->set_image(img);
|
||||
}
|
||||
|
||||
graph_texture->update(img);
|
||||
|
||||
graph->set_texture(graph_texture);
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorProfiler::_update_frame() {
|
||||
int cursor_metric = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number;
|
||||
|
||||
updating_frame = true;
|
||||
variables->clear();
|
||||
|
||||
TreeItem *root = variables->create_item();
|
||||
const Metric &m = _get_frame_metric(cursor_metric);
|
||||
|
||||
int dtime = display_time->get_selected();
|
||||
|
||||
for (int i = 0; i < m.categories.size(); i++) {
|
||||
TreeItem *category = variables->create_item(root);
|
||||
category->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
category->set_editable(0, true);
|
||||
category->set_metadata(0, m.categories[i].signature);
|
||||
category->set_text(0, String(m.categories[i].name));
|
||||
category->set_text(1, _get_time_as_text(m, m.categories[i].total_time, 1));
|
||||
|
||||
if (plot_sigs.has(m.categories[i].signature)) {
|
||||
category->set_checked(0, true);
|
||||
category->set_custom_color(0, _get_color_from_signature(m.categories[i].signature));
|
||||
}
|
||||
|
||||
for (int j = 0; j < m.categories[i].items.size(); j++) {
|
||||
const Metric::Category::Item &it = m.categories[i].items[j];
|
||||
|
||||
if (it.internal == it.total && !display_internal_profiles->is_pressed() && m.categories[i].name == "Script Functions") {
|
||||
continue;
|
||||
}
|
||||
TreeItem *item = variables->create_item(category);
|
||||
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_editable(0, true);
|
||||
item->set_text(0, it.name);
|
||||
item->set_metadata(0, it.signature);
|
||||
item->set_metadata(1, it.script);
|
||||
item->set_metadata(2, it.line);
|
||||
item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
item->set_tooltip_text(0, it.name + "\n" + it.script + ":" + itos(it.line));
|
||||
|
||||
float time = dtime == DISPLAY_SELF_TIME ? it.self : it.total;
|
||||
if (dtime == DISPLAY_SELF_TIME && !display_internal_profiles->is_pressed()) {
|
||||
time += it.internal;
|
||||
}
|
||||
|
||||
item->set_text(1, _get_time_as_text(m, time, it.calls));
|
||||
|
||||
item->set_text(2, itos(it.calls));
|
||||
|
||||
if (plot_sigs.has(it.signature)) {
|
||||
item->set_checked(0, true);
|
||||
item->set_custom_color(0, _get_color_from_signature(it.signature));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updating_frame = false;
|
||||
}
|
||||
|
||||
void EditorProfiler::_update_button_text() {
|
||||
if (activate->is_pressed()) {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Stop")));
|
||||
activate->set_text(TTR("Stop"));
|
||||
} else {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Play")));
|
||||
activate->set_text(TTR("Start"));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::_activate_pressed() {
|
||||
_update_button_text();
|
||||
|
||||
if (activate->is_pressed()) {
|
||||
_clear_pressed();
|
||||
}
|
||||
|
||||
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
|
||||
}
|
||||
|
||||
void EditorProfiler::_clear_pressed() {
|
||||
clear_button->set_disabled(true);
|
||||
clear();
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorProfiler::_internal_profiles_pressed() {
|
||||
_combo_changed(0);
|
||||
}
|
||||
|
||||
void EditorProfiler::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
||||
case NOTIFICATION_THEME_CHANGED:
|
||||
case NOTIFICATION_TRANSLATION_CHANGED: {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Play")));
|
||||
clear_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
|
||||
|
||||
theme_cache.seek_line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
|
||||
theme_cache.seek_line_color.a = 0.8;
|
||||
theme_cache.seek_line_hover_color = theme_cache.seek_line_color;
|
||||
theme_cache.seek_line_hover_color.a = 0.4;
|
||||
|
||||
if (total_metrics > 0) {
|
||||
_update_plot();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::_graph_tex_draw() {
|
||||
if (total_metrics == 0) {
|
||||
return;
|
||||
}
|
||||
if (seeking) {
|
||||
int frame = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number;
|
||||
int cur_x = (2 * frame + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1;
|
||||
graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_color);
|
||||
}
|
||||
if (hover_metric > -1 && hover_metric < total_metrics) {
|
||||
int cur_x = (2 * hover_metric + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1;
|
||||
graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_hover_color);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::_graph_tex_mouse_exit() {
|
||||
hover_metric = -1;
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorProfiler::_cursor_metric_changed(double) {
|
||||
if (updating_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
graph->queue_redraw();
|
||||
_update_frame();
|
||||
}
|
||||
|
||||
void EditorProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) {
|
||||
if (last_metric < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouse> me = p_ev;
|
||||
Ref<InputEventMouseButton> mb = p_ev;
|
||||
Ref<InputEventMouseMotion> mm = p_ev;
|
||||
|
||||
if (
|
||||
(mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) ||
|
||||
(mm.is_valid())) {
|
||||
int x = me->get_position().x - 1;
|
||||
x = x * frame_metrics.size() / graph->get_size().width;
|
||||
|
||||
hover_metric = x;
|
||||
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (x >= frame_metrics.size()) {
|
||||
x = frame_metrics.size() - 1;
|
||||
}
|
||||
|
||||
if (mb.is_valid() || (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
|
||||
updating_frame = true;
|
||||
|
||||
if (x < total_metrics) {
|
||||
cursor_metric_edit->set_value(_get_frame_metric(x).frame_number);
|
||||
}
|
||||
updating_frame = false;
|
||||
|
||||
if (activate->is_pressed()) {
|
||||
if (!seeking) {
|
||||
emit_signal(SNAME("break_request"));
|
||||
}
|
||||
}
|
||||
|
||||
seeking = true;
|
||||
|
||||
if (!frame_delay->is_processing()) {
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->start();
|
||||
}
|
||||
}
|
||||
|
||||
graph->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::disable_seeking() {
|
||||
seeking = false;
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorProfiler::_combo_changed(int) {
|
||||
_update_frame();
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorProfiler::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
|
||||
ADD_SIGNAL(MethodInfo("break_request"));
|
||||
}
|
||||
|
||||
void EditorProfiler::set_enabled(bool p_enable, bool p_clear) {
|
||||
activate->set_disabled(!p_enable);
|
||||
if (p_clear) {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorProfiler::set_pressed(bool p_pressed) {
|
||||
activate->set_pressed(p_pressed);
|
||||
_update_button_text();
|
||||
}
|
||||
|
||||
bool EditorProfiler::is_profiling() {
|
||||
return activate->is_pressed();
|
||||
}
|
||||
|
||||
Vector<Vector<String>> EditorProfiler::get_data_as_csv() const {
|
||||
Vector<Vector<String>> res;
|
||||
|
||||
if (frame_metrics.is_empty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Different metrics may contain different number of categories.
|
||||
HashSet<StringName> possible_signatures;
|
||||
for (int i = 0; i < frame_metrics.size(); i++) {
|
||||
const Metric &m = frame_metrics[i];
|
||||
if (!m.valid) {
|
||||
continue;
|
||||
}
|
||||
for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) {
|
||||
possible_signatures.insert(E.key);
|
||||
}
|
||||
for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) {
|
||||
possible_signatures.insert(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate CSV header and cache indices.
|
||||
HashMap<StringName, int> sig_map;
|
||||
Vector<String> signatures;
|
||||
signatures.resize(possible_signatures.size());
|
||||
int sig_index = 0;
|
||||
for (const StringName &E : possible_signatures) {
|
||||
signatures.write[sig_index] = E;
|
||||
sig_map[E] = sig_index;
|
||||
sig_index++;
|
||||
}
|
||||
res.push_back(signatures);
|
||||
|
||||
// values
|
||||
Vector<String> values;
|
||||
|
||||
int index = last_metric;
|
||||
|
||||
for (int i = 0; i < frame_metrics.size(); i++) {
|
||||
++index;
|
||||
|
||||
if (index >= frame_metrics.size()) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
const Metric &m = frame_metrics[index];
|
||||
|
||||
if (!m.valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't keep old values since there may be empty cells.
|
||||
values.clear();
|
||||
values.resize(possible_signatures.size());
|
||||
|
||||
for (const KeyValue<StringName, Metric::Category *> &E : m.category_ptrs) {
|
||||
values.write[sig_map[E.key]] = String::num_real(E.value->total_time);
|
||||
}
|
||||
for (const KeyValue<StringName, Metric::Category::Item *> &E : m.item_ptrs) {
|
||||
values.write[sig_map[E.key]] = String::num_real(E.value->total);
|
||||
}
|
||||
|
||||
res.push_back(values);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
EditorProfiler::EditorProfiler() {
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
add_child(hb);
|
||||
activate = memnew(Button);
|
||||
activate->set_toggle_mode(true);
|
||||
activate->set_disabled(true);
|
||||
activate->set_text(TTR("Start"));
|
||||
activate->connect(SceneStringName(pressed), callable_mp(this, &EditorProfiler::_activate_pressed));
|
||||
hb->add_child(activate);
|
||||
|
||||
clear_button = memnew(Button);
|
||||
clear_button->set_text(TTR("Clear"));
|
||||
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorProfiler::_clear_pressed));
|
||||
clear_button->set_disabled(true);
|
||||
hb->add_child(clear_button);
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Measure:"))));
|
||||
|
||||
display_mode = memnew(OptionButton);
|
||||
display_mode->add_item(TTR("Frame Time (ms)"));
|
||||
display_mode->add_item(TTR("Average Time (ms)"));
|
||||
display_mode->add_item(TTR("Frame %"));
|
||||
display_mode->add_item(TTR("Physics Frame %"));
|
||||
display_mode->connect(SceneStringName(item_selected), callable_mp(this, &EditorProfiler::_combo_changed));
|
||||
|
||||
hb->add_child(display_mode);
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Time:"))));
|
||||
|
||||
display_time = memnew(OptionButton);
|
||||
// TRANSLATORS: This is an option in the profiler to display the time spent in a function, including the time spent in other functions called by that function.
|
||||
display_time->add_item(TTR("Inclusive"));
|
||||
// TRANSLATORS: This is an option in the profiler to display the time spent in a function, exincluding the time spent in other functions called by that function.
|
||||
display_time->add_item(TTR("Self"));
|
||||
display_time->set_tooltip_text(TTR("Inclusive: Includes time from other functions called by this function.\nUse this to spot bottlenecks.\n\nSelf: Only count the time spent in the function itself, not in other functions called by that function.\nUse this to find individual functions to optimize."));
|
||||
display_time->connect(SceneStringName(item_selected), callable_mp(this, &EditorProfiler::_combo_changed));
|
||||
|
||||
hb->add_child(display_time);
|
||||
|
||||
display_internal_profiles = memnew(CheckButton(TTR("Display internal functions")));
|
||||
display_internal_profiles->set_visible(EDITOR_GET("debugger/profile_native_calls"));
|
||||
display_internal_profiles->set_pressed(false);
|
||||
display_internal_profiles->connect(SceneStringName(pressed), callable_mp(this, &EditorProfiler::_internal_profiles_pressed));
|
||||
hb->add_child(display_internal_profiles);
|
||||
|
||||
hb->add_spacer();
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Frame #:"))));
|
||||
|
||||
cursor_metric_edit = memnew(SpinBox);
|
||||
cursor_metric_edit->set_h_size_flags(SIZE_FILL);
|
||||
cursor_metric_edit->set_value(0);
|
||||
cursor_metric_edit->set_editable(false);
|
||||
hb->add_child(cursor_metric_edit);
|
||||
cursor_metric_edit->connect(SceneStringName(value_changed), callable_mp(this, &EditorProfiler::_cursor_metric_changed));
|
||||
|
||||
hb->add_theme_constant_override("separation", 8 * EDSCALE);
|
||||
|
||||
h_split = memnew(HSplitContainer);
|
||||
add_child(h_split);
|
||||
h_split->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
variables = memnew(Tree);
|
||||
variables->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
variables->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
|
||||
variables->set_hide_folding(true);
|
||||
h_split->add_child(variables);
|
||||
variables->set_hide_root(true);
|
||||
variables->set_columns(3);
|
||||
variables->set_column_titles_visible(true);
|
||||
variables->set_column_title(0, TTR("Name"));
|
||||
variables->set_column_expand(0, true);
|
||||
variables->set_column_clip_content(0, true);
|
||||
variables->set_column_custom_minimum_width(0, 60);
|
||||
variables->set_column_title(1, TTR("Time"));
|
||||
variables->set_column_expand(1, false);
|
||||
variables->set_column_clip_content(1, true);
|
||||
variables->set_column_custom_minimum_width(1, 75 * EDSCALE);
|
||||
variables->set_column_title(2, TTR("Calls"));
|
||||
variables->set_column_expand(2, false);
|
||||
variables->set_column_clip_content(2, true);
|
||||
variables->set_column_custom_minimum_width(2, 50 * EDSCALE);
|
||||
variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited));
|
||||
|
||||
graph = memnew(TextureRect);
|
||||
graph->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
graph->set_mouse_filter(MOUSE_FILTER_STOP);
|
||||
graph->connect(SceneStringName(draw), callable_mp(this, &EditorProfiler::_graph_tex_draw));
|
||||
graph->connect(SceneStringName(gui_input), callable_mp(this, &EditorProfiler::_graph_tex_input));
|
||||
graph->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorProfiler::_graph_tex_mouse_exit));
|
||||
|
||||
h_split->add_child(graph);
|
||||
graph->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
int metric_size = CLAMP(int(EDITOR_GET("debugger/profiler_frame_history_size")), 60, 10000);
|
||||
frame_metrics.resize(metric_size);
|
||||
|
||||
frame_delay = memnew(Timer);
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->set_one_shot(true);
|
||||
add_child(frame_delay);
|
||||
frame_delay->connect("timeout", callable_mp(this, &EditorProfiler::_update_frame));
|
||||
|
||||
plot_delay = memnew(Timer);
|
||||
plot_delay->set_wait_time(0.1);
|
||||
plot_delay->set_one_shot(true);
|
||||
add_child(plot_delay);
|
||||
plot_delay->connect("timeout", callable_mp(this, &EditorProfiler::_update_plot));
|
||||
|
||||
plot_sigs.insert("physics_frame_time");
|
||||
plot_sigs.insert("category_frame_time");
|
||||
}
|
||||
183
engine/editor/debugger/editor_profiler.h
Normal file
183
engine/editor/debugger/editor_profiler.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/**************************************************************************/
|
||||
/* editor_profiler.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_PROFILER_H
|
||||
#define EDITOR_PROFILER_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class ImageTexture;
|
||||
|
||||
class EditorProfiler : public VBoxContainer {
|
||||
GDCLASS(EditorProfiler, VBoxContainer);
|
||||
|
||||
public:
|
||||
struct Metric {
|
||||
bool valid = false;
|
||||
|
||||
int frame_number = 0;
|
||||
float frame_time = 0;
|
||||
float process_time = 0;
|
||||
float physics_time = 0;
|
||||
float physics_frame_time = 0;
|
||||
|
||||
struct Category {
|
||||
StringName signature;
|
||||
String name;
|
||||
float total_time = 0; //total for category
|
||||
|
||||
struct Item {
|
||||
StringName signature;
|
||||
String name;
|
||||
String script;
|
||||
int line = 0;
|
||||
float self = 0;
|
||||
float total = 0;
|
||||
float internal = 0;
|
||||
int calls = 0;
|
||||
};
|
||||
|
||||
Vector<Item> items;
|
||||
};
|
||||
|
||||
Vector<Category> categories;
|
||||
|
||||
HashMap<StringName, Category *> category_ptrs;
|
||||
HashMap<StringName, Category::Item *> item_ptrs;
|
||||
};
|
||||
|
||||
enum DisplayMode {
|
||||
DISPLAY_FRAME_TIME,
|
||||
DISPLAY_AVERAGE_TIME,
|
||||
DISPLAY_FRAME_PERCENT,
|
||||
DISPLAY_PHYSICS_FRAME_PERCENT,
|
||||
};
|
||||
|
||||
enum DisplayTime {
|
||||
DISPLAY_TOTAL_TIME,
|
||||
DISPLAY_SELF_TIME,
|
||||
};
|
||||
|
||||
private:
|
||||
struct ThemeCache {
|
||||
Color seek_line_color;
|
||||
Color seek_line_hover_color;
|
||||
} theme_cache;
|
||||
|
||||
Button *activate = nullptr;
|
||||
Button *clear_button = nullptr;
|
||||
TextureRect *graph = nullptr;
|
||||
Ref<ImageTexture> graph_texture;
|
||||
Vector<uint8_t> graph_image;
|
||||
Tree *variables = nullptr;
|
||||
HSplitContainer *h_split = nullptr;
|
||||
|
||||
HashSet<StringName> plot_sigs;
|
||||
|
||||
OptionButton *display_mode = nullptr;
|
||||
OptionButton *display_time = nullptr;
|
||||
|
||||
CheckButton *display_internal_profiles = nullptr;
|
||||
|
||||
SpinBox *cursor_metric_edit = nullptr;
|
||||
|
||||
Vector<Metric> frame_metrics;
|
||||
int total_metrics = 0;
|
||||
int last_metric = -1;
|
||||
|
||||
int max_functions = 0;
|
||||
|
||||
bool updating_frame = false;
|
||||
|
||||
int hover_metric = -1;
|
||||
|
||||
float graph_height = 1.0f;
|
||||
|
||||
bool seeking = false;
|
||||
|
||||
Timer *frame_delay = nullptr;
|
||||
Timer *plot_delay = nullptr;
|
||||
|
||||
void _update_button_text();
|
||||
void _update_frame();
|
||||
|
||||
void _activate_pressed();
|
||||
void _clear_pressed();
|
||||
|
||||
void _internal_profiles_pressed();
|
||||
|
||||
String _get_time_as_text(const Metric &m, float p_time, int p_calls);
|
||||
|
||||
void _make_metric_ptrs(Metric &m);
|
||||
void _item_edited();
|
||||
|
||||
void _update_plot();
|
||||
|
||||
void _graph_tex_mouse_exit();
|
||||
|
||||
void _graph_tex_draw();
|
||||
void _graph_tex_input(const Ref<InputEvent> &p_ev);
|
||||
|
||||
Color _get_color_from_signature(const StringName &p_signature) const;
|
||||
|
||||
void _cursor_metric_changed(double);
|
||||
|
||||
void _combo_changed(int);
|
||||
|
||||
Metric _get_frame_metric(int index);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void add_frame_metric(const Metric &p_metric, bool p_final = false);
|
||||
void set_enabled(bool p_enable, bool p_clear = true);
|
||||
void set_pressed(bool p_pressed);
|
||||
bool is_profiling();
|
||||
bool is_seeking() { return seeking; }
|
||||
void disable_seeking();
|
||||
|
||||
void clear();
|
||||
|
||||
Vector<Vector<String>> get_data_as_csv() const;
|
||||
|
||||
EditorProfiler();
|
||||
};
|
||||
|
||||
#endif // EDITOR_PROFILER_H
|
||||
826
engine/editor/debugger/editor_visual_profiler.cpp
Normal file
826
engine/editor/debugger/editor_visual_profiler.cpp
Normal file
|
|
@ -0,0 +1,826 @@
|
|||
/**************************************************************************/
|
||||
/* editor_visual_profiler.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_visual_profiler.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
void EditorVisualProfiler::add_frame_metric(const Metric &p_metric) {
|
||||
++last_metric;
|
||||
if (last_metric >= frame_metrics.size()) {
|
||||
last_metric = 0;
|
||||
}
|
||||
|
||||
frame_metrics.write[last_metric] = p_metric;
|
||||
|
||||
List<String> stack;
|
||||
for (int i = 0; i < frame_metrics[last_metric].areas.size(); i++) {
|
||||
String name = frame_metrics[last_metric].areas[i].name;
|
||||
frame_metrics.write[last_metric].areas.write[i].color_cache = _get_color_from_signature(name);
|
||||
String full_name;
|
||||
|
||||
if (name[0] == '<') {
|
||||
stack.pop_back();
|
||||
}
|
||||
|
||||
if (stack.size()) {
|
||||
full_name = stack.back()->get() + name;
|
||||
} else {
|
||||
full_name = name;
|
||||
}
|
||||
|
||||
if (name[0] == '>') {
|
||||
stack.push_back(full_name + "/");
|
||||
}
|
||||
|
||||
frame_metrics.write[last_metric].areas.write[i].fullpath_cache = full_name;
|
||||
}
|
||||
|
||||
updating_frame = true;
|
||||
clear_button->set_disabled(false);
|
||||
cursor_metric_edit->set_max(frame_metrics[last_metric].frame_number);
|
||||
cursor_metric_edit->set_min(MAX(int64_t(frame_metrics[last_metric].frame_number) - frame_metrics.size(), 0));
|
||||
|
||||
if (!seeking) {
|
||||
cursor_metric_edit->set_value(frame_metrics[last_metric].frame_number);
|
||||
if (hover_metric != -1) {
|
||||
hover_metric++;
|
||||
if (hover_metric >= frame_metrics.size()) {
|
||||
hover_metric = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
updating_frame = false;
|
||||
|
||||
if (frame_delay->is_stopped()) {
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->start();
|
||||
}
|
||||
|
||||
if (plot_delay->is_stopped()) {
|
||||
plot_delay->set_wait_time(0.1);
|
||||
plot_delay->start();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::clear() {
|
||||
int metric_size = EDITOR_GET("debugger/profiler_frame_history_size");
|
||||
metric_size = CLAMP(metric_size, 60, 10000);
|
||||
frame_metrics.clear();
|
||||
frame_metrics.resize(metric_size);
|
||||
last_metric = -1;
|
||||
variables->clear();
|
||||
//activate->set_pressed(false);
|
||||
|
||||
updating_frame = true;
|
||||
cursor_metric_edit->set_min(0);
|
||||
cursor_metric_edit->set_max(0);
|
||||
cursor_metric_edit->set_value(0);
|
||||
updating_frame = false;
|
||||
hover_metric = -1;
|
||||
seeking = false;
|
||||
}
|
||||
|
||||
String EditorVisualProfiler::_get_time_as_text(float p_time) {
|
||||
int dmode = display_mode->get_selected();
|
||||
|
||||
if (dmode == DISPLAY_FRAME_TIME) {
|
||||
return TS->format_number(String::num(p_time, 2)) + " " + TTR("ms");
|
||||
} else if (dmode == DISPLAY_FRAME_PERCENT) {
|
||||
return TS->format_number(String::num(p_time * 100 / graph_limit, 2)) + " " + TS->percent_sign();
|
||||
}
|
||||
|
||||
return "err";
|
||||
}
|
||||
|
||||
Color EditorVisualProfiler::_get_color_from_signature(const StringName &p_signature) const {
|
||||
Color bc = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
|
||||
double rot = ABS(double(p_signature.hash()) / double(0x7FFFFFFF));
|
||||
Color c;
|
||||
c.set_hsv(rot, bc.get_s(), bc.get_v());
|
||||
return c.lerp(get_theme_color(SNAME("base_color"), EditorStringName(Editor)), 0.07);
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_item_selected() {
|
||||
if (updating_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = variables->get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
selected_area = item->get_metadata(0);
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_update_plot() {
|
||||
const int w = graph->get_size().width;
|
||||
const int h = graph->get_size().height;
|
||||
|
||||
bool reset_texture = false;
|
||||
|
||||
const int desired_len = w * h * 4;
|
||||
|
||||
if (graph_image.size() != desired_len) {
|
||||
reset_texture = true;
|
||||
graph_image.resize(desired_len);
|
||||
}
|
||||
|
||||
uint8_t *wr = graph_image.ptrw();
|
||||
const Color background_color = get_theme_color("dark_color_2", EditorStringName(Editor));
|
||||
|
||||
// Clear the previous frame and set the background color.
|
||||
for (int i = 0; i < desired_len; i += 4) {
|
||||
wr[i + 0] = Math::fast_ftoi(background_color.r * 255);
|
||||
wr[i + 1] = Math::fast_ftoi(background_color.g * 255);
|
||||
wr[i + 2] = Math::fast_ftoi(background_color.b * 255);
|
||||
wr[i + 3] = 255;
|
||||
}
|
||||
|
||||
//find highest value
|
||||
|
||||
float highest_cpu = 0;
|
||||
float highest_gpu = 0;
|
||||
|
||||
for (int i = 0; i < frame_metrics.size(); i++) {
|
||||
const Metric &m = frame_metrics[i];
|
||||
if (!m.valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.areas.size()) {
|
||||
highest_cpu = MAX(highest_cpu, m.areas[m.areas.size() - 1].cpu_time);
|
||||
highest_gpu = MAX(highest_gpu, m.areas[m.areas.size() - 1].gpu_time);
|
||||
}
|
||||
}
|
||||
|
||||
if (highest_cpu > 0 || highest_gpu > 0) {
|
||||
if (frame_relative->is_pressed()) {
|
||||
highest_cpu = MAX(graph_limit, highest_cpu);
|
||||
highest_gpu = MAX(graph_limit, highest_gpu);
|
||||
}
|
||||
|
||||
if (linked->is_pressed()) {
|
||||
float highest = MAX(highest_cpu, highest_gpu);
|
||||
highest_cpu = highest_gpu = highest;
|
||||
}
|
||||
|
||||
//means some data exists..
|
||||
highest_cpu *= 1.2; //leave some upper room
|
||||
highest_gpu *= 1.2; //leave some upper room
|
||||
graph_height_cpu = highest_cpu;
|
||||
graph_height_gpu = highest_gpu;
|
||||
|
||||
Vector<Color> columnv_cpu;
|
||||
columnv_cpu.resize(h);
|
||||
Color *column_cpu = columnv_cpu.ptrw();
|
||||
|
||||
Vector<Color> columnv_gpu;
|
||||
columnv_gpu.resize(h);
|
||||
Color *column_gpu = columnv_gpu.ptrw();
|
||||
|
||||
int half_w = w / 2;
|
||||
for (int i = 0; i < half_w; i++) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
column_cpu[j] = Color(0, 0, 0, 0);
|
||||
column_gpu[j] = Color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int current = i * frame_metrics.size() / half_w;
|
||||
int next = (i + 1) * frame_metrics.size() / half_w;
|
||||
if (next > frame_metrics.size()) {
|
||||
next = frame_metrics.size();
|
||||
}
|
||||
if (next == current) {
|
||||
next = current + 1; //just because for loop must work
|
||||
}
|
||||
|
||||
for (int j = current; j < next; j++) {
|
||||
//wrap
|
||||
int idx = last_metric + 1 + j;
|
||||
while (idx >= frame_metrics.size()) {
|
||||
idx -= frame_metrics.size();
|
||||
}
|
||||
|
||||
int area_count = frame_metrics[idx].areas.size();
|
||||
const Metric::Area *areas = frame_metrics[idx].areas.ptr();
|
||||
int prev_cpu = 0;
|
||||
int prev_gpu = 0;
|
||||
for (int k = 1; k < area_count; k++) {
|
||||
int ofs_cpu = int(areas[k].cpu_time * h / highest_cpu);
|
||||
ofs_cpu = CLAMP(ofs_cpu, 0, h - 1);
|
||||
Color color = selected_area == areas[k - 1].fullpath_cache ? Color(1, 1, 1, 1) : areas[k - 1].color_cache;
|
||||
|
||||
for (int l = prev_cpu; l < ofs_cpu; l++) {
|
||||
column_cpu[h - l - 1] += color;
|
||||
}
|
||||
prev_cpu = ofs_cpu;
|
||||
|
||||
int ofs_gpu = int(areas[k].gpu_time * h / highest_gpu);
|
||||
ofs_gpu = CLAMP(ofs_gpu, 0, h - 1);
|
||||
for (int l = prev_gpu; l < ofs_gpu; l++) {
|
||||
column_gpu[h - l - 1] += color;
|
||||
}
|
||||
|
||||
prev_gpu = ofs_gpu;
|
||||
}
|
||||
}
|
||||
|
||||
//plot CPU
|
||||
for (int j = 0; j < h; j++) {
|
||||
uint8_t r, g, b;
|
||||
|
||||
if (column_cpu[j].a == 0) {
|
||||
r = Math::fast_ftoi(background_color.r * 255);
|
||||
g = Math::fast_ftoi(background_color.g * 255);
|
||||
b = Math::fast_ftoi(background_color.b * 255);
|
||||
} else {
|
||||
r = CLAMP((column_cpu[j].r / column_cpu[j].a) * 255.0, 0, 255);
|
||||
g = CLAMP((column_cpu[j].g / column_cpu[j].a) * 255.0, 0, 255);
|
||||
b = CLAMP((column_cpu[j].b / column_cpu[j].a) * 255.0, 0, 255);
|
||||
}
|
||||
|
||||
int widx = (j * w + i) * 4;
|
||||
wr[widx + 0] = r;
|
||||
wr[widx + 1] = g;
|
||||
wr[widx + 2] = b;
|
||||
wr[widx + 3] = 255;
|
||||
}
|
||||
//plot GPU
|
||||
for (int j = 0; j < h; j++) {
|
||||
uint8_t r, g, b;
|
||||
|
||||
if (column_gpu[j].a == 0) {
|
||||
r = Math::fast_ftoi(background_color.r * 255);
|
||||
g = Math::fast_ftoi(background_color.g * 255);
|
||||
b = Math::fast_ftoi(background_color.b * 255);
|
||||
} else {
|
||||
r = CLAMP((column_gpu[j].r / column_gpu[j].a) * 255.0, 0, 255);
|
||||
g = CLAMP((column_gpu[j].g / column_gpu[j].a) * 255.0, 0, 255);
|
||||
b = CLAMP((column_gpu[j].b / column_gpu[j].a) * 255.0, 0, 255);
|
||||
}
|
||||
|
||||
int widx = (j * w + w / 2 + i) * 4;
|
||||
wr[widx + 0] = r;
|
||||
wr[widx + 1] = g;
|
||||
wr[widx + 2] = b;
|
||||
wr[widx + 3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> img = Image::create_from_data(w, h, false, Image::FORMAT_RGBA8, graph_image);
|
||||
|
||||
if (reset_texture) {
|
||||
if (graph_texture.is_null()) {
|
||||
graph_texture.instantiate();
|
||||
}
|
||||
graph_texture->set_image(img);
|
||||
}
|
||||
|
||||
graph_texture->update(img);
|
||||
|
||||
graph->set_texture(graph_texture);
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_update_frame(bool p_focus_selected) {
|
||||
int cursor_metric = _get_cursor_index();
|
||||
|
||||
Ref<Texture> track_icon = get_editor_theme_icon(SNAME("TrackColor"));
|
||||
|
||||
ERR_FAIL_INDEX(cursor_metric, frame_metrics.size());
|
||||
|
||||
updating_frame = true;
|
||||
variables->clear();
|
||||
|
||||
TreeItem *root = variables->create_item();
|
||||
const Metric &m = frame_metrics[cursor_metric];
|
||||
|
||||
List<TreeItem *> stack;
|
||||
List<TreeItem *> categories;
|
||||
|
||||
TreeItem *ensure_selected = nullptr;
|
||||
|
||||
for (int i = 1; i < m.areas.size() - 1; i++) {
|
||||
TreeItem *parent = stack.size() ? stack.back()->get() : root;
|
||||
|
||||
String name = m.areas[i].name;
|
||||
|
||||
float cpu_time = m.areas[i].cpu_time;
|
||||
float gpu_time = m.areas[i].gpu_time;
|
||||
if (i < m.areas.size() - 1) {
|
||||
cpu_time = m.areas[i + 1].cpu_time - cpu_time;
|
||||
gpu_time = m.areas[i + 1].gpu_time - gpu_time;
|
||||
}
|
||||
|
||||
if (name.begins_with(">")) {
|
||||
TreeItem *category = variables->create_item(parent);
|
||||
|
||||
stack.push_back(category);
|
||||
categories.push_back(category);
|
||||
|
||||
name = name.substr(1, name.length());
|
||||
|
||||
category->set_text(0, name);
|
||||
category->set_metadata(1, cpu_time);
|
||||
category->set_metadata(2, gpu_time);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.begins_with("<")) {
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
TreeItem *category = variables->create_item(parent);
|
||||
|
||||
for (TreeItem *E : stack) {
|
||||
float total_cpu = E->get_metadata(1);
|
||||
float total_gpu = E->get_metadata(2);
|
||||
total_cpu += cpu_time;
|
||||
total_gpu += gpu_time;
|
||||
E->set_metadata(1, total_cpu);
|
||||
E->set_metadata(2, total_gpu);
|
||||
}
|
||||
|
||||
category->set_icon(0, track_icon);
|
||||
category->set_icon_modulate(0, m.areas[i].color_cache);
|
||||
category->set_selectable(0, true);
|
||||
category->set_metadata(0, m.areas[i].fullpath_cache);
|
||||
category->set_text(0, m.areas[i].name);
|
||||
category->set_text(1, _get_time_as_text(cpu_time));
|
||||
category->set_metadata(1, m.areas[i].cpu_time);
|
||||
category->set_text(2, _get_time_as_text(gpu_time));
|
||||
category->set_metadata(2, m.areas[i].gpu_time);
|
||||
|
||||
if (selected_area == m.areas[i].fullpath_cache) {
|
||||
category->select(0);
|
||||
if (p_focus_selected) {
|
||||
ensure_selected = category;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (TreeItem *E : categories) {
|
||||
float total_cpu = E->get_metadata(1);
|
||||
float total_gpu = E->get_metadata(2);
|
||||
E->set_text(1, _get_time_as_text(total_cpu));
|
||||
E->set_text(2, _get_time_as_text(total_gpu));
|
||||
}
|
||||
|
||||
if (ensure_selected) {
|
||||
variables->ensure_cursor_is_visible();
|
||||
}
|
||||
updating_frame = false;
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_activate_pressed() {
|
||||
if (activate->is_pressed()) {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Stop")));
|
||||
activate->set_text(TTR("Stop"));
|
||||
_clear_pressed(); //always clear on start
|
||||
clear_button->set_disabled(false);
|
||||
} else {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Play")));
|
||||
activate->set_text(TTR("Start"));
|
||||
}
|
||||
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_clear_pressed() {
|
||||
clear_button->set_disabled(true);
|
||||
clear();
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
||||
case NOTIFICATION_THEME_CHANGED:
|
||||
case NOTIFICATION_TRANSLATION_CHANGED: {
|
||||
if (is_layout_rtl()) {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
|
||||
} else {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Play")));
|
||||
}
|
||||
clear_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_graph_tex_draw() {
|
||||
if (last_metric < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
|
||||
const Color color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));
|
||||
|
||||
if (seeking) {
|
||||
int max_frames = frame_metrics.size();
|
||||
int frame = cursor_metric_edit->get_value() - (frame_metrics[last_metric].frame_number - max_frames + 1);
|
||||
if (frame < 0) {
|
||||
frame = 0;
|
||||
}
|
||||
|
||||
int half_width = graph->get_size().x / 2;
|
||||
int cur_x = frame * half_width / max_frames;
|
||||
|
||||
graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), color * Color(1, 1, 1));
|
||||
graph->draw_line(Vector2(cur_x + half_width, 0), Vector2(cur_x + half_width, graph->get_size().y), color * Color(1, 1, 1));
|
||||
}
|
||||
|
||||
if (graph_height_cpu > 0) {
|
||||
int frame_y = graph->get_size().y - graph_limit * graph->get_size().y / graph_height_cpu - 1;
|
||||
|
||||
int half_width = graph->get_size().x / 2;
|
||||
|
||||
graph->draw_line(Vector2(0, frame_y), Vector2(half_width, frame_y), color * Color(1, 1, 1, 0.5));
|
||||
|
||||
const String limit_str = String::num(graph_limit, 2) + " ms";
|
||||
graph->draw_string(font, Vector2(half_width - font->get_string_size(limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x - 2, frame_y - 2), limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1, 0.75));
|
||||
}
|
||||
|
||||
if (graph_height_gpu > 0) {
|
||||
int frame_y = graph->get_size().y - graph_limit * graph->get_size().y / graph_height_gpu - 1;
|
||||
|
||||
int half_width = graph->get_size().x / 2;
|
||||
|
||||
graph->draw_line(Vector2(half_width, frame_y), Vector2(graph->get_size().x, frame_y), color * Color(1, 1, 1, 0.5));
|
||||
|
||||
const String limit_str = String::num(graph_limit, 2) + " ms";
|
||||
graph->draw_string(font, Vector2(half_width * 2 - font->get_string_size(limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x - 2, frame_y - 2), limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1, 0.75));
|
||||
}
|
||||
|
||||
graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x, font->get_ascent(font_size) + 2), "CPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1));
|
||||
graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + graph->get_size().width / 2, font->get_ascent(font_size) + 2), "GPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1));
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_graph_tex_mouse_exit() {
|
||||
hover_metric = -1;
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_cursor_metric_changed(double) {
|
||||
if (updating_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
graph->queue_redraw();
|
||||
_update_frame();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_graph_tex_input(const Ref<InputEvent> &p_ev) {
|
||||
if (last_metric < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouse> me = p_ev;
|
||||
Ref<InputEventMouseButton> mb = p_ev;
|
||||
Ref<InputEventMouseMotion> mm = p_ev;
|
||||
|
||||
if (
|
||||
(mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) ||
|
||||
(mm.is_valid())) {
|
||||
int half_w = graph->get_size().width / 2;
|
||||
int x = me->get_position().x;
|
||||
if (x > half_w) {
|
||||
x -= half_w;
|
||||
}
|
||||
x = x * frame_metrics.size() / half_w;
|
||||
|
||||
bool show_hover = x >= 0 && x < frame_metrics.size();
|
||||
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (x >= frame_metrics.size()) {
|
||||
x = frame_metrics.size() - 1;
|
||||
}
|
||||
|
||||
int metric = frame_metrics.size() - x - 1;
|
||||
metric = last_metric - metric;
|
||||
while (metric < 0) {
|
||||
metric += frame_metrics.size();
|
||||
}
|
||||
|
||||
if (show_hover) {
|
||||
hover_metric = metric;
|
||||
|
||||
} else {
|
||||
hover_metric = -1;
|
||||
}
|
||||
|
||||
if (mb.is_valid() || mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
|
||||
//cursor_metric=x;
|
||||
updating_frame = true;
|
||||
|
||||
//metric may be invalid, so look for closest metric that is valid, this makes snap feel better
|
||||
bool valid = false;
|
||||
for (int i = 0; i < frame_metrics.size(); i++) {
|
||||
if (frame_metrics[metric].valid) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
metric++;
|
||||
if (metric >= frame_metrics.size()) {
|
||||
metric = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
cursor_metric_edit->set_value(frame_metrics[metric].frame_number);
|
||||
|
||||
updating_frame = false;
|
||||
|
||||
if (activate->is_pressed()) {
|
||||
if (!seeking) {
|
||||
// Break request is not required, just stop profiling
|
||||
}
|
||||
}
|
||||
|
||||
seeking = true;
|
||||
|
||||
if (!frame_delay->is_processing()) {
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->start();
|
||||
}
|
||||
|
||||
bool touched_cpu = me->get_position().x < graph->get_size().width * 0.5;
|
||||
|
||||
const Metric::Area *areas = frame_metrics[metric].areas.ptr();
|
||||
int area_count = frame_metrics[metric].areas.size();
|
||||
float posy = (1.0 - (me->get_position().y / graph->get_size().height)) * (touched_cpu ? graph_height_cpu : graph_height_gpu);
|
||||
int last_valid = -1;
|
||||
bool found = false;
|
||||
for (int i = 0; i < area_count - 1; i++) {
|
||||
if (areas[i].name[0] != '<' && areas[i].name[0] != '>') {
|
||||
last_valid = i;
|
||||
}
|
||||
float h = touched_cpu ? areas[i + 1].cpu_time : areas[i + 1].gpu_time;
|
||||
|
||||
if (h > posy) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StringName area_found;
|
||||
if (found && last_valid != -1) {
|
||||
area_found = areas[last_valid].fullpath_cache;
|
||||
}
|
||||
|
||||
if (area_found != selected_area) {
|
||||
selected_area = area_found;
|
||||
_update_frame(true);
|
||||
_update_plot();
|
||||
}
|
||||
}
|
||||
|
||||
graph->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
int EditorVisualProfiler::_get_cursor_index() const {
|
||||
if (last_metric < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!frame_metrics[last_metric].valid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int diff = (frame_metrics[last_metric].frame_number - cursor_metric_edit->get_value());
|
||||
|
||||
int idx = last_metric - diff;
|
||||
while (idx < 0) {
|
||||
idx += frame_metrics.size();
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::disable_seeking() {
|
||||
seeking = false;
|
||||
graph->queue_redraw();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_combo_changed(int) {
|
||||
_update_frame();
|
||||
_update_plot();
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::_update_button_text() {
|
||||
if (activate->is_pressed()) {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Stop")));
|
||||
activate->set_text(TTR("Stop"));
|
||||
} else {
|
||||
activate->set_icon(get_editor_theme_icon(SNAME("Play")));
|
||||
activate->set_text(TTR("Start"));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::set_enabled(bool p_enable) {
|
||||
activate->set_disabled(!p_enable);
|
||||
}
|
||||
|
||||
void EditorVisualProfiler::set_pressed(bool p_pressed) {
|
||||
activate->set_pressed(p_pressed);
|
||||
_update_button_text();
|
||||
}
|
||||
|
||||
bool EditorVisualProfiler::is_profiling() {
|
||||
return activate->is_pressed();
|
||||
}
|
||||
|
||||
Vector<Vector<String>> EditorVisualProfiler::get_data_as_csv() const {
|
||||
Vector<Vector<String>> res;
|
||||
#if 0
|
||||
if (frame_metrics.is_empty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// signatures
|
||||
Vector<String> signatures;
|
||||
const Vector<EditorFrameProfiler::Metric::Category> &categories = frame_metrics[0].categories;
|
||||
|
||||
for (int j = 0; j < categories.size(); j++) {
|
||||
const EditorFrameProfiler::Metric::Category &c = categories[j];
|
||||
signatures.push_back(c.signature);
|
||||
|
||||
for (int k = 0; k < c.items.size(); k++) {
|
||||
signatures.push_back(c.items[k].signature);
|
||||
}
|
||||
}
|
||||
res.push_back(signatures);
|
||||
|
||||
// values
|
||||
Vector<String> values;
|
||||
values.resize(signatures.size());
|
||||
|
||||
int index = last_metric;
|
||||
|
||||
for (int i = 0; i < frame_metrics.size(); i++) {
|
||||
++index;
|
||||
|
||||
if (index >= frame_metrics.size()) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (!frame_metrics[index].valid) {
|
||||
continue;
|
||||
}
|
||||
int it = 0;
|
||||
const Vector<EditorFrameProfiler::Metric::Category> &frame_cat = frame_metrics[index].categories;
|
||||
|
||||
for (int j = 0; j < frame_cat.size(); j++) {
|
||||
const EditorFrameProfiler::Metric::Category &c = frame_cat[j];
|
||||
values.write[it++] = String::num_real(c.total_time);
|
||||
|
||||
for (int k = 0; k < c.items.size(); k++) {
|
||||
values.write[it++] = String::num_real(c.items[k].total);
|
||||
}
|
||||
}
|
||||
res.push_back(values);
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
EditorVisualProfiler::EditorVisualProfiler() {
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
add_child(hb);
|
||||
activate = memnew(Button);
|
||||
activate->set_toggle_mode(true);
|
||||
activate->set_disabled(true);
|
||||
activate->set_text(TTR("Start"));
|
||||
activate->connect(SceneStringName(pressed), callable_mp(this, &EditorVisualProfiler::_activate_pressed));
|
||||
hb->add_child(activate);
|
||||
|
||||
clear_button = memnew(Button);
|
||||
clear_button->set_text(TTR("Clear"));
|
||||
clear_button->set_disabled(true);
|
||||
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorVisualProfiler::_clear_pressed));
|
||||
hb->add_child(clear_button);
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Measure:"))));
|
||||
|
||||
display_mode = memnew(OptionButton);
|
||||
display_mode->add_item(TTR("Frame Time (ms)"));
|
||||
display_mode->add_item(TTR("Frame %"));
|
||||
display_mode->connect(SceneStringName(item_selected), callable_mp(this, &EditorVisualProfiler::_combo_changed));
|
||||
|
||||
hb->add_child(display_mode);
|
||||
|
||||
frame_relative = memnew(CheckBox(TTR("Fit to Frame")));
|
||||
frame_relative->set_pressed(true);
|
||||
hb->add_child(frame_relative);
|
||||
frame_relative->connect(SceneStringName(pressed), callable_mp(this, &EditorVisualProfiler::_update_plot));
|
||||
linked = memnew(CheckBox(TTR("Linked")));
|
||||
linked->set_pressed(true);
|
||||
hb->add_child(linked);
|
||||
linked->connect(SceneStringName(pressed), callable_mp(this, &EditorVisualProfiler::_update_plot));
|
||||
|
||||
hb->add_spacer();
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Frame #:"))));
|
||||
|
||||
cursor_metric_edit = memnew(SpinBox);
|
||||
cursor_metric_edit->set_h_size_flags(SIZE_FILL);
|
||||
hb->add_child(cursor_metric_edit);
|
||||
cursor_metric_edit->connect(SceneStringName(value_changed), callable_mp(this, &EditorVisualProfiler::_cursor_metric_changed));
|
||||
|
||||
hb->add_theme_constant_override("separation", 8 * EDSCALE);
|
||||
|
||||
h_split = memnew(HSplitContainer);
|
||||
add_child(h_split);
|
||||
h_split->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
variables = memnew(Tree);
|
||||
variables->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
|
||||
variables->set_hide_folding(true);
|
||||
h_split->add_child(variables);
|
||||
variables->set_hide_root(true);
|
||||
variables->set_columns(3);
|
||||
variables->set_column_titles_visible(true);
|
||||
variables->set_column_title(0, TTR("Name"));
|
||||
variables->set_column_expand(0, true);
|
||||
variables->set_column_clip_content(0, true);
|
||||
variables->set_column_custom_minimum_width(0, 60);
|
||||
variables->set_column_title(1, TTR("CPU"));
|
||||
variables->set_column_expand(1, false);
|
||||
variables->set_column_clip_content(1, true);
|
||||
variables->set_column_custom_minimum_width(1, 75 * EDSCALE);
|
||||
variables->set_column_title(2, TTR("GPU"));
|
||||
variables->set_column_expand(2, false);
|
||||
variables->set_column_clip_content(2, true);
|
||||
variables->set_column_custom_minimum_width(2, 75 * EDSCALE);
|
||||
variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected));
|
||||
|
||||
graph = memnew(TextureRect);
|
||||
graph->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
graph->set_mouse_filter(MOUSE_FILTER_STOP);
|
||||
graph->connect(SceneStringName(draw), callable_mp(this, &EditorVisualProfiler::_graph_tex_draw));
|
||||
graph->connect(SceneStringName(gui_input), callable_mp(this, &EditorVisualProfiler::_graph_tex_input));
|
||||
graph->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorVisualProfiler::_graph_tex_mouse_exit));
|
||||
|
||||
h_split->add_child(graph);
|
||||
graph->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
int metric_size = CLAMP(int(EDITOR_GET("debugger/profiler_frame_history_size")), 60, 10000);
|
||||
frame_metrics.resize(metric_size);
|
||||
|
||||
frame_delay = memnew(Timer);
|
||||
frame_delay->set_wait_time(0.1);
|
||||
frame_delay->set_one_shot(true);
|
||||
add_child(frame_delay);
|
||||
frame_delay->connect("timeout", callable_mp(this, &EditorVisualProfiler::_update_frame).bind(false));
|
||||
|
||||
plot_delay = memnew(Timer);
|
||||
plot_delay->set_wait_time(0.1);
|
||||
plot_delay->set_one_shot(true);
|
||||
add_child(plot_delay);
|
||||
plot_delay->connect("timeout", callable_mp(this, &EditorVisualProfiler::_update_plot));
|
||||
}
|
||||
152
engine/editor/debugger/editor_visual_profiler.h
Normal file
152
engine/editor/debugger/editor_visual_profiler.h
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/**************************************************************************/
|
||||
/* editor_visual_profiler.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_VISUAL_PROFILER_H
|
||||
#define EDITOR_VISUAL_PROFILER_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class ImageTexture;
|
||||
|
||||
class EditorVisualProfiler : public VBoxContainer {
|
||||
GDCLASS(EditorVisualProfiler, VBoxContainer);
|
||||
|
||||
public:
|
||||
struct Metric {
|
||||
bool valid = false;
|
||||
|
||||
uint64_t frame_number = 0;
|
||||
|
||||
struct Area {
|
||||
String name;
|
||||
Color color_cache;
|
||||
StringName fullpath_cache;
|
||||
float cpu_time = 0;
|
||||
float gpu_time = 0;
|
||||
};
|
||||
|
||||
Vector<Area> areas;
|
||||
};
|
||||
|
||||
enum DisplayTimeMode {
|
||||
DISPLAY_FRAME_TIME,
|
||||
DISPLAY_FRAME_PERCENT,
|
||||
};
|
||||
|
||||
private:
|
||||
Button *activate = nullptr;
|
||||
Button *clear_button = nullptr;
|
||||
|
||||
TextureRect *graph = nullptr;
|
||||
Ref<ImageTexture> graph_texture;
|
||||
Vector<uint8_t> graph_image;
|
||||
Tree *variables = nullptr;
|
||||
HSplitContainer *h_split = nullptr;
|
||||
CheckBox *frame_relative = nullptr;
|
||||
CheckBox *linked = nullptr;
|
||||
|
||||
OptionButton *display_mode = nullptr;
|
||||
|
||||
SpinBox *cursor_metric_edit = nullptr;
|
||||
|
||||
Vector<Metric> frame_metrics;
|
||||
int last_metric = -1;
|
||||
|
||||
int hover_metric = -1;
|
||||
|
||||
StringName selected_area;
|
||||
|
||||
bool updating_frame = false;
|
||||
|
||||
float graph_height_cpu = 1.0f;
|
||||
float graph_height_gpu = 1.0f;
|
||||
|
||||
float graph_limit = 1000.0f / 60;
|
||||
|
||||
bool seeking = false;
|
||||
|
||||
Timer *frame_delay = nullptr;
|
||||
Timer *plot_delay = nullptr;
|
||||
|
||||
void _update_button_text();
|
||||
|
||||
void _update_frame(bool p_focus_selected = false);
|
||||
|
||||
void _activate_pressed();
|
||||
void _clear_pressed();
|
||||
|
||||
String _get_time_as_text(float p_time);
|
||||
|
||||
//void _make_metric_ptrs(Metric &m);
|
||||
void _item_selected();
|
||||
|
||||
void _update_plot();
|
||||
|
||||
void _graph_tex_mouse_exit();
|
||||
|
||||
void _graph_tex_draw();
|
||||
void _graph_tex_input(const Ref<InputEvent> &p_ev);
|
||||
|
||||
int _get_cursor_index() const;
|
||||
|
||||
Color _get_color_from_signature(const StringName &p_signature) const;
|
||||
|
||||
void _cursor_metric_changed(double);
|
||||
|
||||
void _combo_changed(int);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void add_frame_metric(const Metric &p_metric);
|
||||
void set_enabled(bool p_enable);
|
||||
void set_pressed(bool p_pressed);
|
||||
bool is_profiling();
|
||||
bool is_seeking() { return seeking; }
|
||||
void disable_seeking();
|
||||
|
||||
void clear();
|
||||
|
||||
Vector<Vector<String>> get_data_as_csv() const;
|
||||
|
||||
EditorVisualProfiler();
|
||||
};
|
||||
|
||||
#endif // EDITOR_VISUAL_PROFILER_H
|
||||
2139
engine/editor/debugger/script_editor_debugger.cpp
Normal file
2139
engine/editor/debugger/script_editor_debugger.cpp
Normal file
File diff suppressed because it is too large
Load diff
322
engine/editor/debugger/script_editor_debugger.h
Normal file
322
engine/editor/debugger/script_editor_debugger.h
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
/**************************************************************************/
|
||||
/* script_editor_debugger.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SCRIPT_EDITOR_DEBUGGER_H
|
||||
#define SCRIPT_EDITOR_DEBUGGER_H
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "editor/debugger/editor_debugger_inspector.h"
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/debugger/editor_debugger_server.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Tree;
|
||||
class LineEdit;
|
||||
class TabContainer;
|
||||
class RichTextLabel;
|
||||
class TextureButton;
|
||||
class AcceptDialog;
|
||||
class TreeItem;
|
||||
class HSplitContainer;
|
||||
class ItemList;
|
||||
class EditorProfiler;
|
||||
class EditorFileDialog;
|
||||
class EditorVisualProfiler;
|
||||
class EditorPerformanceProfiler;
|
||||
class SceneDebuggerTree;
|
||||
class EditorDebuggerPlugin;
|
||||
class DebugAdapterProtocol;
|
||||
class DebugAdapterParser;
|
||||
|
||||
class ScriptEditorDebugger : public MarginContainer {
|
||||
GDCLASS(ScriptEditorDebugger, MarginContainer);
|
||||
|
||||
friend class EditorDebuggerNode;
|
||||
friend class DebugAdapterProtocol;
|
||||
friend class DebugAdapterParser;
|
||||
|
||||
private:
|
||||
enum MessageType {
|
||||
MESSAGE_ERROR,
|
||||
MESSAGE_WARNING,
|
||||
MESSAGE_SUCCESS,
|
||||
};
|
||||
|
||||
enum ProfilerType {
|
||||
PROFILER_VISUAL,
|
||||
PROFILER_SCRIPTS_SERVERS
|
||||
};
|
||||
|
||||
enum Actions {
|
||||
ACTION_COPY_ERROR,
|
||||
ACTION_OPEN_SOURCE,
|
||||
ACTION_DELETE_BREAKPOINT,
|
||||
ACTION_DELETE_BREAKPOINTS_IN_FILE,
|
||||
ACTION_DELETE_ALL_BREAKPOINTS,
|
||||
};
|
||||
|
||||
AcceptDialog *msgdialog = nullptr;
|
||||
|
||||
LineEdit *clicked_ctrl = nullptr;
|
||||
LineEdit *clicked_ctrl_type = nullptr;
|
||||
LineEdit *live_edit_root = nullptr;
|
||||
Button *le_set = nullptr;
|
||||
Button *le_clear = nullptr;
|
||||
Button *export_csv = nullptr;
|
||||
|
||||
VBoxContainer *errors_tab = nullptr;
|
||||
Tree *error_tree = nullptr;
|
||||
Button *expand_all_button = nullptr;
|
||||
Button *collapse_all_button = nullptr;
|
||||
Button *clear_button = nullptr;
|
||||
PopupMenu *item_menu = nullptr;
|
||||
|
||||
Tree *breakpoints_tree = nullptr;
|
||||
PopupMenu *breakpoints_menu = nullptr;
|
||||
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
enum FileDialogPurpose {
|
||||
SAVE_MONITORS_CSV,
|
||||
SAVE_VRAM_CSV,
|
||||
};
|
||||
FileDialogPurpose file_dialog_purpose;
|
||||
|
||||
int error_count;
|
||||
int warning_count;
|
||||
|
||||
bool skip_breakpoints_value = false;
|
||||
Ref<Script> stack_script;
|
||||
|
||||
TabContainer *tabs = nullptr;
|
||||
|
||||
Label *reason = nullptr;
|
||||
|
||||
Button *skip_breakpoints = nullptr;
|
||||
Button *copy = nullptr;
|
||||
Button *step = nullptr;
|
||||
Button *next = nullptr;
|
||||
Button *dobreak = nullptr;
|
||||
Button *docontinue = nullptr;
|
||||
// Reference to "Remote" tab in scene tree. Needed by _live_edit_set and buttons state.
|
||||
// Each debugger should have it's tree in the future I guess.
|
||||
const Tree *editor_remote_tree = nullptr;
|
||||
|
||||
HashMap<int, String> profiler_signature;
|
||||
|
||||
Tree *vmem_tree = nullptr;
|
||||
Button *vmem_refresh = nullptr;
|
||||
Button *vmem_export = nullptr;
|
||||
LineEdit *vmem_total = nullptr;
|
||||
|
||||
Tree *stack_dump = nullptr;
|
||||
LineEdit *search = nullptr;
|
||||
OptionButton *threads = nullptr;
|
||||
EditorDebuggerInspector *inspector = nullptr;
|
||||
SceneDebuggerTree *scene_tree = nullptr;
|
||||
|
||||
Ref<RemoteDebuggerPeer> peer;
|
||||
|
||||
HashMap<NodePath, int> node_path_cache;
|
||||
int last_path_id;
|
||||
HashMap<String, int> res_path_cache;
|
||||
|
||||
EditorProfiler *profiler = nullptr;
|
||||
EditorVisualProfiler *visual_profiler = nullptr;
|
||||
EditorPerformanceProfiler *performance_profiler = nullptr;
|
||||
|
||||
OS::ProcessID remote_pid = 0;
|
||||
bool move_to_foreground = true;
|
||||
bool can_request_idle_draw = false;
|
||||
|
||||
bool live_debug;
|
||||
|
||||
uint64_t debugging_thread_id = Thread::UNASSIGNED_ID;
|
||||
|
||||
struct ThreadDebugged {
|
||||
String name;
|
||||
String error;
|
||||
bool can_debug = false;
|
||||
bool has_stackdump = false;
|
||||
uint32_t debug_order = 0;
|
||||
uint64_t thread_id = Thread::UNASSIGNED_ID; // for order
|
||||
};
|
||||
|
||||
struct ThreadSort {
|
||||
bool operator()(const ThreadDebugged *a, const ThreadDebugged *b) const {
|
||||
return a->debug_order < b->debug_order;
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<uint64_t, ThreadDebugged> threads_debugged;
|
||||
bool thread_list_updating = false;
|
||||
|
||||
void _select_thread(int p_index);
|
||||
|
||||
EditorDebuggerNode::CameraOverride camera_override;
|
||||
|
||||
void _stack_dump_frame_selected();
|
||||
|
||||
void _file_selected(const String &p_file);
|
||||
void _parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data);
|
||||
void _set_reason_text(const String &p_reason, MessageType p_type);
|
||||
void _update_buttons_state();
|
||||
void _remote_object_selected(ObjectID p_object);
|
||||
void _remote_object_edited(ObjectID, const String &p_prop, const Variant &p_value);
|
||||
void _remote_object_property_updated(ObjectID p_id, const String &p_property);
|
||||
|
||||
void _video_mem_request();
|
||||
void _video_mem_export();
|
||||
|
||||
int _get_node_path_cache(const NodePath &p_path);
|
||||
|
||||
int _get_res_path_cache(const String &p_path);
|
||||
|
||||
void _live_edit_set();
|
||||
void _live_edit_clear();
|
||||
|
||||
void _method_changed(Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
|
||||
void _property_changed(Object *p_base, const StringName &p_property, const Variant &p_value);
|
||||
|
||||
void _error_activated();
|
||||
void _error_selected();
|
||||
|
||||
void _expand_errors_list();
|
||||
void _collapse_errors_list();
|
||||
|
||||
void _profiler_activate(bool p_enable, int p_profiler);
|
||||
void _profiler_seeked();
|
||||
|
||||
void _clear_errors_list();
|
||||
|
||||
void _breakpoints_item_rmb_selected(const Vector2 &p_pos, MouseButton p_button);
|
||||
void _error_tree_item_rmb_selected(const Vector2 &p_pos, MouseButton p_button);
|
||||
void _item_menu_id_pressed(int p_option);
|
||||
void _tab_changed(int p_tab);
|
||||
|
||||
void _put_msg(const String &p_message, const Array &p_data, uint64_t p_thread_id = Thread::MAIN_ID);
|
||||
void _export_csv();
|
||||
|
||||
void _clear_execution();
|
||||
void _stop_and_notify();
|
||||
|
||||
void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled);
|
||||
void _clear_breakpoints();
|
||||
|
||||
void _breakpoint_tree_clicked();
|
||||
|
||||
String _format_frame_text(const ScriptLanguage::StackInfo *info);
|
||||
|
||||
void _thread_debug_enter(uint64_t p_thread_id);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void request_remote_object(ObjectID p_obj_id);
|
||||
void update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value);
|
||||
Object *get_remote_object(ObjectID p_id);
|
||||
|
||||
// Needed by _live_edit_set, buttons state.
|
||||
void set_editor_remote_tree(const Tree *p_tree) { editor_remote_tree = p_tree; }
|
||||
|
||||
void request_remote_tree();
|
||||
const SceneDebuggerTree *get_remote_tree();
|
||||
|
||||
void start(Ref<RemoteDebuggerPeer> p_peer);
|
||||
void stop();
|
||||
|
||||
void debug_skip_breakpoints();
|
||||
void debug_copy();
|
||||
|
||||
void debug_next();
|
||||
void debug_step();
|
||||
void debug_break();
|
||||
void debug_continue();
|
||||
bool is_breaked() const { return threads_debugged.size() > 0; }
|
||||
bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; }
|
||||
bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }
|
||||
int get_remote_pid() const { return remote_pid; }
|
||||
|
||||
bool is_move_to_foreground() const;
|
||||
void set_move_to_foreground(const bool &p_move_to_foreground);
|
||||
|
||||
int get_error_count() const { return error_count; }
|
||||
int get_warning_count() const { return warning_count; }
|
||||
String get_stack_script_file() const;
|
||||
int get_stack_script_line() const;
|
||||
int get_stack_script_frame() const;
|
||||
|
||||
bool request_stack_dump(const int &p_frame);
|
||||
|
||||
void update_tabs();
|
||||
void clear_style();
|
||||
String get_var_value(const String &p_var) const;
|
||||
|
||||
void save_node(ObjectID p_id, const String &p_file);
|
||||
void set_live_debugging(bool p_enable);
|
||||
|
||||
void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name);
|
||||
void live_debug_instantiate_node(const NodePath &p_parent, const String &p_path, const String &p_name);
|
||||
void live_debug_remove_node(const NodePath &p_at);
|
||||
void live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id);
|
||||
void live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos);
|
||||
void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name);
|
||||
void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos);
|
||||
|
||||
EditorDebuggerNode::CameraOverride get_camera_override() const;
|
||||
void set_camera_override(EditorDebuggerNode::CameraOverride p_override);
|
||||
|
||||
void set_breakpoint(const String &p_path, int p_line, bool p_enabled);
|
||||
|
||||
void update_live_edit_root();
|
||||
|
||||
void reload_all_scripts();
|
||||
void reload_scripts(const Vector<String> &p_script_paths);
|
||||
|
||||
bool is_skip_breakpoints();
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
void add_debugger_tab(Control *p_control);
|
||||
void remove_debugger_tab(Control *p_control);
|
||||
int get_current_debugger_tab() const;
|
||||
void switch_to_debugger(int p_debugger_tab_idx);
|
||||
|
||||
void send_message(const String &p_message, const Array &p_args);
|
||||
void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data);
|
||||
|
||||
ScriptEditorDebugger();
|
||||
~ScriptEditorDebugger();
|
||||
};
|
||||
|
||||
#endif // SCRIPT_EDITOR_DEBUGGER_H
|
||||
898
engine/editor/dependency_editor.cpp
Normal file
898
engine/editor/dependency_editor.cpp
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
/**************************************************************************/
|
||||
/* dependency_editor.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 "dependency_editor.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
void DependencyEditor::_searched(const String &p_path) {
|
||||
HashMap<String, String> dep_rename;
|
||||
dep_rename[replacing] = p_path;
|
||||
|
||||
ResourceLoader::rename_dependencies(editing, dep_rename);
|
||||
|
||||
_update_list();
|
||||
_update_file();
|
||||
}
|
||||
|
||||
void DependencyEditor::_load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button) {
|
||||
if (p_mouse_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
|
||||
replacing = ti->get_text(1);
|
||||
|
||||
search->set_title(TTR("Search Replacement For:") + " " + replacing.get_file());
|
||||
|
||||
// Set directory to closest existing directory.
|
||||
search->set_current_dir(replacing.get_base_dir());
|
||||
|
||||
search->clear_filters();
|
||||
List<String> ext;
|
||||
ResourceLoader::get_recognized_extensions_for_type(ti->get_metadata(0), &ext);
|
||||
for (const String &E : ext) {
|
||||
search->add_filter("*." + E);
|
||||
}
|
||||
search->popup_file_dialog();
|
||||
}
|
||||
|
||||
void DependencyEditor::_fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates) {
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_fix_and_find(efsd->get_subdir(i), candidates);
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
String file = efsd->get_file(i);
|
||||
if (!candidates.has(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String path = efsd->get_file_path(i);
|
||||
|
||||
for (KeyValue<String, String> &E : candidates[file]) {
|
||||
if (E.value.is_empty()) {
|
||||
E.value = path;
|
||||
continue;
|
||||
}
|
||||
|
||||
//must match the best, using subdirs
|
||||
String existing = E.value.replace_first("res://", "");
|
||||
String current = path.replace_first("res://", "");
|
||||
String lost = E.key.replace_first("res://", "");
|
||||
|
||||
Vector<String> existingv = existing.split("/");
|
||||
existingv.reverse();
|
||||
Vector<String> currentv = current.split("/");
|
||||
currentv.reverse();
|
||||
Vector<String> lostv = lost.split("/");
|
||||
lostv.reverse();
|
||||
|
||||
int existing_score = 0;
|
||||
int current_score = 0;
|
||||
|
||||
for (int j = 0; j < lostv.size(); j++) {
|
||||
if (j < existingv.size() && lostv[j] == existingv[j]) {
|
||||
existing_score++;
|
||||
}
|
||||
if (j < currentv.size() && lostv[j] == currentv[j]) {
|
||||
current_score++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_score > existing_score) {
|
||||
//if it was the same, could track distance to new path but..
|
||||
|
||||
E.value = path; //replace by more accurate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditor::_fix_all() {
|
||||
if (!EditorFileSystem::get_singleton()->get_filesystem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, HashMap<String, String>> candidates;
|
||||
|
||||
for (const String &E : missing) {
|
||||
String base = E.get_file();
|
||||
if (!candidates.has(base)) {
|
||||
candidates[base] = HashMap<String, String>();
|
||||
}
|
||||
|
||||
candidates[base][E] = "";
|
||||
}
|
||||
|
||||
_fix_and_find(EditorFileSystem::get_singleton()->get_filesystem(), candidates);
|
||||
|
||||
HashMap<String, String> remaps;
|
||||
|
||||
for (KeyValue<String, HashMap<String, String>> &E : candidates) {
|
||||
for (const KeyValue<String, String> &F : E.value) {
|
||||
if (!F.value.is_empty()) {
|
||||
remaps[F.key] = F.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remaps.size()) {
|
||||
ResourceLoader::rename_dependencies(editing, remaps);
|
||||
|
||||
_update_list();
|
||||
_update_file();
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditor::_update_file() {
|
||||
EditorFileSystem::get_singleton()->update_file(editing);
|
||||
}
|
||||
|
||||
void DependencyEditor::_update_list() {
|
||||
List<String> deps;
|
||||
ResourceLoader::get_dependencies(editing, &deps, true);
|
||||
|
||||
tree->clear();
|
||||
missing.clear();
|
||||
|
||||
TreeItem *root = tree->create_item();
|
||||
|
||||
Ref<Texture2D> folder = tree->get_theme_icon(SNAME("folder"), SNAME("FileDialog"));
|
||||
|
||||
bool broken = false;
|
||||
|
||||
for (const String &n : deps) {
|
||||
TreeItem *item = tree->create_item(root);
|
||||
String path;
|
||||
String type;
|
||||
|
||||
if (n.contains("::")) {
|
||||
path = n.get_slice("::", 0);
|
||||
type = n.get_slice("::", 1);
|
||||
} else {
|
||||
path = n;
|
||||
type = "Resource";
|
||||
}
|
||||
|
||||
ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(path);
|
||||
if (uid != ResourceUID::INVALID_ID) {
|
||||
// Dependency is in uid format, obtain proper path.
|
||||
if (ResourceUID::get_singleton()->has_id(uid)) {
|
||||
path = ResourceUID::get_singleton()->get_id_path(uid);
|
||||
} else if (n.get_slice_count("::") >= 3) {
|
||||
// If uid can't be found, try to use fallback path.
|
||||
path = n.get_slice("::", 2);
|
||||
} else {
|
||||
ERR_PRINT("Invalid dependency UID and fallback path.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String name = path.get_file();
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
|
||||
item->set_text(0, name);
|
||||
item->set_icon(0, icon);
|
||||
item->set_metadata(0, type);
|
||||
item->set_text(1, path);
|
||||
|
||||
if (!FileAccess::exists(path)) {
|
||||
item->set_custom_color(1, Color(1, 0.4, 0.3));
|
||||
missing.push_back(path);
|
||||
broken = true;
|
||||
}
|
||||
|
||||
item->add_button(1, folder, 0);
|
||||
}
|
||||
|
||||
fixdeps->set_disabled(!broken);
|
||||
}
|
||||
|
||||
void DependencyEditor::edit(const String &p_path) {
|
||||
editing = p_path;
|
||||
set_title(TTR("Dependencies For:") + " " + p_path.get_file());
|
||||
|
||||
_update_list();
|
||||
popup_centered_ratio(0.4);
|
||||
|
||||
if (EditorNode::get_singleton()->is_scene_open(p_path)) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Scene '%s' is currently being edited.\nChanges will only take effect when reloaded."), p_path.get_file()));
|
||||
} else if (ResourceCache::has(p_path)) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Resource '%s' is in use.\nChanges will only take effect when reloaded."), p_path.get_file()));
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditor::_bind_methods() {
|
||||
}
|
||||
|
||||
DependencyEditor::DependencyEditor() {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->set_name(TTR("Dependencies"));
|
||||
add_child(vb);
|
||||
|
||||
tree = memnew(Tree);
|
||||
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
tree->set_columns(2);
|
||||
tree->set_column_titles_visible(true);
|
||||
tree->set_column_title(0, TTR("Resource"));
|
||||
tree->set_column_clip_content(0, true);
|
||||
tree->set_column_expand_ratio(0, 2);
|
||||
tree->set_column_title(1, TTR("Path"));
|
||||
tree->set_column_clip_content(1, true);
|
||||
tree->set_column_expand_ratio(1, 1);
|
||||
tree->set_hide_root(true);
|
||||
tree->connect("button_clicked", callable_mp(this, &DependencyEditor::_load_pressed));
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
Label *label = memnew(Label(TTR("Dependencies:")));
|
||||
label->set_theme_type_variation("HeaderSmall");
|
||||
|
||||
hbc->add_child(label);
|
||||
hbc->add_spacer();
|
||||
fixdeps = memnew(Button(TTR("Fix Broken")));
|
||||
hbc->add_child(fixdeps);
|
||||
fixdeps->connect(SceneStringName(pressed), callable_mp(this, &DependencyEditor::_fix_all));
|
||||
|
||||
vb->add_child(hbc);
|
||||
|
||||
MarginContainer *mc = memnew(MarginContainer);
|
||||
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
mc->add_child(tree);
|
||||
vb->add_child(mc);
|
||||
|
||||
set_title(TTR("Dependency Editor"));
|
||||
search = memnew(EditorFileDialog);
|
||||
search->connect("file_selected", callable_mp(this, &DependencyEditor::_searched));
|
||||
search->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
search->set_title(TTR("Search Replacement Resource:"));
|
||||
add_child(search);
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
void DependencyEditorOwners::_list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index) {
|
||||
if (p_mouse_button_index != MouseButton::RIGHT) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_options->clear();
|
||||
file_options->reset_size();
|
||||
if (p_item >= 0) {
|
||||
PackedInt32Array selected_items = owners->get_selected_items();
|
||||
bool only_scenes_selected = true;
|
||||
|
||||
for (int i = 0; i < selected_items.size(); i++) {
|
||||
int item_idx = selected_items[i];
|
||||
if (ResourceLoader::get_resource_type(owners->get_item_text(item_idx)) != "PackedScene") {
|
||||
only_scenes_selected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (only_scenes_selected) {
|
||||
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTRN("Open Scene", "Open Scenes", selected_items.size()), FILE_OPEN);
|
||||
} else if (selected_items.size() == 1) {
|
||||
file_options->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Open"), FILE_OPEN);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
file_options->set_position(owners->get_screen_position() + p_pos);
|
||||
file_options->reset_size();
|
||||
file_options->popup();
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_select_file(int p_idx) {
|
||||
String fpath = owners->get_item_text(p_idx);
|
||||
|
||||
if (ResourceLoader::get_resource_type(fpath) == "PackedScene") {
|
||||
EditorNode::get_singleton()->open_request(fpath);
|
||||
} else {
|
||||
EditorNode::get_singleton()->load_resource(fpath);
|
||||
}
|
||||
hide();
|
||||
emit_signal(SceneStringName(confirmed));
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index) {
|
||||
if (p_mouse_button_index != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
owners->deselect_all();
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_file_option(int p_option) {
|
||||
switch (p_option) {
|
||||
case FILE_OPEN: {
|
||||
PackedInt32Array selected_items = owners->get_selected_items();
|
||||
for (int i = 0; i < selected_items.size(); i++) {
|
||||
int item_idx = selected_items[i];
|
||||
if (item_idx < 0 || item_idx >= owners->get_item_count()) {
|
||||
break;
|
||||
}
|
||||
_select_file(item_idx);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_bind_methods() {
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::_fill_owners(EditorFileSystemDirectory *efsd) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_fill_owners(efsd->get_subdir(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
Vector<String> deps = efsd->get_file_deps(i);
|
||||
bool found = false;
|
||||
for (int j = 0; j < deps.size(); j++) {
|
||||
if (deps[j] == editing) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(efsd->get_file_type(i));
|
||||
|
||||
owners->add_item(efsd->get_file_path(i), icon);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyEditorOwners::show(const String &p_path) {
|
||||
editing = p_path;
|
||||
owners->clear();
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem());
|
||||
popup_centered_ratio(0.3);
|
||||
|
||||
set_title(vformat(TTR("Owners of: %s (Total: %d)"), p_path.get_file(), owners->get_item_count()));
|
||||
}
|
||||
|
||||
DependencyEditorOwners::DependencyEditorOwners() {
|
||||
file_options = memnew(PopupMenu);
|
||||
add_child(file_options);
|
||||
file_options->connect(SceneStringName(id_pressed), callable_mp(this, &DependencyEditorOwners::_file_option));
|
||||
|
||||
owners = memnew(ItemList);
|
||||
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
owners->set_select_mode(ItemList::SELECT_MULTI);
|
||||
owners->connect("item_clicked", callable_mp(this, &DependencyEditorOwners::_list_rmb_clicked));
|
||||
owners->connect("item_activated", callable_mp(this, &DependencyEditorOwners::_select_file));
|
||||
owners->connect("empty_clicked", callable_mp(this, &DependencyEditorOwners::_empty_clicked));
|
||||
owners->set_allow_rmb_select(true);
|
||||
add_child(owners);
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
|
||||
void DependencyRemoveDialog::_find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); ++i) {
|
||||
_find_files_in_removed_folder(efsd->get_subdir(i), p_folder);
|
||||
}
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
String file = efsd->get_file_path(i);
|
||||
ERR_FAIL_COND(all_remove_files.has(file)); //We are deleting a directory which is contained in a directory we are deleting...
|
||||
all_remove_files[file] = p_folder; //Point the file to the ancestor directory we are deleting so we know what to parent it under in the tree.
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_find_all_removed_dependencies(efsd->get_subdir(i), p_removed);
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
const String path = efsd->get_file_path(i);
|
||||
|
||||
//It doesn't matter if a file we are about to delete will have some of its dependencies removed too
|
||||
if (all_remove_files.has(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<String> all_deps = efsd->get_file_deps(i);
|
||||
for (int j = 0; j < all_deps.size(); ++j) {
|
||||
if (all_remove_files.has(all_deps[j])) {
|
||||
RemovedDependency dep;
|
||||
dep.file = path;
|
||||
dep.file_type = efsd->get_file_type(i);
|
||||
dep.dependency = all_deps[j];
|
||||
dep.dependency_folder = all_remove_files[all_deps[j]];
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed) {
|
||||
for (KeyValue<String, String> &files : all_remove_files) {
|
||||
const String &path = files.key;
|
||||
|
||||
// Look for dependencies in the translation remaps.
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
|
||||
Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps");
|
||||
|
||||
if (remaps.has(path)) {
|
||||
RemovedDependency dep;
|
||||
dep.file = TTR("Localization remap");
|
||||
dep.file_type = "";
|
||||
dep.dependency = path;
|
||||
dep.dependency_folder = files.value;
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
|
||||
Array remap_keys = remaps.keys();
|
||||
for (int j = 0; j < remap_keys.size(); j++) {
|
||||
PackedStringArray remapped_files = remaps[remap_keys[j]];
|
||||
for (int k = 0; k < remapped_files.size(); k++) {
|
||||
int splitter_pos = remapped_files[k].rfind(":");
|
||||
String res_path = remapped_files[k].substr(0, splitter_pos);
|
||||
if (res_path == path) {
|
||||
String locale_name = remapped_files[k].substr(splitter_pos + 1);
|
||||
|
||||
RemovedDependency dep;
|
||||
dep.file = vformat(TTR("Localization remap for path '%s' and locale '%s'."), remap_keys[j], locale_name);
|
||||
dep.file_type = "";
|
||||
dep.dependency = path;
|
||||
dep.dependency_folder = files.value;
|
||||
p_removed.push_back(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed) {
|
||||
owners->clear();
|
||||
owners->create_item(); // root
|
||||
|
||||
HashMap<String, TreeItem *> tree_items;
|
||||
for (int i = 0; i < p_removed.size(); i++) {
|
||||
RemovedDependency rd = p_removed[i];
|
||||
|
||||
//Ensure that the dependency is already in the tree
|
||||
if (!tree_items.has(rd.dependency)) {
|
||||
if (rd.dependency_folder.length() > 0) {
|
||||
//Ensure the ancestor folder is already in the tree
|
||||
if (!tree_items.has(rd.dependency_folder)) {
|
||||
TreeItem *folder_item = owners->create_item(owners->get_root());
|
||||
folder_item->set_text(0, rd.dependency_folder);
|
||||
folder_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Folder")));
|
||||
tree_items[rd.dependency_folder] = folder_item;
|
||||
}
|
||||
TreeItem *dependency_item = owners->create_item(tree_items[rd.dependency_folder]);
|
||||
dependency_item->set_text(0, rd.dependency);
|
||||
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
|
||||
tree_items[rd.dependency] = dependency_item;
|
||||
} else {
|
||||
TreeItem *dependency_item = owners->create_item(owners->get_root());
|
||||
dependency_item->set_text(0, rd.dependency);
|
||||
dependency_item->set_icon(0, owners->get_editor_theme_icon(SNAME("Warning")));
|
||||
tree_items[rd.dependency] = dependency_item;
|
||||
}
|
||||
}
|
||||
|
||||
//List this file under this dependency
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(rd.file_type);
|
||||
TreeItem *file_item = owners->create_item(tree_items[rd.dependency]);
|
||||
file_item->set_text(0, rd.file);
|
||||
file_item->set_icon(0, icon);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<String> &p_files) {
|
||||
all_remove_files.clear();
|
||||
dirs_to_delete.clear();
|
||||
files_to_delete.clear();
|
||||
owners->clear();
|
||||
|
||||
for (int i = 0; i < p_folders.size(); ++i) {
|
||||
String folder = p_folders[i].ends_with("/") ? p_folders[i] : (p_folders[i] + "/");
|
||||
_find_files_in_removed_folder(EditorFileSystem::get_singleton()->get_filesystem_path(folder), folder);
|
||||
dirs_to_delete.push_back(folder);
|
||||
}
|
||||
for (int i = 0; i < p_files.size(); ++i) {
|
||||
all_remove_files[p_files[i]] = String();
|
||||
files_to_delete.push_back(p_files[i]);
|
||||
}
|
||||
|
||||
Vector<RemovedDependency> removed_deps;
|
||||
_find_all_removed_dependencies(EditorFileSystem::get_singleton()->get_filesystem(), removed_deps);
|
||||
_find_localization_remaps_of_removed_files(removed_deps);
|
||||
removed_deps.sort();
|
||||
if (removed_deps.is_empty()) {
|
||||
owners->hide();
|
||||
text->set_text(TTR("Remove the selected files from the project? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently."));
|
||||
reset_size();
|
||||
popup_centered();
|
||||
} else {
|
||||
_build_removed_dependency_tree(removed_deps);
|
||||
owners->show();
|
||||
text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently."));
|
||||
popup_centered(Size2(500, 350));
|
||||
}
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::ok_pressed() {
|
||||
for (const KeyValue<String, String> &E : all_remove_files) {
|
||||
String file = E.key;
|
||||
|
||||
if (ResourceCache::has(file)) {
|
||||
Ref<Resource> res = ResourceCache::get_ref(file);
|
||||
emit_signal(SNAME("resource_removed"), res);
|
||||
res->set_path("");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < files_to_delete.size(); ++i) {
|
||||
// If the file we are deleting for e.g. the main scene, default environment,
|
||||
// or audio bus layout, we must clear its definition in Project Settings.
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("application/config/icon"))) {
|
||||
ProjectSettings::get_singleton()->set("application/config/icon", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("application/run/main_scene"))) {
|
||||
ProjectSettings::get_singleton()->set("application/run/main_scene", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("application/boot_splash/image"))) {
|
||||
ProjectSettings::get_singleton()->set("application/boot_splash/image", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("rendering/environment/defaults/default_environment"))) {
|
||||
ProjectSettings::get_singleton()->set("rendering/environment/defaults/default_environment", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("display/mouse_cursor/custom_image"))) {
|
||||
ProjectSettings::get_singleton()->set("display/mouse_cursor/custom_image", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom"))) {
|
||||
ProjectSettings::get_singleton()->set("gui/theme/custom", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("gui/theme/custom_font"))) {
|
||||
ProjectSettings::get_singleton()->set("gui/theme/custom_font", "");
|
||||
}
|
||||
if (files_to_delete[i] == String(GLOBAL_GET("audio/buses/default_bus_layout"))) {
|
||||
ProjectSettings::get_singleton()->set("audio/buses/default_bus_layout", "");
|
||||
}
|
||||
|
||||
String path = OS::get_singleton()->get_resource_dir() + files_to_delete[i].replace_first("res://", "/");
|
||||
print_verbose("Moving to trash: " + path);
|
||||
Error err = OS::get_singleton()->move_to_trash(path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + files_to_delete[i] + "\n");
|
||||
} else {
|
||||
emit_signal(SNAME("file_removed"), files_to_delete[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirs_to_delete.size() == 0) {
|
||||
// If we only deleted files we should only need to tell the file system about the files we touched.
|
||||
for (int i = 0; i < files_to_delete.size(); ++i) {
|
||||
EditorFileSystem::get_singleton()->update_file(files_to_delete[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < dirs_to_delete.size(); ++i) {
|
||||
String path = OS::get_singleton()->get_resource_dir() + dirs_to_delete[i].replace_first("res://", "/");
|
||||
print_verbose("Moving to trash: " + path);
|
||||
Error err = OS::get_singleton()->move_to_trash(path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + dirs_to_delete[i] + "\n");
|
||||
} else {
|
||||
emit_signal(SNAME("folder_removed"), dirs_to_delete[i]);
|
||||
}
|
||||
}
|
||||
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
||||
|
||||
// If some files/dirs would be deleted, favorite dirs need to be updated
|
||||
Vector<String> previous_favorites = EditorSettings::get_singleton()->get_favorites();
|
||||
Vector<String> new_favorites;
|
||||
|
||||
for (int i = 0; i < previous_favorites.size(); ++i) {
|
||||
if (previous_favorites[i].ends_with("/")) {
|
||||
if (!dirs_to_delete.has(previous_favorites[i])) {
|
||||
new_favorites.push_back(previous_favorites[i]);
|
||||
}
|
||||
} else {
|
||||
if (!files_to_delete.has(previous_favorites[i])) {
|
||||
new_favorites.push_back(previous_favorites[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (new_favorites.size() < previous_favorites.size()) {
|
||||
EditorSettings::get_singleton()->set_favorites(new_favorites);
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyRemoveDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("resource_removed", PropertyInfo(Variant::OBJECT, "obj")));
|
||||
ADD_SIGNAL(MethodInfo("file_removed", PropertyInfo(Variant::STRING, "file")));
|
||||
ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder")));
|
||||
}
|
||||
|
||||
DependencyRemoveDialog::DependencyRemoveDialog() {
|
||||
set_ok_button_text(TTR("Remove"));
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
text = memnew(Label);
|
||||
vb->add_child(text);
|
||||
|
||||
owners = memnew(Tree);
|
||||
owners->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
owners->set_hide_root(true);
|
||||
vb->add_child(owners);
|
||||
owners->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
}
|
||||
|
||||
//////////////
|
||||
|
||||
void DependencyErrorDialog::show(Mode p_mode, const String &p_for_file, const Vector<String> &report) {
|
||||
mode = p_mode;
|
||||
for_file = p_for_file;
|
||||
set_title(TTR("Error loading:") + " " + p_for_file.get_file());
|
||||
files->clear();
|
||||
|
||||
TreeItem *root = files->create_item(nullptr);
|
||||
for (int i = 0; i < report.size(); i++) {
|
||||
String dep;
|
||||
String type = "Object";
|
||||
dep = report[i].get_slice("::", 0);
|
||||
if (report[i].get_slice_count("::") > 0) {
|
||||
type = report[i].get_slice("::", 1);
|
||||
}
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
|
||||
|
||||
TreeItem *ti = files->create_item(root);
|
||||
ti->set_text(0, dep);
|
||||
ti->set_icon(0, icon);
|
||||
}
|
||||
|
||||
popup_centered();
|
||||
}
|
||||
|
||||
void DependencyErrorDialog::ok_pressed() {
|
||||
switch (mode) {
|
||||
case MODE_SCENE:
|
||||
EditorNode::get_singleton()->load_scene(for_file, true);
|
||||
break;
|
||||
case MODE_RESOURCE:
|
||||
EditorNode::get_singleton()->load_resource(for_file, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DependencyErrorDialog::custom_action(const String &) {
|
||||
EditorNode::get_singleton()->fix_dependencies(for_file);
|
||||
}
|
||||
|
||||
DependencyErrorDialog::DependencyErrorDialog() {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
files = memnew(Tree);
|
||||
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
files->set_hide_root(true);
|
||||
vb->add_margin_child(TTR("Load failed due to missing dependencies:"), files, true);
|
||||
files->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
set_min_size(Size2(500, 220) * EDSCALE);
|
||||
set_ok_button_text(TTR("Open Anyway"));
|
||||
set_cancel_button_text(TTR("Close"));
|
||||
|
||||
text = memnew(Label);
|
||||
vb->add_child(text);
|
||||
text->set_text(TTR("Which action should be taken?"));
|
||||
|
||||
mode = Mode::MODE_RESOURCE;
|
||||
|
||||
fdep = add_button(TTR("Fix Dependencies"), true, "fixdeps");
|
||||
|
||||
set_title(TTR("Errors loading!"));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void OrphanResourcesDialog::ok_pressed() {
|
||||
paths.clear();
|
||||
|
||||
_find_to_delete(files->get_root(), paths);
|
||||
if (paths.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_confirm->set_text(vformat(TTR("Permanently delete %d item(s)? (No undo!)"), paths.size()));
|
||||
delete_confirm->popup_centered();
|
||||
}
|
||||
|
||||
bool OrphanResourcesDialog::_fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent) {
|
||||
if (!efsd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_children = false;
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
TreeItem *dir_item = nullptr;
|
||||
if (p_parent) {
|
||||
dir_item = files->create_item(p_parent);
|
||||
dir_item->set_text(0, efsd->get_subdir(i)->get_name());
|
||||
dir_item->set_icon(0, files->get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
|
||||
}
|
||||
bool children = _fill_owners(efsd->get_subdir(i), refs, dir_item);
|
||||
|
||||
if (p_parent) {
|
||||
if (!children) {
|
||||
memdelete(dir_item);
|
||||
} else {
|
||||
has_children = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
if (!p_parent) {
|
||||
Vector<String> deps = efsd->get_file_deps(i);
|
||||
for (int j = 0; j < deps.size(); j++) {
|
||||
if (!refs.has(deps[j])) {
|
||||
refs[deps[j]] = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String path = efsd->get_file_path(i);
|
||||
if (!refs.has(path)) {
|
||||
TreeItem *ti = files->create_item(p_parent);
|
||||
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
ti->set_text(0, efsd->get_file(i));
|
||||
ti->set_editable(0, true);
|
||||
|
||||
String type = efsd->get_file_type(i);
|
||||
|
||||
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(type);
|
||||
ti->set_icon(0, icon);
|
||||
int ds = efsd->get_file_deps(i).size();
|
||||
ti->set_text(1, itos(ds));
|
||||
if (ds) {
|
||||
ti->add_button(1, files->get_editor_theme_icon(SNAME("GuiVisibilityVisible")), -1, false, TTR("Show Dependencies"));
|
||||
}
|
||||
ti->set_metadata(0, path);
|
||||
has_children = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return has_children;
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::refresh() {
|
||||
HashMap<String, int> refs;
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, nullptr);
|
||||
files->clear();
|
||||
TreeItem *root = files->create_item();
|
||||
_fill_owners(EditorFileSystem::get_singleton()->get_filesystem(), refs, root);
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::show() {
|
||||
refresh();
|
||||
popup_centered_ratio(0.4);
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_find_to_delete(TreeItem *p_item, List<String> &r_paths) {
|
||||
while (p_item) {
|
||||
if (p_item->get_cell_mode(0) == TreeItem::CELL_MODE_CHECK && p_item->is_checked(0)) {
|
||||
r_paths.push_back(p_item->get_metadata(0));
|
||||
}
|
||||
|
||||
if (p_item->get_first_child()) {
|
||||
_find_to_delete(p_item->get_first_child(), r_paths);
|
||||
}
|
||||
|
||||
p_item = p_item->get_next();
|
||||
}
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_delete_confirm() {
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
for (const String &E : paths) {
|
||||
da->remove(E);
|
||||
EditorFileSystem::get_singleton()->update_file(E);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
|
||||
if (p_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
|
||||
|
||||
String path = ti->get_metadata(0);
|
||||
dep_edit->edit(path);
|
||||
}
|
||||
|
||||
void OrphanResourcesDialog::_bind_methods() {
|
||||
}
|
||||
|
||||
OrphanResourcesDialog::OrphanResourcesDialog() {
|
||||
set_title(TTR("Orphan Resource Explorer"));
|
||||
delete_confirm = memnew(ConfirmationDialog);
|
||||
set_ok_button_text(TTR("Delete"));
|
||||
add_child(delete_confirm);
|
||||
dep_edit = memnew(DependencyEditor);
|
||||
add_child(dep_edit);
|
||||
delete_confirm->connect(SceneStringName(confirmed), callable_mp(this, &OrphanResourcesDialog::_delete_confirm));
|
||||
set_hide_on_ok(false);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
files = memnew(Tree);
|
||||
files->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
files->set_columns(2);
|
||||
files->set_column_titles_visible(true);
|
||||
files->set_column_custom_minimum_width(1, 100 * EDSCALE);
|
||||
files->set_column_expand(0, true);
|
||||
files->set_column_clip_content(0, true);
|
||||
files->set_column_expand(1, false);
|
||||
files->set_column_clip_content(1, true);
|
||||
files->set_column_title(0, TTR("Resource"));
|
||||
files->set_column_title(1, TTR("Owns"));
|
||||
files->set_hide_root(true);
|
||||
vbc->add_margin_child(TTR("Resources Without Explicit Ownership:"), files, true);
|
||||
files->connect("button_clicked", callable_mp(this, &OrphanResourcesDialog::_button_pressed));
|
||||
}
|
||||
185
engine/editor/dependency_editor.h
Normal file
185
engine/editor/dependency_editor.h
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/**************************************************************************/
|
||||
/* dependency_editor.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DEPENDENCY_EDITOR_H
|
||||
#define DEPENDENCY_EDITOR_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorFileDialog;
|
||||
class EditorFileSystemDirectory;
|
||||
|
||||
class DependencyEditor : public AcceptDialog {
|
||||
GDCLASS(DependencyEditor, AcceptDialog);
|
||||
|
||||
Tree *tree = nullptr;
|
||||
Button *fixdeps = nullptr;
|
||||
|
||||
EditorFileDialog *search = nullptr;
|
||||
|
||||
String replacing;
|
||||
String editing;
|
||||
List<String> missing;
|
||||
|
||||
void _fix_and_find(EditorFileSystemDirectory *efsd, HashMap<String, HashMap<String, String>> &candidates);
|
||||
|
||||
void _searched(const String &p_path);
|
||||
void _load_pressed(Object *p_item, int p_cell, int p_button, MouseButton p_mouse_button);
|
||||
void _fix_all();
|
||||
void _update_list();
|
||||
|
||||
void _update_file();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void edit(const String &p_path);
|
||||
DependencyEditor();
|
||||
};
|
||||
|
||||
#ifdef MINGW_ENABLED
|
||||
#undef FILE_OPEN
|
||||
#endif
|
||||
|
||||
class DependencyEditorOwners : public AcceptDialog {
|
||||
GDCLASS(DependencyEditorOwners, AcceptDialog);
|
||||
|
||||
ItemList *owners = nullptr;
|
||||
PopupMenu *file_options = nullptr;
|
||||
String editing;
|
||||
|
||||
void _fill_owners(EditorFileSystemDirectory *efsd);
|
||||
|
||||
static void _bind_methods();
|
||||
void _list_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _select_file(int p_idx);
|
||||
void _empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _file_option(int p_option);
|
||||
|
||||
private:
|
||||
enum FileMenu {
|
||||
FILE_OPEN
|
||||
};
|
||||
|
||||
public:
|
||||
void show(const String &p_path);
|
||||
DependencyEditorOwners();
|
||||
};
|
||||
|
||||
class DependencyRemoveDialog : public ConfirmationDialog {
|
||||
GDCLASS(DependencyRemoveDialog, ConfirmationDialog);
|
||||
|
||||
Label *text = nullptr;
|
||||
Tree *owners = nullptr;
|
||||
|
||||
HashMap<String, String> all_remove_files;
|
||||
Vector<String> dirs_to_delete;
|
||||
Vector<String> files_to_delete;
|
||||
|
||||
struct RemovedDependency {
|
||||
String file;
|
||||
String file_type;
|
||||
String dependency;
|
||||
String dependency_folder;
|
||||
|
||||
bool operator<(const RemovedDependency &p_other) const {
|
||||
if (dependency_folder.is_empty() != p_other.dependency_folder.is_empty()) {
|
||||
return p_other.dependency_folder.is_empty();
|
||||
} else {
|
||||
return dependency < p_other.dependency;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void _find_files_in_removed_folder(EditorFileSystemDirectory *efsd, const String &p_folder);
|
||||
void _find_all_removed_dependencies(EditorFileSystemDirectory *efsd, Vector<RemovedDependency> &p_removed);
|
||||
void _find_localization_remaps_of_removed_files(Vector<RemovedDependency> &p_removed);
|
||||
void _build_removed_dependency_tree(const Vector<RemovedDependency> &p_removed);
|
||||
|
||||
void ok_pressed() override;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void show(const Vector<String> &p_folders, const Vector<String> &p_files);
|
||||
DependencyRemoveDialog();
|
||||
};
|
||||
|
||||
class DependencyErrorDialog : public ConfirmationDialog {
|
||||
GDCLASS(DependencyErrorDialog, ConfirmationDialog);
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_SCENE,
|
||||
MODE_RESOURCE,
|
||||
};
|
||||
|
||||
private:
|
||||
String for_file;
|
||||
Mode mode;
|
||||
Button *fdep = nullptr;
|
||||
Label *text = nullptr;
|
||||
Tree *files = nullptr;
|
||||
void ok_pressed() override;
|
||||
void custom_action(const String &) override;
|
||||
|
||||
public:
|
||||
void show(Mode p_mode, const String &p_for_file, const Vector<String> &report);
|
||||
DependencyErrorDialog();
|
||||
};
|
||||
|
||||
class OrphanResourcesDialog : public ConfirmationDialog {
|
||||
GDCLASS(OrphanResourcesDialog, ConfirmationDialog);
|
||||
|
||||
DependencyEditor *dep_edit = nullptr;
|
||||
Tree *files = nullptr;
|
||||
ConfirmationDialog *delete_confirm = nullptr;
|
||||
void ok_pressed() override;
|
||||
|
||||
bool _fill_owners(EditorFileSystemDirectory *efsd, HashMap<String, int> &refs, TreeItem *p_parent);
|
||||
|
||||
List<String> paths;
|
||||
void _find_to_delete(TreeItem *p_item, List<String> &r_paths);
|
||||
void _delete_confirm();
|
||||
void _button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
|
||||
void refresh();
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void show();
|
||||
OrphanResourcesDialog();
|
||||
};
|
||||
|
||||
#endif // DEPENDENCY_EDITOR_H
|
||||
163
engine/editor/directory_create_dialog.cpp
Normal file
163
engine/editor/directory_create_dialog.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/**************************************************************************/
|
||||
/* directory_create_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 "directory_create_dialog.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/gui/editor_validation_panel.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
|
||||
static String sanitize_input(const String &p_path) {
|
||||
String path = p_path.strip_edges();
|
||||
if (path.ends_with("/")) {
|
||||
path = path.left(path.length() - 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
String DirectoryCreateDialog::_validate_path(const String &p_path) const {
|
||||
if (p_path.is_empty()) {
|
||||
return TTR("Folder name cannot be empty.");
|
||||
}
|
||||
|
||||
if (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") ||
|
||||
p_path.contains("|") || p_path.contains(">")) {
|
||||
return TTR("Folder name contains invalid characters.");
|
||||
}
|
||||
|
||||
for (const String &part : p_path.split("/")) {
|
||||
if (part.is_empty()) {
|
||||
return TTR("Folder name cannot be empty.");
|
||||
}
|
||||
if (part.ends_with(" ") || part[0] == ' ') {
|
||||
return TTR("Folder name cannot begin or end with a space.");
|
||||
}
|
||||
if (part[0] == '.') {
|
||||
return TTR("Folder name cannot begin with a dot.");
|
||||
}
|
||||
}
|
||||
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
da->change_dir(base_dir);
|
||||
if (da->file_exists(p_path)) {
|
||||
return TTR("File with that name already exists.");
|
||||
}
|
||||
if (da->dir_exists(p_path)) {
|
||||
return TTR("Folder with that name already exists.");
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
void DirectoryCreateDialog::_on_dir_path_changed() {
|
||||
const String path = sanitize_input(dir_path->get_text());
|
||||
const String error = _validate_path(path);
|
||||
|
||||
if (error.is_empty()) {
|
||||
if (path.contains("/")) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
|
||||
}
|
||||
} else {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryCreateDialog::ok_pressed() {
|
||||
const String path = sanitize_input(dir_path->get_text());
|
||||
|
||||
// The OK button should be disabled if the path is invalid, but just in case.
|
||||
const String error = _validate_path(path);
|
||||
ERR_FAIL_COND_MSG(!error.is_empty(), error);
|
||||
|
||||
Error err;
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
|
||||
err = da->change_dir(base_dir);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Cannot open directory '" + base_dir + "'.");
|
||||
|
||||
print_verbose("Making folder " + path + " in " + base_dir);
|
||||
err = da->make_dir_recursive(path);
|
||||
|
||||
if (err == OK) {
|
||||
emit_signal(SNAME("dir_created"), base_dir.path_join(path));
|
||||
} else {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Could not create folder."));
|
||||
}
|
||||
hide();
|
||||
}
|
||||
|
||||
void DirectoryCreateDialog::_post_popup() {
|
||||
ConfirmationDialog::_post_popup();
|
||||
dir_path->grab_focus();
|
||||
}
|
||||
|
||||
void DirectoryCreateDialog::config(const String &p_base_dir) {
|
||||
base_dir = p_base_dir;
|
||||
label->set_text(vformat(TTR("Create new folder in %s:"), base_dir));
|
||||
dir_path->set_text("new folder");
|
||||
dir_path->select_all();
|
||||
validation_panel->update();
|
||||
}
|
||||
|
||||
void DirectoryCreateDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("dir_created", PropertyInfo(Variant::STRING, "path")));
|
||||
}
|
||||
|
||||
DirectoryCreateDialog::DirectoryCreateDialog() {
|
||||
set_title(TTR("Create Folder"));
|
||||
set_min_size(Size2i(480, 0) * EDSCALE);
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
label = memnew(Label);
|
||||
label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS);
|
||||
vb->add_child(label);
|
||||
|
||||
dir_path = memnew(LineEdit);
|
||||
vb->add_child(dir_path);
|
||||
register_text_enter(dir_path);
|
||||
|
||||
Control *spacing = memnew(Control);
|
||||
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
|
||||
vb->add_child(spacing);
|
||||
|
||||
validation_panel = memnew(EditorValidationPanel);
|
||||
vb->add_child(validation_panel);
|
||||
validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Folder name is valid."));
|
||||
validation_panel->set_update_callback(callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed));
|
||||
validation_panel->set_accept_button(get_ok_button());
|
||||
|
||||
dir_path->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
|
||||
}
|
||||
64
engine/editor/directory_create_dialog.h
Normal file
64
engine/editor/directory_create_dialog.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**************************************************************************/
|
||||
/* directory_create_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DIRECTORY_CREATE_DIALOG_H
|
||||
#define DIRECTORY_CREATE_DIALOG_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class EditorValidationPanel;
|
||||
class Label;
|
||||
class LineEdit;
|
||||
|
||||
class DirectoryCreateDialog : public ConfirmationDialog {
|
||||
GDCLASS(DirectoryCreateDialog, ConfirmationDialog);
|
||||
|
||||
String base_dir;
|
||||
|
||||
Label *label = nullptr;
|
||||
LineEdit *dir_path = nullptr;
|
||||
EditorValidationPanel *validation_panel = nullptr;
|
||||
|
||||
String _validate_path(const String &p_path) const;
|
||||
void _on_dir_path_changed();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void ok_pressed() override;
|
||||
virtual void _post_popup() override;
|
||||
|
||||
public:
|
||||
void config(const String &p_base_dir);
|
||||
|
||||
DirectoryCreateDialog();
|
||||
};
|
||||
|
||||
#endif // DIRECTORY_CREATE_DIALOG_H
|
||||
1790
engine/editor/doc_tools.cpp
Normal file
1790
engine/editor/doc_tools.cpp
Normal file
File diff suppressed because it is too large
Load diff
62
engine/editor/doc_tools.h
Normal file
62
engine/editor/doc_tools.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**************************************************************************/
|
||||
/* doc_tools.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DOC_TOOLS_H
|
||||
#define DOC_TOOLS_H
|
||||
|
||||
#include "core/doc_data.h"
|
||||
#include "core/templates/rb_set.h"
|
||||
|
||||
class DocTools {
|
||||
public:
|
||||
String version;
|
||||
HashMap<String, DocData::ClassDoc> class_list;
|
||||
HashMap<String, RBSet<String, NaturalNoCaseComparator>> inheriting;
|
||||
|
||||
static Error erase_classes(const String &p_dir);
|
||||
|
||||
void merge_from(const DocTools &p_data);
|
||||
void add_doc(const DocData::ClassDoc &p_class_doc);
|
||||
void remove_doc(const String &p_class_name);
|
||||
bool has_doc(const String &p_class_name);
|
||||
enum GenerateFlags {
|
||||
GENERATE_FLAG_SKIP_BASIC_TYPES = (1 << 0),
|
||||
GENERATE_FLAG_EXTENSION_CLASSES_ONLY = (1 << 1),
|
||||
};
|
||||
void generate(BitField<GenerateFlags> p_flags = {});
|
||||
Error load_classes(const String &p_dir);
|
||||
Error save_classes(const String &p_default_path, const HashMap<String, String> &p_class_path, bool p_use_relative_schema = true);
|
||||
|
||||
Error _load(Ref<XMLParser> parser);
|
||||
Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size);
|
||||
Error load_xml(const uint8_t *p_data, int p_size);
|
||||
};
|
||||
|
||||
#endif // DOC_TOOLS_H
|
||||
367
engine/editor/editor_about.cpp
Normal file
367
engine/editor/editor_about.cpp
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
/**************************************************************************/
|
||||
/* editor_about.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_about.h"
|
||||
|
||||
#include "core/authors.gen.h"
|
||||
#include "core/donors.gen.h"
|
||||
#include "core/license.gen.h"
|
||||
#include "core/os/time.h"
|
||||
#include "core/version.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/resources/style_box.h"
|
||||
|
||||
// The metadata key used to store and retrieve the version text to copy to the clipboard.
|
||||
const String EditorAbout::META_TEXT_TO_COPY = "text_to_copy";
|
||||
|
||||
void EditorAbout::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
const Ref<Font> font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts));
|
||||
const int font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts));
|
||||
|
||||
_tpl_text->begin_bulk_theme_override();
|
||||
_tpl_text->add_theme_font_override("normal_font", font);
|
||||
_tpl_text->add_theme_font_size_override("normal_font_size", font_size);
|
||||
_tpl_text->add_theme_constant_override(SceneStringName(line_separation), 4 * EDSCALE);
|
||||
_tpl_text->end_bulk_theme_override();
|
||||
|
||||
license_text_label->begin_bulk_theme_override();
|
||||
license_text_label->add_theme_font_override("normal_font", font);
|
||||
license_text_label->add_theme_font_size_override("normal_font_size", font_size);
|
||||
license_text_label->add_theme_constant_override(SceneStringName(line_separation), 4 * EDSCALE);
|
||||
license_text_label->end_bulk_theme_override();
|
||||
|
||||
_logo->set_texture(get_editor_theme_icon(SNAME("Logo")));
|
||||
|
||||
for (ItemList *il : name_lists) {
|
||||
for (int i = 0; i < il->get_item_count(); i++) {
|
||||
if (il->get_item_metadata(i)) {
|
||||
il->set_item_icon(i, get_theme_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
|
||||
il->set_item_icon_modulate(i, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAbout::_license_tree_selected() {
|
||||
TreeItem *selected = _tpl_tree->get_selected();
|
||||
_tpl_text->scroll_to_line(0);
|
||||
_tpl_text->set_text(selected->get_metadata(0));
|
||||
}
|
||||
|
||||
void EditorAbout::_version_button_pressed() {
|
||||
DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
|
||||
}
|
||||
|
||||
void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) {
|
||||
const String website = p_il->get_item_metadata(p_id);
|
||||
if (!website.is_empty()) {
|
||||
OS::get_singleton()->shell_open(website);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAbout::_item_list_resized(ItemList *p_il) {
|
||||
p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?.
|
||||
}
|
||||
|
||||
ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website) {
|
||||
ScrollContainer *sc = memnew(ScrollContainer);
|
||||
sc->set_name(p_name);
|
||||
sc->set_v_size_flags(Control::SIZE_EXPAND);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
sc->add_child(vbc);
|
||||
|
||||
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
|
||||
|
||||
int i = 0;
|
||||
for (List<String>::ConstIterator itr = p_sections.begin(); itr != p_sections.end(); ++itr, ++i) {
|
||||
bool single_column = p_single_column_flags & (1 << i);
|
||||
const char *const *names_ptr = p_src[i];
|
||||
if (*names_ptr) {
|
||||
Label *lbl = memnew(Label);
|
||||
lbl->set_theme_type_variation("HeaderSmall");
|
||||
lbl->set_text(*itr);
|
||||
vbc->add_child(lbl);
|
||||
|
||||
ItemList *il = memnew(ItemList);
|
||||
il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
il->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
il->set_same_column_width(true);
|
||||
il->set_auto_height(true);
|
||||
il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
||||
il->set_focus_mode(Control::FOCUS_NONE);
|
||||
il->add_theme_constant_override("h_separation", 16 * EDSCALE);
|
||||
if (p_allow_website) {
|
||||
il->set_focus_mode(Control::FOCUS_CLICK);
|
||||
il->set_mouse_filter(Control::MOUSE_FILTER_PASS);
|
||||
|
||||
il->connect("item_activated", callable_mp(this, &EditorAbout::_item_with_website_selected).bind(il));
|
||||
il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il));
|
||||
il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all));
|
||||
|
||||
il->add_theme_style_override("focus", empty_stylebox);
|
||||
il->add_theme_style_override("selected", empty_stylebox);
|
||||
|
||||
while (*names_ptr) {
|
||||
const String name = String::utf8(*names_ptr++);
|
||||
const String identifier = name.get_slice("<", 0);
|
||||
const String website = name.get_slice_count("<") == 1 ? "" : name.get_slice("<", 1).trim_suffix(">");
|
||||
|
||||
const int name_item_id = il->add_item(identifier, nullptr, false);
|
||||
il->set_item_tooltip_enabled(name_item_id, false);
|
||||
|
||||
if (!website.is_empty()) {
|
||||
il->set_item_selectable(name_item_id, true);
|
||||
il->set_item_metadata(name_item_id, website);
|
||||
il->set_item_tooltip(name_item_id, website + "\n\n" + TTR("Double-click to open in browser."));
|
||||
il->set_item_tooltip_enabled(name_item_id, true);
|
||||
}
|
||||
|
||||
if (!*names_ptr && name.contains(" anonymous ")) {
|
||||
il->set_item_disabled(name_item_id, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (*names_ptr) {
|
||||
il->add_item(String::utf8(*names_ptr++), nullptr, false);
|
||||
}
|
||||
}
|
||||
il->set_max_columns(single_column ? 1 : 16);
|
||||
|
||||
name_lists.append(il);
|
||||
|
||||
vbc->add_child(il);
|
||||
|
||||
HSeparator *hs = memnew(HSeparator);
|
||||
hs->set_modulate(Color(0, 0, 0, 0));
|
||||
vbc->add_child(hs);
|
||||
}
|
||||
}
|
||||
|
||||
return sc;
|
||||
}
|
||||
|
||||
EditorAbout::EditorAbout() {
|
||||
set_title(TTR("Thanks from the Godot community!"));
|
||||
set_hide_on_ok(true);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
hbc->set_alignment(BoxContainer::ALIGNMENT_CENTER);
|
||||
hbc->add_theme_constant_override("separation", 30 * EDSCALE);
|
||||
vbc->add_child(hbc);
|
||||
|
||||
_logo = memnew(TextureRect);
|
||||
_logo->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
|
||||
hbc->add_child(_logo);
|
||||
|
||||
VBoxContainer *version_info_vbc = memnew(VBoxContainer);
|
||||
|
||||
// Add a dummy control node for spacing.
|
||||
Control *v_spacer = memnew(Control);
|
||||
version_info_vbc->add_child(v_spacer);
|
||||
|
||||
version_btn = memnew(LinkButton);
|
||||
String hash = String(VERSION_HASH);
|
||||
if (hash.length() != 0) {
|
||||
hash = " " + vformat("[%s]", hash.left(9));
|
||||
}
|
||||
version_btn->set_text(VERSION_FULL_NAME + hash);
|
||||
// Set the text to copy in metadata as it slightly differs from the button's text.
|
||||
version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);
|
||||
version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
|
||||
String build_date;
|
||||
if (VERSION_TIMESTAMP > 0) {
|
||||
build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
|
||||
} else {
|
||||
build_date = TTR("(unknown)");
|
||||
}
|
||||
version_btn->set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version number."), build_date));
|
||||
|
||||
version_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorAbout::_version_button_pressed));
|
||||
version_info_vbc->add_child(version_btn);
|
||||
|
||||
Label *about_text = memnew(Label);
|
||||
about_text->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
about_text->set_text(
|
||||
String::utf8("\xc2\xa9 2014-present ") + TTR("Godot Engine contributors") + "." +
|
||||
String::utf8("\n\xc2\xa9 2007-2014 Juan Linietsky, Ariel Manzur.\n"));
|
||||
version_info_vbc->add_child(about_text);
|
||||
|
||||
hbc->add_child(version_info_vbc);
|
||||
|
||||
TabContainer *tc = memnew(TabContainer);
|
||||
tc->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
|
||||
tc->set_custom_minimum_size(Size2(400, 200) * EDSCALE);
|
||||
tc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tc->set_theme_type_variation("TabContainerOdd");
|
||||
vbc->add_child(tc);
|
||||
|
||||
// Authors.
|
||||
|
||||
List<String> dev_sections;
|
||||
dev_sections.push_back(TTR("Project Founders"));
|
||||
dev_sections.push_back(TTR("Lead Developer"));
|
||||
// TRANSLATORS: This refers to a job title.
|
||||
dev_sections.push_back(TTR("Project Manager", "Job Title"));
|
||||
dev_sections.push_back(TTR("Developers"));
|
||||
const char *const *dev_src[] = {
|
||||
AUTHORS_FOUNDERS,
|
||||
AUTHORS_LEAD_DEVELOPERS,
|
||||
AUTHORS_PROJECT_MANAGERS,
|
||||
AUTHORS_DEVELOPERS,
|
||||
};
|
||||
tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1)); // First section (Project Founders) is always one column.
|
||||
|
||||
// Donors.
|
||||
|
||||
List<String> donor_sections;
|
||||
donor_sections.push_back(TTR("Patrons"));
|
||||
donor_sections.push_back(TTR("Platinum Sponsors"));
|
||||
donor_sections.push_back(TTR("Gold Sponsors"));
|
||||
donor_sections.push_back(TTR("Silver Sponsors"));
|
||||
donor_sections.push_back(TTR("Diamond Members"));
|
||||
donor_sections.push_back(TTR("Titanium Members"));
|
||||
donor_sections.push_back(TTR("Platinum Members"));
|
||||
donor_sections.push_back(TTR("Gold Members"));
|
||||
const char *const *donor_src[] = {
|
||||
DONORS_PATRONS,
|
||||
DONORS_SPONSORS_PLATINUM,
|
||||
DONORS_SPONSORS_GOLD,
|
||||
DONORS_SPONSORS_SILVER,
|
||||
DONORS_MEMBERS_DIAMOND,
|
||||
DONORS_MEMBERS_TITANIUM,
|
||||
DONORS_MEMBERS_PLATINUM,
|
||||
DONORS_MEMBERS_GOLD,
|
||||
};
|
||||
tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 0b1, true)); // First section (Patron) is one column.
|
||||
|
||||
// License.
|
||||
|
||||
license_text_label = memnew(RichTextLabel);
|
||||
license_text_label->set_threaded(true);
|
||||
license_text_label->set_name(TTR("License"));
|
||||
license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT));
|
||||
tc->add_child(license_text_label);
|
||||
|
||||
// Thirdparty License.
|
||||
|
||||
VBoxContainer *license_thirdparty = memnew(VBoxContainer);
|
||||
license_thirdparty->set_name(TTR("Third-party Licenses"));
|
||||
license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tc->add_child(license_thirdparty);
|
||||
|
||||
Label *tpl_label = memnew(Label);
|
||||
tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tpl_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
|
||||
tpl_label->set_text(TTR("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms."));
|
||||
tpl_label->set_size(Size2(630, 1) * EDSCALE);
|
||||
license_thirdparty->add_child(tpl_label);
|
||||
|
||||
HSplitContainer *tpl_hbc = memnew(HSplitContainer);
|
||||
tpl_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tpl_hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tpl_hbc->set_split_offset(240 * EDSCALE);
|
||||
license_thirdparty->add_child(tpl_hbc);
|
||||
|
||||
_tpl_tree = memnew(Tree);
|
||||
_tpl_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
_tpl_tree->set_hide_root(true);
|
||||
TreeItem *root = _tpl_tree->create_item();
|
||||
TreeItem *tpl_ti_all = _tpl_tree->create_item(root);
|
||||
tpl_ti_all->set_text(0, TTR("All Components"));
|
||||
TreeItem *tpl_ti_tp = _tpl_tree->create_item(root);
|
||||
tpl_ti_tp->set_text(0, TTR("Components"));
|
||||
tpl_ti_tp->set_selectable(0, false);
|
||||
TreeItem *tpl_ti_lc = _tpl_tree->create_item(root);
|
||||
tpl_ti_lc->set_text(0, TTR("Licenses"));
|
||||
tpl_ti_lc->set_selectable(0, false);
|
||||
String long_text = "";
|
||||
for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
|
||||
const ComponentCopyright &component = COPYRIGHT_INFO[component_index];
|
||||
TreeItem *ti = _tpl_tree->create_item(tpl_ti_tp);
|
||||
String component_name = String::utf8(component.name);
|
||||
ti->set_text(0, component_name);
|
||||
String text = component_name + "\n";
|
||||
long_text += "- " + component_name + "\n";
|
||||
for (int part_index = 0; part_index < component.part_count; part_index++) {
|
||||
const ComponentCopyrightPart &part = component.parts[part_index];
|
||||
text += "\n Files:";
|
||||
for (int file_num = 0; file_num < part.file_count; file_num++) {
|
||||
text += "\n " + String::utf8(part.files[file_num]);
|
||||
}
|
||||
String copyright;
|
||||
for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) {
|
||||
copyright += String::utf8("\n \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]);
|
||||
}
|
||||
text += copyright;
|
||||
long_text += copyright;
|
||||
String license = "\n License: " + String::utf8(part.license) + "\n";
|
||||
text += license;
|
||||
long_text += license + "\n";
|
||||
}
|
||||
ti->set_metadata(0, text);
|
||||
}
|
||||
for (int i = 0; i < LICENSE_COUNT; i++) {
|
||||
TreeItem *ti = _tpl_tree->create_item(tpl_ti_lc);
|
||||
String licensename = String::utf8(LICENSE_NAMES[i]);
|
||||
ti->set_text(0, licensename);
|
||||
long_text += "- " + licensename + "\n\n";
|
||||
String licensebody = String::utf8(LICENSE_BODIES[i]);
|
||||
ti->set_metadata(0, licensebody);
|
||||
long_text += " " + licensebody.replace("\n", "\n ") + "\n\n";
|
||||
}
|
||||
tpl_ti_all->set_metadata(0, long_text);
|
||||
tpl_hbc->add_child(_tpl_tree);
|
||||
|
||||
_tpl_text = memnew(RichTextLabel);
|
||||
_tpl_text->set_threaded(true);
|
||||
_tpl_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
_tpl_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tpl_hbc->add_child(_tpl_text);
|
||||
|
||||
_tpl_tree->connect(SceneStringName(item_selected), callable_mp(this, &EditorAbout::_license_tree_selected));
|
||||
tpl_ti_all->select(0);
|
||||
_tpl_text->set_text(tpl_ti_all->get_metadata(0));
|
||||
}
|
||||
|
||||
EditorAbout::~EditorAbout() {}
|
||||
76
engine/editor/editor_about.h
Normal file
76
engine/editor/editor_about.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**************************************************************************/
|
||||
/* editor_about.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_ABOUT_H
|
||||
#define EDITOR_ABOUT_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/link_button.h"
|
||||
#include "scene/gui/rich_text_label.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
/**
|
||||
* NOTE: Do not assume the EditorNode singleton to be available in this class' methods.
|
||||
* EditorAbout is also used from the project manager where EditorNode isn't initialized.
|
||||
*/
|
||||
class EditorAbout : public AcceptDialog {
|
||||
GDCLASS(EditorAbout, AcceptDialog);
|
||||
|
||||
static const String META_TEXT_TO_COPY;
|
||||
|
||||
private:
|
||||
void _license_tree_selected();
|
||||
void _version_button_pressed();
|
||||
void _item_with_website_selected(int p_id, ItemList *p_il);
|
||||
void _item_list_resized(ItemList *p_il);
|
||||
ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false);
|
||||
|
||||
LinkButton *version_btn = nullptr;
|
||||
Tree *_tpl_tree = nullptr;
|
||||
RichTextLabel *license_text_label = nullptr;
|
||||
RichTextLabel *_tpl_text = nullptr;
|
||||
TextureRect *_logo = nullptr;
|
||||
Vector<ItemList *> name_lists;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
EditorAbout();
|
||||
~EditorAbout();
|
||||
};
|
||||
|
||||
#endif // EDITOR_ABOUT_H
|
||||
763
engine/editor/editor_asset_installer.cpp
Normal file
763
engine/editor/editor_asset_installer.cpp
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
/**************************************************************************/
|
||||
/* editor_asset_installer.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_asset_installer.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/zip_io.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/gui/editor_toaster.h"
|
||||
#include "editor/progress_dialog.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/link_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
|
||||
void EditorAssetInstaller::_item_checked_cbk() {
|
||||
if (updating_source || !source_tree->get_edited()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating_source = true;
|
||||
TreeItem *item = source_tree->get_edited();
|
||||
item->propagate_check(0);
|
||||
_fix_conflicted_indeterminate_state(source_tree->get_root(), 0);
|
||||
_update_confirm_button();
|
||||
_rebuild_destination_tree();
|
||||
updating_source = false;
|
||||
}
|
||||
|
||||
// Determine parent state based on non-conflict children, to avoid indeterminate state, and allow toggle dir with conflicts.
|
||||
bool EditorAssetInstaller::_fix_conflicted_indeterminate_state(TreeItem *p_item, int p_column) {
|
||||
if (p_item->get_child_count() == 0) {
|
||||
return false;
|
||||
}
|
||||
bool all_non_conflict_checked = true;
|
||||
bool all_non_conflict_unchecked = true;
|
||||
bool has_conflict_child = false;
|
||||
bool has_indeterminate_child = false;
|
||||
TreeItem *child_item = p_item->get_first_child();
|
||||
while (child_item) {
|
||||
has_conflict_child |= _fix_conflicted_indeterminate_state(child_item, p_column);
|
||||
Dictionary child_meta = child_item->get_metadata(p_column);
|
||||
bool child_conflict = child_meta.get("is_conflict", false);
|
||||
if (child_conflict) {
|
||||
child_item->set_checked(p_column, false);
|
||||
has_conflict_child = true;
|
||||
} else {
|
||||
bool child_checked = child_item->is_checked(p_column);
|
||||
bool child_indeterminate = child_item->is_indeterminate(p_column);
|
||||
all_non_conflict_checked &= (child_checked || child_indeterminate);
|
||||
all_non_conflict_unchecked &= !child_checked;
|
||||
has_indeterminate_child |= child_indeterminate;
|
||||
}
|
||||
child_item = child_item->get_next();
|
||||
}
|
||||
if (has_indeterminate_child) {
|
||||
p_item->set_indeterminate(p_column, true);
|
||||
} else if (all_non_conflict_checked) {
|
||||
p_item->set_checked(p_column, true);
|
||||
} else if (all_non_conflict_unchecked) {
|
||||
p_item->set_checked(p_column, false);
|
||||
}
|
||||
if (has_conflict_child) {
|
||||
p_item->set_custom_color(p_column, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
} else {
|
||||
p_item->clear_custom_color(p_column);
|
||||
}
|
||||
return has_conflict_child;
|
||||
}
|
||||
|
||||
bool EditorAssetInstaller::_is_item_checked(const String &p_source_path) const {
|
||||
return file_item_map.has(p_source_path) && (file_item_map[p_source_path]->is_checked(0) || file_item_map[p_source_path]->is_indeterminate(0));
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::open_asset(const String &p_path, bool p_autoskip_toplevel) {
|
||||
package_path = p_path;
|
||||
asset_files.clear();
|
||||
|
||||
Ref<FileAccess> io_fa;
|
||||
zlib_filefunc_def io = zipio_create_io(&io_fa);
|
||||
|
||||
unzFile pkg = unzOpen2(p_path.utf8().get_data(), &io);
|
||||
if (!pkg) {
|
||||
EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = unzGoToFirstFile(pkg);
|
||||
|
||||
while (ret == UNZ_OK) {
|
||||
//get filename
|
||||
unz_file_info info;
|
||||
char fname[16384];
|
||||
unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
|
||||
|
||||
String source_name = String::utf8(fname);
|
||||
|
||||
// Create intermediate directories if they aren't reported by unzip.
|
||||
// We are only interested in subfolders, so skip the root slash.
|
||||
int separator = source_name.find("/", 1);
|
||||
while (separator != -1) {
|
||||
String dir_name = source_name.substr(0, separator + 1);
|
||||
if (!dir_name.is_empty() && !asset_files.has(dir_name)) {
|
||||
asset_files.insert(dir_name);
|
||||
}
|
||||
|
||||
separator = source_name.find("/", separator + 1);
|
||||
}
|
||||
|
||||
if (!source_name.is_empty() && !asset_files.has(source_name)) {
|
||||
asset_files.insert(source_name);
|
||||
}
|
||||
|
||||
ret = unzGoToNextFile(pkg);
|
||||
}
|
||||
|
||||
unzClose(pkg);
|
||||
|
||||
asset_title_label->set_text(asset_name);
|
||||
|
||||
_check_has_toplevel();
|
||||
// Default to false, unless forced.
|
||||
skip_toplevel = p_autoskip_toplevel;
|
||||
skip_toplevel_check->set_block_signals(true);
|
||||
skip_toplevel_check->set_pressed(!skip_toplevel_check->is_disabled() && skip_toplevel);
|
||||
skip_toplevel_check->set_block_signals(false);
|
||||
|
||||
_update_file_mappings();
|
||||
_rebuild_source_tree();
|
||||
_rebuild_destination_tree();
|
||||
|
||||
popup_centered_clamped(Size2(620, 640) * EDSCALE);
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_update_file_mappings() {
|
||||
mapped_files.clear();
|
||||
|
||||
bool first = true;
|
||||
for (const String &E : asset_files) {
|
||||
if (first) {
|
||||
first = false;
|
||||
|
||||
if (!toplevel_prefix.is_empty() && skip_toplevel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String path = E; // We're going to mutate it.
|
||||
if (!toplevel_prefix.is_empty() && skip_toplevel) {
|
||||
path = path.trim_prefix(toplevel_prefix);
|
||||
}
|
||||
|
||||
mapped_files[E] = path;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_rebuild_source_tree() {
|
||||
updating_source = true;
|
||||
source_tree->clear();
|
||||
|
||||
TreeItem *root = source_tree->create_item();
|
||||
root->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
root->set_checked(0, true);
|
||||
root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
|
||||
root->set_text(0, "/");
|
||||
root->set_editable(0, true);
|
||||
|
||||
file_item_map.clear();
|
||||
HashMap<String, TreeItem *> directory_item_map;
|
||||
int num_file_conflicts = 0;
|
||||
first_file_conflict = nullptr;
|
||||
|
||||
for (const String &E : asset_files) {
|
||||
String path = E; // We're going to mutate it.
|
||||
|
||||
bool is_directory = false;
|
||||
if (path.ends_with("/")) {
|
||||
path = path.trim_suffix("/");
|
||||
is_directory = true;
|
||||
}
|
||||
|
||||
TreeItem *parent_item;
|
||||
|
||||
int separator = path.rfind("/");
|
||||
if (separator == -1) {
|
||||
parent_item = root;
|
||||
} else {
|
||||
String parent_path = path.substr(0, separator);
|
||||
HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);
|
||||
ERR_CONTINUE(!I);
|
||||
parent_item = I->value;
|
||||
}
|
||||
|
||||
TreeItem *ti;
|
||||
if (is_directory) {
|
||||
ti = _create_dir_item(source_tree, parent_item, path, directory_item_map);
|
||||
} else {
|
||||
ti = _create_file_item(source_tree, parent_item, path, &num_file_conflicts);
|
||||
}
|
||||
file_item_map[E] = ti;
|
||||
}
|
||||
|
||||
_update_conflict_status(num_file_conflicts);
|
||||
_update_confirm_button();
|
||||
|
||||
updating_source = false;
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_update_source_tree() {
|
||||
int num_file_conflicts = 0;
|
||||
first_file_conflict = nullptr;
|
||||
|
||||
for (const KeyValue<String, TreeItem *> &E : file_item_map) {
|
||||
TreeItem *ti = E.value;
|
||||
Dictionary item_meta = ti->get_metadata(0);
|
||||
if ((bool)item_meta.get("is_dir", false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String asset_path = item_meta.get("asset_path", "");
|
||||
ERR_CONTINUE(asset_path.is_empty());
|
||||
|
||||
bool target_exists = _update_source_item_status(ti, asset_path);
|
||||
if (target_exists) {
|
||||
if (first_file_conflict == nullptr) {
|
||||
first_file_conflict = ti;
|
||||
}
|
||||
num_file_conflicts += 1;
|
||||
}
|
||||
|
||||
item_meta["is_conflict"] = target_exists;
|
||||
ti->set_metadata(0, item_meta);
|
||||
}
|
||||
|
||||
_update_conflict_status(num_file_conflicts);
|
||||
_update_confirm_button();
|
||||
}
|
||||
|
||||
bool EditorAssetInstaller::_update_source_item_status(TreeItem *p_item, const String &p_path) {
|
||||
ERR_FAIL_COND_V(!mapped_files.has(p_path), false);
|
||||
String target_path = target_dir_path.path_join(mapped_files[p_path]);
|
||||
|
||||
bool target_exists = FileAccess::exists(target_path);
|
||||
if (target_exists) {
|
||||
p_item->set_custom_color(0, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
p_item->set_tooltip_text(0, vformat(TTR("%s (already exists)"), target_path));
|
||||
p_item->set_checked(0, false);
|
||||
} else {
|
||||
p_item->clear_custom_color(0);
|
||||
p_item->set_tooltip_text(0, target_path);
|
||||
p_item->set_checked(0, true);
|
||||
}
|
||||
|
||||
p_item->propagate_check(0);
|
||||
_fix_conflicted_indeterminate_state(p_item->get_tree()->get_root(), 0);
|
||||
return target_exists;
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_rebuild_destination_tree() {
|
||||
destination_tree->clear();
|
||||
|
||||
TreeItem *root = destination_tree->create_item();
|
||||
root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
|
||||
root->set_text(0, target_dir_path + (target_dir_path == "res://" ? "" : "/"));
|
||||
|
||||
HashMap<String, TreeItem *> directory_item_map;
|
||||
|
||||
for (const KeyValue<String, String> &E : mapped_files) {
|
||||
if (!_is_item_checked(E.key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String path = E.value; // We're going to mutate it.
|
||||
|
||||
bool is_directory = false;
|
||||
if (path.ends_with("/")) {
|
||||
path = path.trim_suffix("/");
|
||||
is_directory = true;
|
||||
}
|
||||
|
||||
TreeItem *parent_item;
|
||||
|
||||
int separator = path.rfind("/");
|
||||
if (separator == -1) {
|
||||
parent_item = root;
|
||||
} else {
|
||||
String parent_path = path.substr(0, separator);
|
||||
HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);
|
||||
ERR_CONTINUE(!I);
|
||||
parent_item = I->value;
|
||||
}
|
||||
|
||||
if (is_directory) {
|
||||
_create_dir_item(destination_tree, parent_item, path, directory_item_map);
|
||||
} else {
|
||||
int num_file_conflicts = 0; // Don't need it, but need to pass something.
|
||||
_create_file_item(destination_tree, parent_item, path, &num_file_conflicts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *EditorAssetInstaller::_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map) {
|
||||
TreeItem *ti = p_tree->create_item(p_parent);
|
||||
|
||||
if (p_tree == source_tree) {
|
||||
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
ti->set_editable(0, true);
|
||||
ti->set_checked(0, true);
|
||||
ti->propagate_check(0);
|
||||
_fix_conflicted_indeterminate_state(ti->get_tree()->get_root(), 0);
|
||||
|
||||
Dictionary meta;
|
||||
meta["asset_path"] = p_path + "/";
|
||||
meta["is_dir"] = true;
|
||||
meta["is_conflict"] = false;
|
||||
ti->set_metadata(0, meta);
|
||||
}
|
||||
|
||||
ti->set_text(0, p_path.get_file() + "/");
|
||||
ti->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
|
||||
|
||||
p_item_map[p_path] = ti;
|
||||
return ti;
|
||||
}
|
||||
|
||||
TreeItem *EditorAssetInstaller::_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts) {
|
||||
TreeItem *ti = p_tree->create_item(p_parent);
|
||||
|
||||
if (p_tree == source_tree) {
|
||||
ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
ti->set_editable(0, true);
|
||||
|
||||
bool target_exists = _update_source_item_status(ti, p_path);
|
||||
if (target_exists) {
|
||||
if (first_file_conflict == nullptr) {
|
||||
first_file_conflict = ti;
|
||||
}
|
||||
*r_conflicts += 1;
|
||||
}
|
||||
|
||||
Dictionary meta;
|
||||
meta["asset_path"] = p_path;
|
||||
meta["is_dir"] = false;
|
||||
meta["is_conflict"] = target_exists;
|
||||
ti->set_metadata(0, meta);
|
||||
}
|
||||
|
||||
String file = p_path.get_file();
|
||||
String extension = file.get_extension().to_lower();
|
||||
if (extension_icon_map.has(extension)) {
|
||||
ti->set_icon(0, extension_icon_map[extension]);
|
||||
} else {
|
||||
ti->set_icon(0, generic_extension_icon);
|
||||
}
|
||||
ti->set_text(0, file);
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_update_conflict_status(int p_conflicts) {
|
||||
if (p_conflicts >= 1) {
|
||||
asset_conflicts_link->set_text(vformat(TTRN("%d file conflicts with your project and won't be installed", "%d files conflict with your project and won't be installed", p_conflicts), p_conflicts));
|
||||
asset_conflicts_link->show();
|
||||
asset_conflicts_label->hide();
|
||||
} else {
|
||||
asset_conflicts_link->hide();
|
||||
asset_conflicts_label->show();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_update_confirm_button() {
|
||||
TreeItem *root = source_tree->get_root();
|
||||
get_ok_button()->set_disabled(!root || (!root->is_checked(0) && !root->is_indeterminate(0)));
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_toggle_source_tree(bool p_visible, bool p_scroll_to_error) {
|
||||
source_tree_vb->set_visible(p_visible);
|
||||
show_source_files_button->set_pressed_no_signal(p_visible); // To keep in sync if triggered by something else.
|
||||
|
||||
if (p_visible) {
|
||||
show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
|
||||
} else {
|
||||
show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
|
||||
}
|
||||
|
||||
if (p_visible && p_scroll_to_error && first_file_conflict) {
|
||||
source_tree->scroll_to_item(first_file_conflict, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_check_has_toplevel() {
|
||||
// Check if the file structure has a distinct top-level directory. This is typical
|
||||
// for archives generated by GitHub, etc, but not for manually created ZIPs.
|
||||
|
||||
toplevel_prefix = "";
|
||||
skip_toplevel_check->set_pressed(false);
|
||||
skip_toplevel_check->set_disabled(true);
|
||||
skip_toplevel_check->set_tooltip_text(TTR("This asset doesn't have a root directory, so it can't be ignored."));
|
||||
|
||||
if (asset_files.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String first_asset;
|
||||
for (const String &E : asset_files) {
|
||||
if (first_asset.is_empty()) { // Checking the first file/directory.
|
||||
if (!E.ends_with("/")) {
|
||||
return; // No directories in this asset.
|
||||
}
|
||||
|
||||
// We will match everything else against this directory.
|
||||
first_asset = E;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!E.begins_with(first_asset)) {
|
||||
return; // Found a file or a directory that doesn't share the same base path.
|
||||
}
|
||||
}
|
||||
|
||||
toplevel_prefix = first_asset;
|
||||
skip_toplevel_check->set_disabled(false);
|
||||
skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_set_skip_toplevel(bool p_checked) {
|
||||
if (skip_toplevel == p_checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
skip_toplevel = p_checked;
|
||||
_update_file_mappings();
|
||||
_update_source_tree();
|
||||
_rebuild_destination_tree();
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_open_target_dir_dialog() {
|
||||
if (!target_dir_dialog) {
|
||||
target_dir_dialog = memnew(EditorFileDialog);
|
||||
target_dir_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
|
||||
target_dir_dialog->set_title(TTR("Select Install Folder"));
|
||||
target_dir_dialog->set_current_dir(target_dir_path);
|
||||
target_dir_dialog->connect("dir_selected", callable_mp(this, &EditorAssetInstaller::_target_dir_selected));
|
||||
add_child(target_dir_dialog);
|
||||
}
|
||||
|
||||
target_dir_dialog->popup_file_dialog();
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_target_dir_selected(const String &p_target_path) {
|
||||
if (target_dir_path == p_target_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
target_dir_path = p_target_path;
|
||||
_update_file_mappings();
|
||||
_update_source_tree();
|
||||
_rebuild_destination_tree();
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::ok_pressed() {
|
||||
_install_asset();
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_install_asset() {
|
||||
Ref<FileAccess> io_fa;
|
||||
zlib_filefunc_def io = zipio_create_io(&io_fa);
|
||||
|
||||
unzFile pkg = unzOpen2(package_path.utf8().get_data(), &io);
|
||||
if (!pkg) {
|
||||
EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<String> failed_files;
|
||||
int ret = unzGoToFirstFile(pkg);
|
||||
|
||||
ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Assets"), file_item_map.size());
|
||||
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
for (int idx = 0; ret == UNZ_OK; ret = unzGoToNextFile(pkg), idx++) {
|
||||
unz_file_info info;
|
||||
char fname[16384];
|
||||
ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
|
||||
if (ret != UNZ_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
String source_name = String::utf8(fname);
|
||||
if (!_is_item_checked(source_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HashMap<String, String>::Iterator E = mapped_files.find(source_name);
|
||||
if (!E) {
|
||||
continue; // No remapped path means we don't want it; most likely the root.
|
||||
}
|
||||
|
||||
String target_path = target_dir_path.path_join(E->value);
|
||||
|
||||
Dictionary asset_meta = file_item_map[source_name]->get_metadata(0);
|
||||
bool is_dir = asset_meta.get("is_dir", false);
|
||||
if (is_dir) {
|
||||
if (target_path.ends_with("/")) {
|
||||
target_path = target_path.substr(0, target_path.length() - 1);
|
||||
}
|
||||
|
||||
da->make_dir_recursive(target_path);
|
||||
} else {
|
||||
Vector<uint8_t> uncomp_data;
|
||||
uncomp_data.resize(info.uncompressed_size);
|
||||
|
||||
unzOpenCurrentFile(pkg);
|
||||
unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());
|
||||
unzCloseCurrentFile(pkg);
|
||||
|
||||
// Ensure that the target folder exists.
|
||||
da->make_dir_recursive(target_path.get_base_dir());
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(target_path, FileAccess::WRITE);
|
||||
if (f.is_valid()) {
|
||||
f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
|
||||
} else {
|
||||
failed_files.push_back(target_path);
|
||||
}
|
||||
|
||||
ProgressDialog::get_singleton()->task_step("uncompress", target_path, idx);
|
||||
}
|
||||
}
|
||||
|
||||
ProgressDialog::get_singleton()->end_task("uncompress");
|
||||
unzClose(pkg);
|
||||
|
||||
if (failed_files.size()) {
|
||||
String msg = vformat(TTR("The following files failed extraction from asset \"%s\":"), asset_name) + "\n\n";
|
||||
for (int i = 0; i < failed_files.size(); i++) {
|
||||
if (i > 10) {
|
||||
msg += "\n" + vformat(TTR("(and %s more files)"), itos(failed_files.size() - i));
|
||||
break;
|
||||
}
|
||||
msg += "\n" + failed_files[i];
|
||||
}
|
||||
if (EditorNode::get_singleton() != nullptr) {
|
||||
EditorNode::get_singleton()->show_warning(msg);
|
||||
}
|
||||
} else {
|
||||
if (EditorNode::get_singleton() != nullptr) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Asset \"%s\" installed successfully!"), asset_name), TTR("Success!"));
|
||||
}
|
||||
}
|
||||
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::set_asset_name(const String &p_asset_name) {
|
||||
asset_name = p_asset_name;
|
||||
}
|
||||
|
||||
String EditorAssetInstaller::get_asset_name() const {
|
||||
return asset_name;
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (show_source_files_button->is_pressed()) {
|
||||
show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
|
||||
} else {
|
||||
show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
|
||||
}
|
||||
asset_conflicts_link->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
|
||||
generic_extension_icon = get_editor_theme_icon(SNAME("Object"));
|
||||
|
||||
extension_icon_map.clear();
|
||||
{
|
||||
extension_icon_map["bmp"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["dds"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["exr"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["hdr"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["jpg"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["jpeg"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["png"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["svg"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["tga"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
extension_icon_map["webp"] = get_editor_theme_icon(SNAME("ImageTexture"));
|
||||
|
||||
extension_icon_map["wav"] = get_editor_theme_icon(SNAME("AudioStreamWAV"));
|
||||
extension_icon_map["ogg"] = get_editor_theme_icon(SNAME("AudioStreamOggVorbis"));
|
||||
extension_icon_map["mp3"] = get_editor_theme_icon(SNAME("AudioStreamMP3"));
|
||||
|
||||
extension_icon_map["scn"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
extension_icon_map["tscn"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
extension_icon_map["escn"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
extension_icon_map["dae"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
extension_icon_map["gltf"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
extension_icon_map["glb"] = get_editor_theme_icon(SNAME("PackedScene"));
|
||||
|
||||
extension_icon_map["gdshader"] = get_editor_theme_icon(SNAME("Shader"));
|
||||
extension_icon_map["gdshaderinc"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["gd"] = get_editor_theme_icon(SNAME("GDScript"));
|
||||
if (ClassDB::class_exists("CSharpScript")) {
|
||||
extension_icon_map["cs"] = get_editor_theme_icon(SNAME("CSharpScript"));
|
||||
} else {
|
||||
// Mark C# support as unavailable.
|
||||
extension_icon_map["cs"] = get_editor_theme_icon(SNAME("ImportFail"));
|
||||
}
|
||||
|
||||
extension_icon_map["res"] = get_editor_theme_icon(SNAME("Resource"));
|
||||
extension_icon_map["tres"] = get_editor_theme_icon(SNAME("Resource"));
|
||||
extension_icon_map["atlastex"] = get_editor_theme_icon(SNAME("AtlasTexture"));
|
||||
// By default, OBJ files are imported as Mesh resources rather than PackedScenes.
|
||||
extension_icon_map["obj"] = get_editor_theme_icon(SNAME("MeshItem"));
|
||||
|
||||
extension_icon_map["txt"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["md"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["rst"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["json"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["yml"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["yaml"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["toml"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["cfg"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
extension_icon_map["ini"] = get_editor_theme_icon(SNAME("TextFile"));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAssetInstaller::_bind_methods() {
|
||||
}
|
||||
|
||||
EditorAssetInstaller::EditorAssetInstaller() {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
// Status bar.
|
||||
|
||||
HBoxContainer *asset_status = memnew(HBoxContainer);
|
||||
vb->add_child(asset_status);
|
||||
|
||||
Label *asset_label = memnew(Label);
|
||||
asset_label->set_text(TTR("Asset:"));
|
||||
asset_label->set_theme_type_variation("HeaderSmall");
|
||||
asset_status->add_child(asset_label);
|
||||
|
||||
asset_title_label = memnew(Label);
|
||||
asset_status->add_child(asset_title_label);
|
||||
|
||||
// File remapping controls.
|
||||
|
||||
HBoxContainer *remapping_tools = memnew(HBoxContainer);
|
||||
vb->add_child(remapping_tools);
|
||||
|
||||
show_source_files_button = memnew(Button);
|
||||
show_source_files_button->set_toggle_mode(true);
|
||||
show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install."));
|
||||
remapping_tools->add_child(show_source_files_button);
|
||||
show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
|
||||
|
||||
Button *target_dir_button = memnew(Button);
|
||||
target_dir_button->set_text(TTR("Change Install Folder"));
|
||||
target_dir_button->set_tooltip_text(TTR("Change the folder where the contents of the asset are going to be installed."));
|
||||
remapping_tools->add_child(target_dir_button);
|
||||
target_dir_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetInstaller::_open_target_dir_dialog));
|
||||
|
||||
remapping_tools->add_child(memnew(VSeparator));
|
||||
|
||||
skip_toplevel_check = memnew(CheckBox);
|
||||
skip_toplevel_check->set_text(TTR("Ignore asset root"));
|
||||
skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
|
||||
skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
|
||||
remapping_tools->add_child(skip_toplevel_check);
|
||||
|
||||
remapping_tools->add_spacer();
|
||||
|
||||
asset_conflicts_label = memnew(Label);
|
||||
asset_conflicts_label->set_theme_type_variation("HeaderSmall");
|
||||
asset_conflicts_label->set_text(TTR("No files conflict with your project"));
|
||||
remapping_tools->add_child(asset_conflicts_label);
|
||||
asset_conflicts_link = memnew(LinkButton);
|
||||
asset_conflicts_link->set_theme_type_variation("HeaderSmallLink");
|
||||
asset_conflicts_link->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
asset_conflicts_link->set_tooltip_text(TTR("Show contents of the asset and conflicting files."));
|
||||
asset_conflicts_link->set_visible(false);
|
||||
remapping_tools->add_child(asset_conflicts_link);
|
||||
asset_conflicts_link->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(true, true));
|
||||
|
||||
// File hierarchy trees.
|
||||
|
||||
HSplitContainer *tree_split = memnew(HSplitContainer);
|
||||
tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vb->add_child(tree_split);
|
||||
|
||||
source_tree_vb = memnew(VBoxContainer);
|
||||
source_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
source_tree_vb->set_visible(show_source_files_button->is_pressed());
|
||||
tree_split->add_child(source_tree_vb);
|
||||
|
||||
Label *source_tree_label = memnew(Label);
|
||||
source_tree_label->set_text(TTR("Contents of the asset:"));
|
||||
source_tree_label->set_theme_type_variation("HeaderSmall");
|
||||
source_tree_vb->add_child(source_tree_label);
|
||||
|
||||
source_tree = memnew(Tree);
|
||||
source_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
source_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
source_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
|
||||
source_tree_vb->add_child(source_tree);
|
||||
|
||||
VBoxContainer *destination_tree_vb = memnew(VBoxContainer);
|
||||
destination_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tree_split->add_child(destination_tree_vb);
|
||||
|
||||
Label *destination_tree_label = memnew(Label);
|
||||
destination_tree_label->set_text(TTR("Installation preview:"));
|
||||
destination_tree_label->set_theme_type_variation("HeaderSmall");
|
||||
destination_tree_vb->add_child(destination_tree_label);
|
||||
|
||||
destination_tree = memnew(Tree);
|
||||
destination_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
destination_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
destination_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
|
||||
destination_tree_vb->add_child(destination_tree);
|
||||
|
||||
// Dialog configuration.
|
||||
|
||||
set_title(TTR("Configure Asset Before Installing"));
|
||||
set_ok_button_text(TTR("Install"));
|
||||
set_hide_on_ok(true);
|
||||
}
|
||||
110
engine/editor/editor_asset_installer.h
Normal file
110
engine/editor/editor_asset_installer.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**************************************************************************/
|
||||
/* editor_asset_installer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_ASSET_INSTALLER_H
|
||||
#define EDITOR_ASSET_INSTALLER_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class CheckBox;
|
||||
class EditorFileDialog;
|
||||
class Label;
|
||||
class LinkButton;
|
||||
|
||||
class EditorAssetInstaller : public ConfirmationDialog {
|
||||
GDCLASS(EditorAssetInstaller, ConfirmationDialog);
|
||||
|
||||
VBoxContainer *source_tree_vb = nullptr;
|
||||
Tree *source_tree = nullptr;
|
||||
Tree *destination_tree = nullptr;
|
||||
Label *asset_title_label = nullptr;
|
||||
Label *asset_conflicts_label = nullptr;
|
||||
LinkButton *asset_conflicts_link = nullptr;
|
||||
|
||||
Button *show_source_files_button = nullptr;
|
||||
CheckBox *skip_toplevel_check = nullptr;
|
||||
EditorFileDialog *target_dir_dialog = nullptr;
|
||||
|
||||
String package_path;
|
||||
String asset_name;
|
||||
HashSet<String> asset_files;
|
||||
HashMap<String, String> mapped_files;
|
||||
HashMap<String, TreeItem *> file_item_map;
|
||||
|
||||
TreeItem *first_file_conflict = nullptr;
|
||||
|
||||
Ref<Texture2D> generic_extension_icon;
|
||||
HashMap<String, Ref<Texture2D>> extension_icon_map;
|
||||
|
||||
bool updating_source = false;
|
||||
String toplevel_prefix;
|
||||
bool skip_toplevel = false;
|
||||
String target_dir_path = "res://";
|
||||
|
||||
void _check_has_toplevel();
|
||||
void _set_skip_toplevel(bool p_checked);
|
||||
|
||||
void _open_target_dir_dialog();
|
||||
void _target_dir_selected(const String &p_target_path);
|
||||
|
||||
void _update_file_mappings();
|
||||
void _rebuild_source_tree();
|
||||
void _update_source_tree();
|
||||
bool _update_source_item_status(TreeItem *p_item, const String &p_path);
|
||||
void _rebuild_destination_tree();
|
||||
TreeItem *_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map);
|
||||
TreeItem *_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts);
|
||||
|
||||
void _update_conflict_status(int p_conflicts);
|
||||
void _update_confirm_button();
|
||||
void _toggle_source_tree(bool p_visible, bool p_scroll_to_error = false);
|
||||
|
||||
void _item_checked_cbk();
|
||||
bool _fix_conflicted_indeterminate_state(TreeItem *p_item, int p_column);
|
||||
bool _is_item_checked(const String &p_source_path) const;
|
||||
|
||||
void _install_asset();
|
||||
virtual void ok_pressed() override;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void open_asset(const String &p_path, bool p_autoskip_toplevel = false);
|
||||
|
||||
void set_asset_name(const String &p_asset_name);
|
||||
String get_asset_name() const;
|
||||
|
||||
EditorAssetInstaller();
|
||||
};
|
||||
|
||||
#endif // EDITOR_ASSET_INSTALLER_H
|
||||
247
engine/editor/editor_atlas_packer.cpp
Normal file
247
engine/editor/editor_atlas_packer.cpp
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
/**************************************************************************/
|
||||
/* editor_atlas_packer.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_atlas_packer.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector2i.h"
|
||||
|
||||
void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_height, int p_atlas_max_size, int p_cell_resolution) {
|
||||
int divide_by = MIN(64, p_cell_resolution);
|
||||
Vector<PlottedBitmap> bitmaps;
|
||||
|
||||
int max_w = 0;
|
||||
|
||||
for (int i = 0; i < charts.size(); i++) {
|
||||
const Chart &chart = charts[i];
|
||||
|
||||
//generate aabb
|
||||
|
||||
Rect2i aabb;
|
||||
int vertex_count = chart.vertices.size();
|
||||
const Vector2 *vertices = chart.vertices.ptr();
|
||||
|
||||
for (int j = 0; j < vertex_count; j++) {
|
||||
if (j == 0) {
|
||||
aabb.position = vertices[j];
|
||||
} else {
|
||||
aabb.expand_to(vertices[j]);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<BitMap> src_bitmap;
|
||||
src_bitmap.instantiate();
|
||||
src_bitmap->create((aabb.size + Vector2(divide_by - 1, divide_by - 1)) / divide_by);
|
||||
|
||||
int w = src_bitmap->get_size().width;
|
||||
int h = src_bitmap->get_size().height;
|
||||
|
||||
//plot triangles, using divisor
|
||||
|
||||
for (int j = 0; j < chart.faces.size(); j++) {
|
||||
Vector2i v[3];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
Vector2 vtx = chart.vertices[chart.faces[j].vertex[k]];
|
||||
vtx -= aabb.position;
|
||||
vtx /= divide_by;
|
||||
vtx = vtx.min(Vector2(w - 1, h - 1));
|
||||
v[k] = vtx;
|
||||
}
|
||||
|
||||
for (int k = 0; k < 3; k++) {
|
||||
int l = k == 0 ? 2 : k - 1;
|
||||
Vector<Point2i> points = Geometry2D::bresenham_line(v[k], v[l]);
|
||||
for (Point2i point : points) {
|
||||
src_bitmap->set_bitv(point, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//src_bitmap->convert_to_image()->save_png("bitmap" + itos(i) + ".png");
|
||||
|
||||
//grow by 1 for each side
|
||||
|
||||
int bmw = src_bitmap->get_size().width + 2;
|
||||
int bmh = src_bitmap->get_size().height + 2;
|
||||
|
||||
int heights_size = -1;
|
||||
bool transpose = false;
|
||||
if (chart.can_transpose && bmh > bmw) {
|
||||
heights_size = bmh;
|
||||
transpose = true;
|
||||
} else {
|
||||
heights_size = bmw;
|
||||
}
|
||||
|
||||
max_w = MAX(max_w, heights_size);
|
||||
|
||||
Vector<int> top_heights;
|
||||
Vector<int> bottom_heights;
|
||||
top_heights.resize(heights_size);
|
||||
bottom_heights.resize(heights_size);
|
||||
|
||||
for (int x = 0; x < heights_size; x++) {
|
||||
top_heights.write[x] = -1;
|
||||
bottom_heights.write[x] = 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
for (int x = 0; x < bmw; x++) {
|
||||
for (int y = 0; y < bmh; y++) {
|
||||
bool found_pixel = false;
|
||||
for (int lx = x - 1; lx < x + 2 && !found_pixel; lx++) {
|
||||
for (int ly = y - 1; ly < y + 2 && !found_pixel; ly++) {
|
||||
int px = lx - 1;
|
||||
if (px < 0 || px >= w) {
|
||||
continue;
|
||||
}
|
||||
int py = ly - 1;
|
||||
if (py < 0 || py >= h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src_bitmap->get_bit(px, py)) {
|
||||
found_pixel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found_pixel) {
|
||||
if (transpose) {
|
||||
if (x > top_heights[y]) {
|
||||
top_heights.write[y] = x;
|
||||
}
|
||||
if (x < bottom_heights[y]) {
|
||||
bottom_heights.write[y] = x;
|
||||
}
|
||||
} else {
|
||||
if (y > top_heights[x]) {
|
||||
top_heights.write[x] = y;
|
||||
}
|
||||
if (y < bottom_heights[x]) {
|
||||
bottom_heights.write[x] = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String row;
|
||||
for (int j = 0; j < top_heights.size(); j++) {
|
||||
row += "(" + itos(top_heights[j]) + "-" + itos(bottom_heights[j]) + "),";
|
||||
}
|
||||
|
||||
PlottedBitmap plotted_bitmap;
|
||||
plotted_bitmap.offset = aabb.position;
|
||||
plotted_bitmap.top_heights = top_heights;
|
||||
plotted_bitmap.bottom_heights = bottom_heights;
|
||||
plotted_bitmap.chart_index = i;
|
||||
plotted_bitmap.transposed = transpose;
|
||||
plotted_bitmap.area = bmw * bmh;
|
||||
|
||||
bitmaps.push_back(plotted_bitmap);
|
||||
}
|
||||
|
||||
bitmaps.sort();
|
||||
|
||||
int atlas_max_width = nearest_power_of_2_templated(p_atlas_max_size) / divide_by;
|
||||
int atlas_w = nearest_power_of_2_templated(max_w);
|
||||
int atlas_h;
|
||||
while (true) {
|
||||
atlas_h = 0;
|
||||
|
||||
//do a tetris
|
||||
Vector<int> heights;
|
||||
heights.resize(atlas_w);
|
||||
for (int i = 0; i < atlas_w; i++) {
|
||||
heights.write[i] = 0;
|
||||
}
|
||||
|
||||
int *atlas_ptr = heights.ptrw();
|
||||
|
||||
for (int i = 0; i < bitmaps.size(); i++) {
|
||||
int best_height = 0x7FFFFFFF;
|
||||
int best_height_offset = -1;
|
||||
int w = bitmaps[i].top_heights.size();
|
||||
|
||||
const int *top_heights = bitmaps[i].top_heights.ptr();
|
||||
const int *bottom_heights = bitmaps[i].bottom_heights.ptr();
|
||||
|
||||
for (int j = 0; j <= atlas_w - w; j++) {
|
||||
int height = 0;
|
||||
|
||||
for (int k = 0; k < w; k++) {
|
||||
int pixmap_h = bottom_heights[k];
|
||||
if (pixmap_h == 0x7FFFFFFF) {
|
||||
continue; //no pixel here, anything is fine
|
||||
}
|
||||
|
||||
int h = MAX(0, atlas_ptr[j + k] - pixmap_h);
|
||||
if (h > height) {
|
||||
height = h;
|
||||
}
|
||||
}
|
||||
|
||||
if (height < best_height) {
|
||||
best_height = height;
|
||||
best_height_offset = j;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < w; j++) { //add
|
||||
if (top_heights[j] == -1) { //unused
|
||||
continue;
|
||||
}
|
||||
int height = best_height + top_heights[j] + 1;
|
||||
atlas_ptr[j + best_height_offset] = height;
|
||||
atlas_h = MAX(atlas_h, height);
|
||||
}
|
||||
|
||||
// set
|
||||
Vector2 offset = bitmaps[i].offset;
|
||||
if (bitmaps[i].transposed) {
|
||||
SWAP(offset.x, offset.y);
|
||||
}
|
||||
|
||||
Vector2 final_pos = Vector2(best_height_offset * divide_by, best_height * divide_by) + Vector2(divide_by, divide_by) - offset;
|
||||
charts.write[bitmaps[i].chart_index].final_offset = final_pos;
|
||||
charts.write[bitmaps[i].chart_index].transposed = bitmaps[i].transposed;
|
||||
}
|
||||
|
||||
if (atlas_h <= atlas_w * 2 || atlas_w >= atlas_max_width) {
|
||||
break; //ok this one is enough
|
||||
}
|
||||
|
||||
//try again
|
||||
atlas_w *= 2;
|
||||
}
|
||||
|
||||
r_width = atlas_w * divide_by;
|
||||
r_height = atlas_h * divide_by;
|
||||
}
|
||||
74
engine/editor/editor_atlas_packer.h
Normal file
74
engine/editor/editor_atlas_packer.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**************************************************************************/
|
||||
/* editor_atlas_packer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_ATLAS_PACKER_H
|
||||
#define EDITOR_ATLAS_PACKER_H
|
||||
|
||||
#include "core/templates/vector.h"
|
||||
#include "scene/resources/bit_map.h"
|
||||
|
||||
struct Vector2;
|
||||
struct Vector2i;
|
||||
|
||||
class EditorAtlasPacker {
|
||||
public:
|
||||
struct Chart {
|
||||
Vector<Vector2> vertices;
|
||||
struct Face {
|
||||
int vertex[3] = { 0 };
|
||||
};
|
||||
Vector<Face> faces;
|
||||
bool can_transpose = false;
|
||||
|
||||
Vector2 final_offset;
|
||||
bool transposed = false;
|
||||
};
|
||||
|
||||
private:
|
||||
struct PlottedBitmap {
|
||||
int chart_index = 0;
|
||||
Vector2i offset;
|
||||
int area = 0;
|
||||
Vector<int> top_heights;
|
||||
Vector<int> bottom_heights;
|
||||
bool transposed = false;
|
||||
|
||||
Vector2 final_pos;
|
||||
|
||||
bool operator<(const PlottedBitmap &p_bm) const {
|
||||
return area > p_bm.area;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
static void chart_pack(Vector<Chart> &charts, int &r_width, int &r_height, int p_atlas_max_size = 2048, int p_cell_resolution = 4);
|
||||
};
|
||||
|
||||
#endif // EDITOR_ATLAS_PACKER_H
|
||||
1490
engine/editor/editor_audio_buses.cpp
Normal file
1490
engine/editor/editor_audio_buses.cpp
Normal file
File diff suppressed because it is too large
Load diff
287
engine/editor/editor_audio_buses.h
Normal file
287
engine/editor/editor_audio_buses.h
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/**************************************************************************/
|
||||
/* editor_audio_buses.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_AUDIO_BUSES_H
|
||||
#define EDITOR_AUDIO_BUSES_H
|
||||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/panel.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
#include "scene/gui/slider.h"
|
||||
#include "scene/gui/texture_progress_bar.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorAudioBuses;
|
||||
class EditorFileDialog;
|
||||
|
||||
class EditorAudioBus : public PanelContainer {
|
||||
GDCLASS(EditorAudioBus, PanelContainer);
|
||||
|
||||
Ref<Texture2D> disabled_vu;
|
||||
LineEdit *track_name = nullptr;
|
||||
MenuButton *bus_options = nullptr;
|
||||
VSlider *slider = nullptr;
|
||||
|
||||
int cc;
|
||||
static const int CHANNELS_MAX = 4;
|
||||
|
||||
struct {
|
||||
bool prev_active = false;
|
||||
|
||||
float peak_l = 0;
|
||||
float peak_r = 0;
|
||||
|
||||
TextureProgressBar *vu_l = nullptr;
|
||||
TextureProgressBar *vu_r = nullptr;
|
||||
} channel[CHANNELS_MAX];
|
||||
|
||||
OptionButton *send = nullptr;
|
||||
|
||||
PopupMenu *effect_options = nullptr;
|
||||
PopupMenu *bus_popup = nullptr;
|
||||
PopupMenu *delete_effect_popup = nullptr;
|
||||
|
||||
Panel *audio_value_preview_box = nullptr;
|
||||
Label *audio_value_preview_label = nullptr;
|
||||
Timer *preview_timer = nullptr;
|
||||
|
||||
Button *solo = nullptr;
|
||||
Button *mute = nullptr;
|
||||
Button *bypass = nullptr;
|
||||
|
||||
Tree *effects = nullptr;
|
||||
|
||||
bool updating_bus = false;
|
||||
bool is_master;
|
||||
mutable bool hovering_drop = false;
|
||||
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
void _effects_gui_input(Ref<InputEvent> p_event);
|
||||
void _bus_popup_pressed(int p_option);
|
||||
|
||||
void _name_changed(const String &p_new_name);
|
||||
void _name_focus_exit() { _name_changed(track_name->get_text()); }
|
||||
void _volume_changed(float p_normalized);
|
||||
float _normalized_volume_to_scaled_db(float normalized);
|
||||
float _scaled_db_to_normalized_volume(float db);
|
||||
void _show_value(float slider_value);
|
||||
void _hide_value_preview();
|
||||
void _solo_toggled();
|
||||
void _mute_toggled();
|
||||
void _bypass_toggled();
|
||||
void _send_selected(int p_which);
|
||||
void _effect_edited();
|
||||
void _effect_add(int p_which);
|
||||
void _effect_selected();
|
||||
void _delete_effect_pressed(int p_option);
|
||||
void _effect_rmb(const Vector2 &p_pos, MouseButton p_button);
|
||||
void _update_visible_channels();
|
||||
|
||||
virtual Variant get_drag_data(const Point2 &p_point) override;
|
||||
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
|
||||
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
|
||||
|
||||
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);
|
||||
|
||||
friend class EditorAudioBuses;
|
||||
|
||||
EditorAudioBuses *buses = nullptr;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void update_bus();
|
||||
void update_send();
|
||||
|
||||
EditorAudioBus(EditorAudioBuses *p_buses = nullptr, bool p_is_master = false);
|
||||
};
|
||||
|
||||
class EditorAudioBusDrop : public Control {
|
||||
GDCLASS(EditorAudioBusDrop, Control);
|
||||
|
||||
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
|
||||
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
|
||||
|
||||
mutable bool hovering_drop = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
EditorAudioBusDrop();
|
||||
};
|
||||
|
||||
class EditorAudioBuses : public VBoxContainer {
|
||||
GDCLASS(EditorAudioBuses, VBoxContainer);
|
||||
|
||||
HBoxContainer *top_hb = nullptr;
|
||||
|
||||
ScrollContainer *bus_scroll = nullptr;
|
||||
HBoxContainer *bus_hb = nullptr;
|
||||
|
||||
EditorAudioBusDrop *drop_end = nullptr;
|
||||
|
||||
Label *file = nullptr;
|
||||
|
||||
Button *add = nullptr;
|
||||
Button *load = nullptr;
|
||||
Button *save_as = nullptr;
|
||||
Button *_default = nullptr;
|
||||
Button *_new = nullptr;
|
||||
|
||||
Timer *save_timer = nullptr;
|
||||
String edited_path;
|
||||
|
||||
void _rebuild_buses();
|
||||
void _update_bus(int p_index);
|
||||
void _update_sends();
|
||||
|
||||
void _add_bus();
|
||||
void _delete_bus(Object *p_which);
|
||||
void _duplicate_bus(int p_which);
|
||||
void _reset_bus_volume(Object *p_which);
|
||||
|
||||
void _request_drop_end();
|
||||
void _drop_at_index(int p_bus, int p_index);
|
||||
|
||||
void _server_save();
|
||||
|
||||
void _select_layout();
|
||||
void _load_layout();
|
||||
void _save_as_layout();
|
||||
void _load_default_layout();
|
||||
void _new_layout();
|
||||
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
bool new_layout = false;
|
||||
|
||||
void _file_dialog_callback(const String &p_string);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void open_layout(const String &p_path);
|
||||
|
||||
static EditorAudioBuses *register_editor();
|
||||
|
||||
EditorAudioBuses();
|
||||
};
|
||||
|
||||
class EditorAudioMeterNotches : public Control {
|
||||
GDCLASS(EditorAudioMeterNotches, Control);
|
||||
|
||||
private:
|
||||
struct AudioNotch {
|
||||
float relative_position = 0;
|
||||
float db_value = 0;
|
||||
bool render_db_value = false;
|
||||
|
||||
_FORCE_INLINE_ AudioNotch(float r_pos, float db_v, bool rndr_val) {
|
||||
relative_position = r_pos;
|
||||
db_value = db_v;
|
||||
render_db_value = rndr_val;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ AudioNotch(const AudioNotch &n) {
|
||||
relative_position = n.relative_position;
|
||||
db_value = n.db_value;
|
||||
render_db_value = n.render_db_value;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator=(const EditorAudioMeterNotches::AudioNotch &n) {
|
||||
relative_position = n.relative_position;
|
||||
db_value = n.db_value;
|
||||
render_db_value = n.render_db_value;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ AudioNotch() {}
|
||||
};
|
||||
|
||||
List<AudioNotch> notches;
|
||||
|
||||
struct ThemeCache {
|
||||
Color notch_color;
|
||||
|
||||
Ref<Font> font;
|
||||
int font_size = 0;
|
||||
} theme_cache;
|
||||
|
||||
public:
|
||||
const float line_length = 5.0f;
|
||||
const float label_space = 2.0f;
|
||||
const float btm_padding = 9.0f;
|
||||
const float top_padding = 5.0f;
|
||||
|
||||
void add_notch(float p_normalized_offset, float p_db_value, bool p_render_value = false);
|
||||
Size2 get_minimum_size() const override;
|
||||
|
||||
private:
|
||||
virtual void _update_theme_item_cache() override;
|
||||
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
void _draw_audio_notches();
|
||||
|
||||
public:
|
||||
EditorAudioMeterNotches() {}
|
||||
};
|
||||
|
||||
class AudioBusesEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(AudioBusesEditorPlugin, EditorPlugin);
|
||||
|
||||
EditorAudioBuses *audio_bus_editor = nullptr;
|
||||
|
||||
public:
|
||||
virtual String get_name() const override { return "SampleLibrary"; }
|
||||
bool has_main_screen() const override { return false; }
|
||||
virtual void edit(Object *p_node) override;
|
||||
virtual bool handles(Object *p_node) const override;
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
|
||||
AudioBusesEditorPlugin(EditorAudioBuses *p_node);
|
||||
~AudioBusesEditorPlugin();
|
||||
};
|
||||
|
||||
#endif // EDITOR_AUDIO_BUSES_H
|
||||
985
engine/editor/editor_autoload_settings.cpp
Normal file
985
engine/editor/editor_autoload_settings.cpp
Normal file
|
|
@ -0,0 +1,985 @@
|
|||
/**************************************************************************/
|
||||
/* editor_autoload_settings.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_autoload_settings.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_constants.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/filesystem_dock.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/project_settings_editor.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
#define PREVIEW_LIST_MAX_SIZE 10
|
||||
|
||||
void EditorAutoloadSettings::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
List<String> afn;
|
||||
ResourceLoader::get_recognized_extensions_for_type("Script", &afn);
|
||||
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &afn);
|
||||
|
||||
for (const String &E : afn) {
|
||||
file_dialog->add_filter("*." + E);
|
||||
}
|
||||
|
||||
browse_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
browse_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
|
||||
add_autoload->set_icon(get_editor_theme_icon(SNAME("Add")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
FileSystemDock *dock = FileSystemDock::get_singleton();
|
||||
|
||||
if (dock != nullptr) {
|
||||
ScriptCreateDialog *dialog = dock->get_script_create_dialog();
|
||||
|
||||
if (dialog != nullptr) {
|
||||
Callable script_created = callable_mp(this, &EditorAutoloadSettings::_script_created);
|
||||
|
||||
if (is_visible_in_tree()) {
|
||||
if (!dialog->is_connected(SNAME("script_created"), script_created)) {
|
||||
dialog->connect("script_created", script_created);
|
||||
}
|
||||
} else {
|
||||
if (dialog->is_connected(SNAME("script_created"), script_created)) {
|
||||
dialog->disconnect("script_created", script_created);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, String *r_error) {
|
||||
if (!p_name.is_valid_identifier()) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " ";
|
||||
if (p_name.size() > 0 && p_name.left(1).is_numeric()) {
|
||||
*r_error += TTR("Cannot begin with a digit.");
|
||||
} else {
|
||||
*r_error += TTR("Valid characters:") + " a-z, A-Z, 0-9 or _";
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ClassDB::class_exists(p_name)) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing engine class name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ScriptServer::is_global_class(p_name)) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing global script class name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (Variant::get_type_name(Variant::Type(i)) == p_name) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing built-in type name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) {
|
||||
if (CoreConstants::get_global_constant_name(i) == p_name) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " " + TTR("Must not collide with an existing global constant name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
List<String> keywords;
|
||||
ScriptServer::get_language(i)->get_reserved_words(&keywords);
|
||||
for (const String &E : keywords) {
|
||||
if (E == p_name) {
|
||||
if (r_error) {
|
||||
*r_error = TTR("Invalid name.") + " " + TTR("Keyword cannot be used as an Autoload name.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_add() {
|
||||
if (autoload_add_path->get_text().is_empty()) {
|
||||
ScriptCreateDialog *dialog = FileSystemDock::get_singleton()->get_script_create_dialog();
|
||||
String fpath = path;
|
||||
if (!fpath.ends_with("/")) {
|
||||
fpath = fpath.get_base_dir();
|
||||
}
|
||||
dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text())), false, false);
|
||||
dialog->popup_centered();
|
||||
} else {
|
||||
if (autoload_add(autoload_add_name->get_text(), autoload_add_path->get_text())) {
|
||||
autoload_add_path->set_text("");
|
||||
}
|
||||
|
||||
autoload_add_name->set_text("");
|
||||
add_autoload->set_disabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_selected() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
|
||||
selected_autoload = "autoload/" + ti->get_text(0);
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_edited() {
|
||||
if (updating_autoload) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *ti = tree->get_edited();
|
||||
int column = tree->get_edited_column();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
if (column == 0) {
|
||||
String name = ti->get_text(0);
|
||||
String old_name = selected_autoload.get_slice("/", 1);
|
||||
|
||||
if (name == old_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
String error;
|
||||
if (!_autoload_name_is_valid(name, &error)) {
|
||||
ti->set_text(0, old_name);
|
||||
EditorNode::get_singleton()->show_warning(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting("autoload/" + name)) {
|
||||
ti->set_text(0, old_name);
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Autoload '%s' already exists!"), name));
|
||||
return;
|
||||
}
|
||||
|
||||
updating_autoload = true;
|
||||
|
||||
name = "autoload/" + name;
|
||||
|
||||
int order = ProjectSettings::get_singleton()->get_order(selected_autoload);
|
||||
String scr_path = GLOBAL_GET(selected_autoload);
|
||||
|
||||
undo_redo->create_action(TTR("Rename Autoload"));
|
||||
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, scr_path);
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", name, order);
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", selected_autoload);
|
||||
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), selected_autoload, scr_path);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", selected_autoload, order);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name);
|
||||
|
||||
undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_autoload");
|
||||
undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
|
||||
selected_autoload = name;
|
||||
} else if (column == 2) {
|
||||
updating_autoload = true;
|
||||
|
||||
bool checked = ti->is_checked(2);
|
||||
String base = "autoload/" + ti->get_text(0);
|
||||
|
||||
int order = ProjectSettings::get_singleton()->get_order(base);
|
||||
String scr_path = GLOBAL_GET(base);
|
||||
|
||||
if (scr_path.begins_with("*")) {
|
||||
scr_path = scr_path.substr(1, scr_path.length());
|
||||
}
|
||||
|
||||
// Singleton autoloads are represented with a leading "*" in their path.
|
||||
if (checked) {
|
||||
scr_path = "*" + scr_path;
|
||||
}
|
||||
|
||||
undo_redo->create_action(TTR("Toggle Autoload Globals"));
|
||||
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), base, scr_path);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), base, GLOBAL_GET(base));
|
||||
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", base, order);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", base, order);
|
||||
|
||||
undo_redo->add_do_method(this, CoreStringName(call_deferred), "update_autoload");
|
||||
undo_redo->add_undo_method(this, CoreStringName(call_deferred), "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
updating_autoload = false;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_button_pressed(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) {
|
||||
if (p_mouse_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
|
||||
|
||||
String name = "autoload/" + ti->get_text(0);
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
switch (p_button) {
|
||||
case BUTTON_OPEN: {
|
||||
_autoload_open(ti->get_text(1));
|
||||
} break;
|
||||
case BUTTON_MOVE_UP:
|
||||
case BUTTON_MOVE_DOWN: {
|
||||
TreeItem *swap = nullptr;
|
||||
|
||||
if (p_button == BUTTON_MOVE_UP) {
|
||||
swap = ti->get_prev();
|
||||
} else {
|
||||
swap = ti->get_next();
|
||||
}
|
||||
|
||||
if (!swap) {
|
||||
return;
|
||||
}
|
||||
|
||||
String swap_name = "autoload/" + swap->get_text(0);
|
||||
|
||||
int order = ProjectSettings::get_singleton()->get_order(name);
|
||||
int swap_order = ProjectSettings::get_singleton()->get_order(swap_name);
|
||||
|
||||
undo_redo->create_action(TTR("Move Autoload"));
|
||||
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", name, swap_order);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", name, order);
|
||||
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", swap_name, order);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", swap_name, swap_order);
|
||||
|
||||
undo_redo->add_do_method(this, "update_autoload");
|
||||
undo_redo->add_undo_method(this, "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
} break;
|
||||
case BUTTON_DELETE: {
|
||||
int order = ProjectSettings::get_singleton()->get_order(name);
|
||||
|
||||
undo_redo->create_action(TTR("Remove Autoload"));
|
||||
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, Variant());
|
||||
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, GLOBAL_GET(name));
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", name, order);
|
||||
|
||||
undo_redo->add_do_method(this, "update_autoload");
|
||||
undo_redo->add_undo_method(this, "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_activated() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
_autoload_open(ti->get_text(1));
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_open(const String &fpath) {
|
||||
if (ResourceLoader::get_resource_type(fpath) == "PackedScene") {
|
||||
EditorNode::get_singleton()->open_request(fpath);
|
||||
} else {
|
||||
EditorNode::get_singleton()->load_resource(fpath);
|
||||
}
|
||||
ProjectSettingsEditor::get_singleton()->hide();
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_file_callback(const String &p_path) {
|
||||
// Convert the file name to PascalCase, which is the convention for classes in GDScript.
|
||||
const String class_name = p_path.get_file().get_basename().to_pascal_case();
|
||||
|
||||
// If the name collides with a built-in class, prefix the name to make it possible to add without having to edit the name.
|
||||
// The prefix is subjective, but it provides better UX than leaving the Add button disabled :)
|
||||
const String prefix = ClassDB::class_exists(class_name) ? "Global" : "";
|
||||
|
||||
autoload_add_name->set_text(prefix + class_name);
|
||||
add_autoload->set_disabled(false);
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_text_submitted(const String &p_name) {
|
||||
if (!autoload_add_path->get_text().is_empty() && _autoload_name_is_valid(p_name, nullptr)) {
|
||||
_autoload_add();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_path_text_changed(const String &p_path) {
|
||||
add_autoload->set_disabled(!_autoload_name_is_valid(autoload_add_name->get_text(), nullptr));
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_autoload_text_changed(const String &p_name) {
|
||||
String error_string;
|
||||
bool is_name_valid = _autoload_name_is_valid(p_name, &error_string);
|
||||
add_autoload->set_disabled(!is_name_valid);
|
||||
error_message->set_text(error_string);
|
||||
error_message->set_visible(!autoload_add_name->get_text().is_empty() && !is_name_valid);
|
||||
}
|
||||
|
||||
Node *EditorAutoloadSettings::_create_autoload(const String &p_path) {
|
||||
Node *n = nullptr;
|
||||
if (ResourceLoader::get_resource_type(p_path) == "PackedScene") {
|
||||
// Cache the scene reference before loading it (for cyclic references)
|
||||
Ref<PackedScene> scn;
|
||||
scn.instantiate();
|
||||
scn->set_path(p_path);
|
||||
scn->reload_from_file();
|
||||
ERR_FAIL_COND_V_MSG(!scn.is_valid(), nullptr, vformat("Failed to create an autoload, can't load from path: %s.", p_path));
|
||||
|
||||
if (scn.is_valid()) {
|
||||
n = scn->instantiate();
|
||||
}
|
||||
} else {
|
||||
Ref<Resource> res = ResourceLoader::load(p_path);
|
||||
ERR_FAIL_COND_V_MSG(res.is_null(), nullptr, vformat("Failed to create an autoload, can't load from path: %s.", p_path));
|
||||
|
||||
Ref<Script> scr = res;
|
||||
if (scr.is_valid()) {
|
||||
ERR_FAIL_COND_V_MSG(!scr->is_valid(), nullptr, vformat("Failed to create an autoload, script '%s' is not compiling.", p_path));
|
||||
|
||||
StringName ibt = scr->get_instance_base_type();
|
||||
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
|
||||
ERR_FAIL_COND_V_MSG(!valid_type, nullptr, vformat("Failed to create an autoload, script '%s' does not inherit from 'Node'.", p_path));
|
||||
|
||||
Object *obj = ClassDB::instantiate(ibt);
|
||||
ERR_FAIL_NULL_V_MSG(obj, nullptr, vformat("Failed to create an autoload, cannot instantiate '%s'.", ibt));
|
||||
|
||||
n = Object::cast_to<Node>(obj);
|
||||
n->set_script(scr);
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_NULL_V_MSG(n, nullptr, vformat("Failed to create an autoload, path is not pointing to a scene or a script: %s.", p_path));
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::init_autoloads() {
|
||||
for (AutoloadInfo &info : autoload_cache) {
|
||||
info.node = _create_autoload(info.path);
|
||||
|
||||
if (info.node) {
|
||||
Ref<Script> scr = info.node->get_script();
|
||||
info.in_editor = scr.is_valid() && scr->is_tool();
|
||||
info.node->set_name(info.name);
|
||||
}
|
||||
|
||||
if (info.is_singleton) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->add_named_global_constant(info.name, info.node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.is_singleton && !info.in_editor && info.node != nullptr) {
|
||||
memdelete(info.node);
|
||||
info.node = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (const AutoloadInfo &info : autoload_cache) {
|
||||
if (info.node && info.in_editor) {
|
||||
callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::update_autoload() {
|
||||
if (updating_autoload) {
|
||||
return;
|
||||
}
|
||||
|
||||
updating_autoload = true;
|
||||
|
||||
HashMap<String, AutoloadInfo> to_remove;
|
||||
List<AutoloadInfo *> to_add;
|
||||
|
||||
for (const AutoloadInfo &info : autoload_cache) {
|
||||
to_remove.insert(info.name, info);
|
||||
}
|
||||
|
||||
autoload_cache.clear();
|
||||
|
||||
tree->clear();
|
||||
TreeItem *root = tree->create_item();
|
||||
|
||||
List<PropertyInfo> props;
|
||||
ProjectSettings::get_singleton()->get_property_list(&props);
|
||||
|
||||
for (const PropertyInfo &pi : props) {
|
||||
if (!pi.name.begins_with("autoload/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = pi.name.get_slice("/", 1);
|
||||
String scr_path = GLOBAL_GET(pi.name);
|
||||
|
||||
if (name.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoloadInfo info;
|
||||
info.is_singleton = scr_path.begins_with("*");
|
||||
|
||||
if (info.is_singleton) {
|
||||
scr_path = scr_path.substr(1, scr_path.length());
|
||||
}
|
||||
|
||||
info.name = name;
|
||||
info.path = scr_path;
|
||||
info.order = ProjectSettings::get_singleton()->get_order(pi.name);
|
||||
|
||||
bool need_to_add = true;
|
||||
if (to_remove.has(name)) {
|
||||
AutoloadInfo &old_info = to_remove[name];
|
||||
if (old_info.path == info.path) {
|
||||
// Still the same resource, check status
|
||||
info.node = old_info.node;
|
||||
if (info.node) {
|
||||
Ref<Script> scr = info.node->get_script();
|
||||
info.in_editor = scr.is_valid() && scr->is_tool();
|
||||
if (info.is_singleton == old_info.is_singleton && info.in_editor == old_info.in_editor) {
|
||||
to_remove.erase(name);
|
||||
need_to_add = false;
|
||||
} else {
|
||||
info.node = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
autoload_cache.push_back(info);
|
||||
|
||||
if (need_to_add) {
|
||||
to_add.push_back(&(autoload_cache.back()->get()));
|
||||
}
|
||||
|
||||
TreeItem *item = tree->create_item(root);
|
||||
item->set_text(0, name);
|
||||
item->set_editable(0, true);
|
||||
|
||||
item->set_text(1, scr_path);
|
||||
item->set_selectable(1, true);
|
||||
|
||||
item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK);
|
||||
item->set_editable(2, true);
|
||||
item->set_text(2, TTR("Enable"));
|
||||
item->set_checked(2, info.is_singleton);
|
||||
item->add_button(3, get_editor_theme_icon(SNAME("Load")), BUTTON_OPEN);
|
||||
item->add_button(3, get_editor_theme_icon(SNAME("MoveUp")), BUTTON_MOVE_UP);
|
||||
item->add_button(3, get_editor_theme_icon(SNAME("MoveDown")), BUTTON_MOVE_DOWN);
|
||||
item->add_button(3, get_editor_theme_icon(SNAME("Remove")), BUTTON_DELETE);
|
||||
item->set_selectable(3, false);
|
||||
}
|
||||
|
||||
// Remove deleted/changed autoloads
|
||||
for (KeyValue<String, AutoloadInfo> &E : to_remove) {
|
||||
AutoloadInfo &info = E.value;
|
||||
if (info.is_singleton) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->remove_named_global_constant(info.name);
|
||||
}
|
||||
}
|
||||
if (info.in_editor) {
|
||||
ERR_CONTINUE(!info.node);
|
||||
callable_mp((Node *)get_tree()->get_root(), &Node::remove_child).call_deferred(info.node);
|
||||
}
|
||||
|
||||
if (info.node) {
|
||||
info.node->queue_free();
|
||||
info.node = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Load new/changed autoloads
|
||||
List<Node *> nodes_to_add;
|
||||
for (AutoloadInfo *info : to_add) {
|
||||
info->node = _create_autoload(info->path);
|
||||
|
||||
ERR_CONTINUE(!info->node);
|
||||
info->node->set_name(info->name);
|
||||
|
||||
Ref<Script> scr = info->node->get_script();
|
||||
info->in_editor = scr.is_valid() && scr->is_tool();
|
||||
|
||||
if (info->in_editor) {
|
||||
//defer so references are all valid on _ready()
|
||||
nodes_to_add.push_back(info->node);
|
||||
}
|
||||
|
||||
if (info->is_singleton) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->add_named_global_constant(info->name, info->node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!info->in_editor && !info->is_singleton) {
|
||||
// No reason to keep this node
|
||||
memdelete(info->node);
|
||||
info->node = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (Node *E : nodes_to_add) {
|
||||
get_tree()->get_root()->add_child(E);
|
||||
}
|
||||
|
||||
updating_autoload = false;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_script_created(Ref<Script> p_script) {
|
||||
FileSystemDock::get_singleton()->get_script_create_dialog()->hide();
|
||||
path = p_script->get_path().get_base_dir();
|
||||
autoload_add_path->set_text(p_script->get_path());
|
||||
autoload_add_name->set_text(p_script->get_path().get_file().get_basename().to_pascal_case());
|
||||
_autoload_add();
|
||||
}
|
||||
|
||||
LineEdit *EditorAutoloadSettings::get_path_box() const {
|
||||
return autoload_add_path;
|
||||
}
|
||||
|
||||
Variant EditorAutoloadSettings::get_drag_data_fw(const Point2 &p_point, Control *p_control) {
|
||||
if (autoload_cache.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PackedStringArray autoloads;
|
||||
|
||||
TreeItem *next = tree->get_next_selected(nullptr);
|
||||
|
||||
while (next) {
|
||||
autoloads.push_back(next->get_text(0));
|
||||
next = tree->get_next_selected(next);
|
||||
}
|
||||
|
||||
if (autoloads.size() == 0 || autoloads.size() == autoload_cache.size()) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
VBoxContainer *preview = memnew(VBoxContainer);
|
||||
|
||||
int max_size = MIN(PREVIEW_LIST_MAX_SIZE, autoloads.size());
|
||||
|
||||
for (int i = 0; i < max_size; i++) {
|
||||
Label *label = memnew(Label(autoloads[i]));
|
||||
label->set_self_modulate(Color(1, 1, 1, Math::lerp(1, 0, float(i) / PREVIEW_LIST_MAX_SIZE)));
|
||||
|
||||
preview->add_child(label);
|
||||
}
|
||||
|
||||
tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
|
||||
tree->set_drag_preview(preview);
|
||||
|
||||
Dictionary drop_data;
|
||||
drop_data["type"] = "autoload";
|
||||
drop_data["autoloads"] = autoloads;
|
||||
|
||||
return drop_data;
|
||||
}
|
||||
|
||||
bool EditorAutoloadSettings::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_control) const {
|
||||
if (updating_autoload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary drop_data = p_data;
|
||||
|
||||
if (!drop_data.has("type")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (drop_data.has("type")) {
|
||||
TreeItem *ti = tree->get_item_at_position(p_point);
|
||||
|
||||
if (!ti) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int section = tree->get_drop_section_at_position(p_point);
|
||||
|
||||
return section >= -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_control) {
|
||||
TreeItem *ti = tree->get_item_at_position(p_point);
|
||||
|
||||
if (!ti) {
|
||||
return;
|
||||
}
|
||||
|
||||
int section = tree->get_drop_section_at_position(p_point);
|
||||
|
||||
if (section < -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name;
|
||||
bool move_to_back = false;
|
||||
|
||||
if (section < 0) {
|
||||
name = ti->get_text(0);
|
||||
} else if (ti->get_next()) {
|
||||
name = ti->get_next()->get_text(0);
|
||||
} else {
|
||||
name = ti->get_text(0);
|
||||
move_to_back = true;
|
||||
}
|
||||
|
||||
int order = ProjectSettings::get_singleton()->get_order("autoload/" + name);
|
||||
|
||||
AutoloadInfo aux;
|
||||
List<AutoloadInfo>::Element *E = nullptr;
|
||||
|
||||
if (!move_to_back) {
|
||||
aux.order = order;
|
||||
E = autoload_cache.find(aux);
|
||||
}
|
||||
|
||||
Dictionary drop_data = p_data;
|
||||
PackedStringArray autoloads = drop_data["autoloads"];
|
||||
|
||||
Vector<int> orders;
|
||||
orders.resize(autoload_cache.size());
|
||||
|
||||
for (int i = 0; i < autoloads.size(); i++) {
|
||||
aux.order = ProjectSettings::get_singleton()->get_order("autoload/" + autoloads[i]);
|
||||
|
||||
List<AutoloadInfo>::Element *I = autoload_cache.find(aux);
|
||||
|
||||
if (move_to_back) {
|
||||
autoload_cache.move_to_back(I);
|
||||
} else if (E != I) {
|
||||
autoload_cache.move_before(I, E);
|
||||
} else if (E->next()) {
|
||||
E = E->next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
for (const AutoloadInfo &F : autoload_cache) {
|
||||
orders.write[i++] = F.order;
|
||||
}
|
||||
|
||||
orders.sort();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
undo_redo->create_action(TTR("Rearrange Autoloads"));
|
||||
|
||||
i = 0;
|
||||
|
||||
for (const AutoloadInfo &F : autoload_cache) {
|
||||
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, orders[i++]);
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, F.order);
|
||||
}
|
||||
|
||||
orders.clear();
|
||||
|
||||
undo_redo->add_do_method(this, "update_autoload");
|
||||
undo_redo->add_undo_method(this, "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
bool EditorAutoloadSettings::autoload_add(const String &p_name, const String &p_path) {
|
||||
String name = p_name;
|
||||
|
||||
String error;
|
||||
if (!_autoload_name_is_valid(name, &error)) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Can't add Autoload:") + "\n" + error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FileAccess::exists(p_path)) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Can't add Autoload:") + "\n" + vformat(TTR("%s is an invalid path. File does not exist."), p_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_path.begins_with("res://")) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Can't add Autoload:") + "\n" + vformat(TTR("%s is an invalid path. Not in resource path (res://)."), p_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
name = "autoload/" + name;
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
undo_redo->create_action(TTR("Add Autoload"));
|
||||
// Singleton autoloads are represented with a leading "*" in their path.
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, "*" + p_path);
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting(name)) {
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, GLOBAL_GET(name));
|
||||
} else {
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, Variant());
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "update_autoload");
|
||||
undo_redo->add_undo_method(this, "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::autoload_remove(const String &p_name) {
|
||||
String name = "autoload/" + p_name;
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
|
||||
int order = ProjectSettings::get_singleton()->get_order(name);
|
||||
|
||||
undo_redo->create_action(TTR("Remove Autoload"));
|
||||
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, Variant());
|
||||
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, GLOBAL_GET(name));
|
||||
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", name, order);
|
||||
|
||||
undo_redo->add_do_method(this, "update_autoload");
|
||||
undo_redo->add_undo_method(this, "update_autoload");
|
||||
|
||||
undo_redo->add_do_method(this, "emit_signal", autoload_changed);
|
||||
undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
|
||||
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_bind_methods() {
|
||||
ClassDB::bind_method("update_autoload", &EditorAutoloadSettings::update_autoload);
|
||||
ClassDB::bind_method("autoload_add", &EditorAutoloadSettings::autoload_add);
|
||||
ClassDB::bind_method("autoload_remove", &EditorAutoloadSettings::autoload_remove);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("autoload_changed"));
|
||||
}
|
||||
|
||||
EditorAutoloadSettings::EditorAutoloadSettings() {
|
||||
ProjectSettings::get_singleton()->add_hidden_prefix("autoload/");
|
||||
|
||||
// Make first cache
|
||||
List<PropertyInfo> props;
|
||||
ProjectSettings::get_singleton()->get_property_list(&props);
|
||||
for (const PropertyInfo &pi : props) {
|
||||
if (!pi.name.begins_with("autoload/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = pi.name.get_slice("/", 1);
|
||||
String scr_path = GLOBAL_GET(pi.name);
|
||||
|
||||
if (name.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AutoloadInfo info;
|
||||
info.is_singleton = scr_path.begins_with("*");
|
||||
|
||||
if (info.is_singleton) {
|
||||
scr_path = scr_path.substr(1, scr_path.length());
|
||||
}
|
||||
|
||||
info.name = name;
|
||||
info.path = scr_path;
|
||||
info.order = ProjectSettings::get_singleton()->get_order(pi.name);
|
||||
|
||||
if (info.is_singleton) {
|
||||
// Make sure name references work before parsing scripts
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->add_named_global_constant(info.name, Variant());
|
||||
}
|
||||
}
|
||||
|
||||
autoload_cache.push_back(info);
|
||||
}
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
add_child(hbc);
|
||||
|
||||
error_message = memnew(Label);
|
||||
error_message->hide();
|
||||
error_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
error_message->add_theme_color_override(SceneStringName(font_color), EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
add_child(error_message);
|
||||
|
||||
Label *l = memnew(Label);
|
||||
l->set_text(TTR("Path:"));
|
||||
hbc->add_child(l);
|
||||
|
||||
autoload_add_path = memnew(LineEdit);
|
||||
hbc->add_child(autoload_add_path);
|
||||
autoload_add_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
autoload_add_path->set_clear_button_enabled(true);
|
||||
autoload_add_path->set_placeholder(vformat(TTR("Set path or press \"%s\" to create a script."), TTR("Add")));
|
||||
autoload_add_path->connect(SceneStringName(text_changed), callable_mp(this, &EditorAutoloadSettings::_autoload_path_text_changed));
|
||||
|
||||
browse_button = memnew(Button);
|
||||
hbc->add_child(browse_button);
|
||||
browse_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAutoloadSettings::_browse_autoload_add_path));
|
||||
|
||||
file_dialog = memnew(EditorFileDialog);
|
||||
hbc->add_child(file_dialog);
|
||||
file_dialog->connect("file_selected", callable_mp(this, &EditorAutoloadSettings::_set_autoload_add_path));
|
||||
file_dialog->connect("dir_selected", callable_mp(this, &EditorAutoloadSettings::_set_autoload_add_path));
|
||||
file_dialog->connect("files_selected", callable_mp(this, &EditorAutoloadSettings::_set_autoload_add_path));
|
||||
|
||||
hbc->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
file_dialog->connect("file_selected", callable_mp(this, &EditorAutoloadSettings::_autoload_file_callback));
|
||||
|
||||
l = memnew(Label);
|
||||
l->set_text(TTR("Node Name:"));
|
||||
hbc->add_child(l);
|
||||
|
||||
autoload_add_name = memnew(LineEdit);
|
||||
autoload_add_name->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
autoload_add_name->connect("text_submitted", callable_mp(this, &EditorAutoloadSettings::_autoload_text_submitted));
|
||||
autoload_add_name->connect(SceneStringName(text_changed), callable_mp(this, &EditorAutoloadSettings::_autoload_text_changed));
|
||||
hbc->add_child(autoload_add_name);
|
||||
|
||||
add_autoload = memnew(Button);
|
||||
add_autoload->set_text(TTR("Add"));
|
||||
add_autoload->connect(SceneStringName(pressed), callable_mp(this, &EditorAutoloadSettings::_autoload_add));
|
||||
// The button will be enabled once a valid name is entered (either automatically or manually).
|
||||
add_autoload->set_disabled(true);
|
||||
hbc->add_child(add_autoload);
|
||||
|
||||
tree = memnew(Tree);
|
||||
tree->set_hide_root(true);
|
||||
tree->set_select_mode(Tree::SELECT_MULTI);
|
||||
tree->set_allow_reselect(true);
|
||||
|
||||
SET_DRAG_FORWARDING_GCD(tree, EditorAutoloadSettings);
|
||||
|
||||
tree->set_columns(4);
|
||||
tree->set_column_titles_visible(true);
|
||||
|
||||
tree->set_column_title(0, TTR("Name"));
|
||||
tree->set_column_expand(0, true);
|
||||
tree->set_column_expand_ratio(0, 1);
|
||||
|
||||
tree->set_column_title(1, TTR("Path"));
|
||||
tree->set_column_expand(1, true);
|
||||
tree->set_column_clip_content(1, true);
|
||||
tree->set_column_expand_ratio(1, 2);
|
||||
|
||||
tree->set_column_title(2, TTR("Global Variable"));
|
||||
tree->set_column_expand(2, false);
|
||||
|
||||
tree->set_column_expand(3, false);
|
||||
|
||||
tree->connect("cell_selected", callable_mp(this, &EditorAutoloadSettings::_autoload_selected));
|
||||
tree->connect("item_edited", callable_mp(this, &EditorAutoloadSettings::_autoload_edited));
|
||||
tree->connect("button_clicked", callable_mp(this, &EditorAutoloadSettings::_autoload_button_pressed));
|
||||
tree->connect("item_activated", callable_mp(this, &EditorAutoloadSettings::_autoload_activated));
|
||||
tree->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
add_child(tree, true);
|
||||
}
|
||||
|
||||
EditorAutoloadSettings::~EditorAutoloadSettings() {
|
||||
for (const AutoloadInfo &info : autoload_cache) {
|
||||
if (info.node && !info.in_editor) {
|
||||
memdelete(info.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_set_autoload_add_path(const String &p_text) {
|
||||
autoload_add_path->set_text(p_text);
|
||||
autoload_add_path->emit_signal(SNAME("text_submitted"), p_text);
|
||||
}
|
||||
|
||||
void EditorAutoloadSettings::_browse_autoload_add_path() {
|
||||
file_dialog->popup_file_dialog();
|
||||
}
|
||||
118
engine/editor/editor_autoload_settings.h
Normal file
118
engine/editor/editor_autoload_settings.h
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**************************************************************************/
|
||||
/* editor_autoload_settings.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_AUTOLOAD_SETTINGS_H
|
||||
#define EDITOR_AUTOLOAD_SETTINGS_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorFileDialog;
|
||||
|
||||
class EditorAutoloadSettings : public VBoxContainer {
|
||||
GDCLASS(EditorAutoloadSettings, VBoxContainer);
|
||||
|
||||
enum {
|
||||
BUTTON_OPEN,
|
||||
BUTTON_MOVE_UP,
|
||||
BUTTON_MOVE_DOWN,
|
||||
BUTTON_DELETE
|
||||
};
|
||||
|
||||
String path = "res://";
|
||||
String autoload_changed = "autoload_changed";
|
||||
|
||||
struct AutoloadInfo {
|
||||
String name;
|
||||
String path;
|
||||
bool is_singleton = false;
|
||||
bool in_editor = false;
|
||||
int order = 0;
|
||||
Node *node = nullptr;
|
||||
|
||||
bool operator==(const AutoloadInfo &p_info) const {
|
||||
return order == p_info.order;
|
||||
}
|
||||
};
|
||||
|
||||
List<AutoloadInfo> autoload_cache;
|
||||
|
||||
bool updating_autoload = false;
|
||||
String selected_autoload;
|
||||
|
||||
Tree *tree = nullptr;
|
||||
LineEdit *autoload_add_name = nullptr;
|
||||
Button *add_autoload = nullptr;
|
||||
LineEdit *autoload_add_path = nullptr;
|
||||
Label *error_message = nullptr;
|
||||
Button *browse_button = nullptr;
|
||||
EditorFileDialog *file_dialog = nullptr;
|
||||
|
||||
bool _autoload_name_is_valid(const String &p_name, String *r_error = nullptr);
|
||||
|
||||
void _autoload_add();
|
||||
void _autoload_selected();
|
||||
void _autoload_edited();
|
||||
void _autoload_button_pressed(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button);
|
||||
void _autoload_activated();
|
||||
void _autoload_path_text_changed(const String &p_path);
|
||||
void _autoload_text_submitted(const String &p_name);
|
||||
void _autoload_text_changed(const String &p_name);
|
||||
void _autoload_open(const String &fpath);
|
||||
void _autoload_file_callback(const String &p_path);
|
||||
Node *_create_autoload(const String &p_path);
|
||||
|
||||
void _script_created(Ref<Script> p_script);
|
||||
|
||||
Variant get_drag_data_fw(const Point2 &p_point, Control *p_control);
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_control) const;
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_control);
|
||||
|
||||
void _set_autoload_add_path(const String &p_text);
|
||||
void _browse_autoload_add_path();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void init_autoloads();
|
||||
void update_autoload();
|
||||
bool autoload_add(const String &p_name, const String &p_path);
|
||||
void autoload_remove(const String &p_name);
|
||||
|
||||
LineEdit *get_path_box() const;
|
||||
|
||||
EditorAutoloadSettings();
|
||||
~EditorAutoloadSettings();
|
||||
};
|
||||
|
||||
#endif // EDITOR_AUTOLOAD_SETTINGS_H
|
||||
892
engine/editor/editor_build_profile.cpp
Normal file
892
engine/editor/editor_build_profile.cpp
Normal file
|
|
@ -0,0 +1,892 @@
|
|||
/**************************************************************************/
|
||||
/* editor_build_profile.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_build_profile.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/json.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_paths.h"
|
||||
#include "editor/editor_property_name_processor.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
|
||||
const char *EditorBuildProfile::build_option_identifiers[BUILD_OPTION_MAX] = {
|
||||
// This maps to SCons build options.
|
||||
"disable_3d",
|
||||
"disable_2d_physics",
|
||||
"disable_3d_physics",
|
||||
"disable_navigation",
|
||||
"openxr",
|
||||
"rendering_device", // FIXME: there's no scons option to disable rendering device
|
||||
"opengl3",
|
||||
"vulkan",
|
||||
"module_text_server_fb_enabled",
|
||||
"module_text_server_adv_enabled",
|
||||
"module_freetype_enabled",
|
||||
"brotli",
|
||||
"graphite",
|
||||
"module_msdfgen_enabled"
|
||||
};
|
||||
|
||||
const bool EditorBuildProfile::build_option_disabled_by_default[BUILD_OPTION_MAX] = {
|
||||
// This maps to SCons build options.
|
||||
false, // 3D
|
||||
false, // PHYSICS_2D
|
||||
false, // PHYSICS_3D
|
||||
false, // NAVIGATION
|
||||
false, // XR
|
||||
false, // RENDERING_DEVICE
|
||||
false, // OPENGL
|
||||
false, // VULKAN
|
||||
true, // TEXT_SERVER_FALLBACK
|
||||
false, // TEXT_SERVER_COMPLEX
|
||||
false, // DYNAMIC_FONTS
|
||||
false, // WOFF2_FONTS
|
||||
false, // GRPAHITE_FONTS
|
||||
false, // MSDFGEN
|
||||
};
|
||||
|
||||
const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = {
|
||||
// This maps to SCons build options.
|
||||
true, // 3D
|
||||
true, // PHYSICS_2D
|
||||
true, // PHYSICS_3D
|
||||
true, // NAVIGATION
|
||||
false, // XR
|
||||
false, // RENDERING_DEVICE
|
||||
false, // OPENGL
|
||||
false, // VULKAN
|
||||
false, // TEXT_SERVER_FALLBACK
|
||||
false, // TEXT_SERVER_COMPLEX
|
||||
false, // DYNAMIC_FONTS
|
||||
false, // WOFF2_FONTS
|
||||
false, // GRPAHITE_FONTS
|
||||
false, // MSDFGEN
|
||||
};
|
||||
|
||||
const EditorBuildProfile::BuildOptionCategory EditorBuildProfile::build_option_category[BUILD_OPTION_MAX] = {
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // 3D
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // PHYSICS_2D
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // PHYSICS_3D
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // NAVIGATION
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // XR
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // RENDERING_DEVICE
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // OPENGL
|
||||
BUILD_OPTION_CATEGORY_GENERAL, // VULKAN
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_FALLBACK
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_COMPLEX
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // DYNAMIC_FONTS
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // WOFF2_FONTS
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRPAHITE_FONTS
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER, // MSDFGEN
|
||||
};
|
||||
|
||||
void EditorBuildProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
|
||||
if (p_disabled) {
|
||||
disabled_classes.insert(p_class);
|
||||
} else {
|
||||
disabled_classes.erase(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_class_disabled(const StringName &p_class) const {
|
||||
if (p_class == StringName()) {
|
||||
return false;
|
||||
}
|
||||
return disabled_classes.has(p_class) || is_class_disabled(ClassDB::get_parent_class_nocheck(p_class));
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {
|
||||
if (p_collapsed) {
|
||||
collapsed_classes.insert(p_class);
|
||||
} else {
|
||||
collapsed_classes.erase(p_class);
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_item_collapsed(const StringName &p_class) const {
|
||||
return collapsed_classes.has(p_class);
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_disable_build_option(BuildOption p_build_option, bool p_disable) {
|
||||
ERR_FAIL_INDEX(p_build_option, BUILD_OPTION_MAX);
|
||||
build_options_disabled[p_build_option] = p_disable;
|
||||
}
|
||||
|
||||
void EditorBuildProfile::clear_disabled_classes() {
|
||||
disabled_classes.clear();
|
||||
collapsed_classes.clear();
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::is_build_option_disabled(BuildOption p_build_option) const {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
|
||||
return build_options_disabled[p_build_option];
|
||||
}
|
||||
|
||||
bool EditorBuildProfile::get_build_option_disable_value(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, false);
|
||||
return build_option_disable_values[p_build_option];
|
||||
}
|
||||
|
||||
void EditorBuildProfile::set_force_detect_classes(const String &p_classes) {
|
||||
force_detect_classes = p_classes;
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_force_detect_classes() const {
|
||||
return force_detect_classes;
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_build_option_name(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
|
||||
const char *build_option_names[BUILD_OPTION_MAX] = {
|
||||
TTRC("3D Engine"),
|
||||
TTRC("2D Physics"),
|
||||
TTRC("3D Physics"),
|
||||
TTRC("Navigation"),
|
||||
TTRC("XR"),
|
||||
TTRC("RenderingDevice"),
|
||||
TTRC("OpenGL"),
|
||||
TTRC("Vulkan"),
|
||||
TTRC("Text Server: Fallback"),
|
||||
TTRC("Text Server: Advanced"),
|
||||
TTRC("TTF, OTF, Type 1, WOFF1 Fonts"),
|
||||
TTRC("WOFF2 Fonts"),
|
||||
TTRC("SIL Graphite Fonts"),
|
||||
TTRC("Multi-channel Signed Distance Field Font Rendering"),
|
||||
};
|
||||
return TTRGET(build_option_names[p_build_option]);
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_build_option_description(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, String());
|
||||
|
||||
const char *build_option_descriptions[BUILD_OPTION_MAX] = {
|
||||
TTRC("3D Nodes as well as RenderingServer access to 3D features."),
|
||||
TTRC("2D Physics nodes and PhysicsServer2D."),
|
||||
TTRC("3D Physics nodes and PhysicsServer3D."),
|
||||
TTRC("Navigation, both 2D and 3D."),
|
||||
TTRC("XR (AR and VR)."),
|
||||
TTRC("RenderingDevice based rendering (if disabled, the OpenGL back-end is required)."),
|
||||
TTRC("OpenGL back-end (if disabled, the RenderingDevice back-end is required)."),
|
||||
TTRC("Vulkan back-end of RenderingDevice."),
|
||||
TTRC("Fallback implementation of Text Server\nSupports basic text layouts."),
|
||||
TTRC("Text Server implementation powered by ICU and HarfBuzz libraries.\nSupports complex text layouts, BiDi, and contextual OpenType font features."),
|
||||
TTRC("TrueType, OpenType, Type 1, and WOFF1 font format support using FreeType library (if disabled, WOFF2 support is also disabled)."),
|
||||
TTRC("WOFF2 font format support using FreeType and Brotli libraries."),
|
||||
TTRC("SIL Graphite smart font technology support (supported by Advanced Text Server only)."),
|
||||
TTRC("Multi-channel signed distance field font rendering support using msdfgen library (pre-rendered MSDF fonts can be used even if this option disabled)."),
|
||||
};
|
||||
|
||||
return TTRGET(build_option_descriptions[p_build_option]);
|
||||
}
|
||||
|
||||
EditorBuildProfile::BuildOptionCategory EditorBuildProfile::get_build_option_category(BuildOption p_build_option) {
|
||||
ERR_FAIL_INDEX_V(p_build_option, BUILD_OPTION_MAX, BUILD_OPTION_CATEGORY_GENERAL);
|
||||
return build_option_category[p_build_option];
|
||||
}
|
||||
|
||||
String EditorBuildProfile::get_build_option_category_name(BuildOptionCategory p_build_option_category) {
|
||||
ERR_FAIL_INDEX_V(p_build_option_category, BUILD_OPTION_CATEGORY_MAX, String());
|
||||
|
||||
const char *build_option_subcategories[BUILD_OPTION_CATEGORY_MAX]{
|
||||
TTRC("General Features:"),
|
||||
TTRC("Text Rendering and Font Options:"),
|
||||
};
|
||||
|
||||
return TTRGET(build_option_subcategories[p_build_option_category]);
|
||||
}
|
||||
|
||||
Error EditorBuildProfile::save_to_file(const String &p_path) {
|
||||
Dictionary data;
|
||||
data["type"] = "build_profile";
|
||||
Array dis_classes;
|
||||
for (const StringName &E : disabled_classes) {
|
||||
dis_classes.push_back(String(E));
|
||||
}
|
||||
dis_classes.sort();
|
||||
data["disabled_classes"] = dis_classes;
|
||||
|
||||
Dictionary dis_build_options;
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
if (build_options_disabled[i] != build_option_disabled_by_default[i]) {
|
||||
if (build_options_disabled[i]) {
|
||||
dis_build_options[build_option_identifiers[i]] = build_option_disable_values[i];
|
||||
} else {
|
||||
dis_build_options[build_option_identifiers[i]] = !build_option_disable_values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data["disabled_build_options"] = dis_build_options;
|
||||
|
||||
if (!force_detect_classes.is_empty()) {
|
||||
data["force_detect_classes"] = force_detect_classes;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
|
||||
|
||||
String text = JSON::stringify(data, "\t");
|
||||
f->store_string(text);
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EditorBuildProfile::load_from_file(const String &p_path) {
|
||||
Error err;
|
||||
String text = FileAccess::get_file_as_string(p_path, &err);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
JSON json;
|
||||
err = json.parse(text);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Error parsing '" + p_path + "' on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
Dictionary data = json.get_data();
|
||||
|
||||
if (!data.has("type") || String(data["type"]) != "build_profile") {
|
||||
ERR_PRINT("Error parsing '" + p_path + "', it's not a build profile.");
|
||||
return ERR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
disabled_classes.clear();
|
||||
|
||||
if (data.has("disabled_classes")) {
|
||||
Array disabled_classes_arr = data["disabled_classes"];
|
||||
for (int i = 0; i < disabled_classes_arr.size(); i++) {
|
||||
disabled_classes.insert(disabled_classes_arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
build_options_disabled[i] = build_option_disabled_by_default[i];
|
||||
}
|
||||
|
||||
if (data.has("disabled_build_options")) {
|
||||
Dictionary disabled_build_options_arr = data["disabled_build_options"];
|
||||
List<Variant> keys;
|
||||
disabled_build_options_arr.get_key_list(&keys);
|
||||
|
||||
for (const Variant &K : keys) {
|
||||
String key = K;
|
||||
|
||||
for (int i = 0; i < BUILD_OPTION_MAX; i++) {
|
||||
String f = build_option_identifiers[i];
|
||||
if (f == key) {
|
||||
build_options_disabled[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.has("force_detect_classes")) {
|
||||
force_detect_classes = data["force_detect_classes"];
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorBuildProfile::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_disable_class", "class_name", "disable"), &EditorBuildProfile::set_disable_class);
|
||||
ClassDB::bind_method(D_METHOD("is_class_disabled", "class_name"), &EditorBuildProfile::is_class_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_disable_build_option", "build_option", "disable"), &EditorBuildProfile::set_disable_build_option);
|
||||
ClassDB::bind_method(D_METHOD("is_build_option_disabled", "build_option"), &EditorBuildProfile::is_build_option_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_build_option_name", "build_option"), &EditorBuildProfile::_get_build_option_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("save_to_file", "path"), &EditorBuildProfile::save_to_file);
|
||||
ClassDB::bind_method(D_METHOD("load_from_file", "path"), &EditorBuildProfile::load_from_file);
|
||||
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_3D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_2D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_PHYSICS_3D);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_NAVIGATION);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_XR);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_RENDERING_DEVICE);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_OPENGL);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_VULKAN);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_FALLBACK);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_ADVANCED);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_DYNAMIC_FONTS);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_WOFF2_FONTS);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_GRPAHITE_FONTS);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_MSDFGEN);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_MAX);
|
||||
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_GENERAL);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_TEXT_SERVER);
|
||||
BIND_ENUM_CONSTANT(BUILD_OPTION_CATEGORY_MAX);
|
||||
}
|
||||
|
||||
EditorBuildProfile::EditorBuildProfile() {
|
||||
for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) {
|
||||
build_options_disabled[i] = build_option_disabled_by_default[i];
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
|
||||
void EditorBuildProfileManager::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
String last_file = EditorSettings::get_singleton()->get_project_metadata("build_profile", "last_file_path", "");
|
||||
if (!last_file.is_empty()) {
|
||||
_import_profile(last_file);
|
||||
}
|
||||
if (edited.is_null()) {
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_profile_action(int p_action) {
|
||||
last_action = Action(p_action);
|
||||
|
||||
switch (p_action) {
|
||||
case ACTION_RESET: {
|
||||
confirm_dialog->set_text(TTR("Reset the edited profile?"));
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_LOAD: {
|
||||
import_profile->popup_file_dialog();
|
||||
} break;
|
||||
case ACTION_SAVE: {
|
||||
if (!profile_path->get_text().is_empty()) {
|
||||
Error err = edited->save_to_file(profile_path->get_text());
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("File saving failed."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case ACTION_SAVE_AS: {
|
||||
export_profile->popup_file_dialog();
|
||||
export_profile->set_current_file(profile_path->get_text());
|
||||
} break;
|
||||
case ACTION_NEW: {
|
||||
confirm_dialog->set_text(TTR("Create a new profile?"));
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_DETECT: {
|
||||
confirm_dialog->set_text(TTR("This will scan all files in the current project to detect used classes."));
|
||||
confirm_dialog->popup_centered();
|
||||
} break;
|
||||
case ACTION_MAX: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected) {
|
||||
if (p_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_file_count(); i++) {
|
||||
String p = p_dir->get_file_path(i);
|
||||
|
||||
uint64_t timestamp = 0;
|
||||
String md5;
|
||||
|
||||
if (p_cache.has(p)) {
|
||||
const DetectedFile &cache = p_cache[p];
|
||||
// Check if timestamp and MD5 match.
|
||||
timestamp = FileAccess::get_modified_time(p);
|
||||
bool cache_valid = true;
|
||||
if (cache.timestamp != timestamp) {
|
||||
md5 = FileAccess::get_md5(p);
|
||||
if (md5 != cache.md5) {
|
||||
cache_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_valid) {
|
||||
r_detected.insert(p, cache);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Not cached, or cache invalid.
|
||||
|
||||
DetectedFile cache;
|
||||
|
||||
HashSet<StringName> classes;
|
||||
ResourceLoader::get_classes_used(p, &classes);
|
||||
|
||||
for (const StringName &E : classes) {
|
||||
cache.classes.push_back(E);
|
||||
}
|
||||
|
||||
if (md5.is_empty()) {
|
||||
cache.timestamp = FileAccess::get_modified_time(p);
|
||||
cache.md5 = FileAccess::get_md5(p);
|
||||
} else {
|
||||
cache.timestamp = timestamp;
|
||||
cache.md5 = md5;
|
||||
}
|
||||
|
||||
r_detected.insert(p, cache);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
|
||||
_find_files(p_dir->get_subdir(i), p_cache, r_detected);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_detect_classes() {
|
||||
HashMap<String, DetectedFile> previous_file_cache;
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("used_class_cache"), FileAccess::READ);
|
||||
if (f.is_valid()) {
|
||||
while (!f->eof_reached()) {
|
||||
String l = f->get_line();
|
||||
Vector<String> fields = l.split("::");
|
||||
if (fields.size() == 4) {
|
||||
const String &path = fields[0];
|
||||
DetectedFile df;
|
||||
df.timestamp = fields[1].to_int();
|
||||
df.md5 = fields[2];
|
||||
df.classes = fields[3].split(",");
|
||||
previous_file_cache.insert(path, df);
|
||||
}
|
||||
}
|
||||
f.unref();
|
||||
}
|
||||
|
||||
HashMap<String, DetectedFile> updated_file_cache;
|
||||
|
||||
_find_files(EditorFileSystem::get_singleton()->get_filesystem(), previous_file_cache, updated_file_cache);
|
||||
|
||||
HashSet<StringName> used_classes;
|
||||
|
||||
// Find classes and update the disk cache in the process.
|
||||
f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("used_class_cache"), FileAccess::WRITE);
|
||||
|
||||
for (const KeyValue<String, DetectedFile> &E : updated_file_cache) {
|
||||
String l = E.key + "::" + itos(E.value.timestamp) + "::" + E.value.md5 + "::";
|
||||
for (int i = 0; i < E.value.classes.size(); i++) {
|
||||
String c = E.value.classes[i];
|
||||
if (i > 0) {
|
||||
l += ",";
|
||||
}
|
||||
l += c;
|
||||
used_classes.insert(c);
|
||||
}
|
||||
f->store_line(l);
|
||||
}
|
||||
|
||||
f.unref();
|
||||
|
||||
// Add forced ones.
|
||||
|
||||
Vector<String> force_detect = edited->get_force_detect_classes().split(",");
|
||||
for (int i = 0; i < force_detect.size(); i++) {
|
||||
String c = force_detect[i].strip_edges();
|
||||
if (c.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
used_classes.insert(c);
|
||||
}
|
||||
|
||||
// Filter all classes to discard inherited ones.
|
||||
|
||||
HashSet<StringName> all_used_classes;
|
||||
|
||||
for (const StringName &E : used_classes) {
|
||||
StringName c = E;
|
||||
if (!ClassDB::class_exists(c)) {
|
||||
// Maybe this is an old class that got replaced? try getting compat class.
|
||||
c = ClassDB::get_compatibility_class(c);
|
||||
if (!c) {
|
||||
// No luck, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
while (c) {
|
||||
all_used_classes.insert(c);
|
||||
c = ClassDB::get_parent_class(c);
|
||||
}
|
||||
}
|
||||
|
||||
edited->clear_disabled_classes();
|
||||
|
||||
List<StringName> all_classes;
|
||||
ClassDB::get_class_list(&all_classes);
|
||||
|
||||
for (const StringName &E : all_classes) {
|
||||
if (all_used_classes.has(E)) {
|
||||
// This class is valid, do nothing.
|
||||
continue;
|
||||
}
|
||||
|
||||
StringName p = ClassDB::get_parent_class(E);
|
||||
if (!p || all_used_classes.has(p)) {
|
||||
// If no parent, or if the parent is enabled, then add to disabled classes.
|
||||
// This way we avoid disabling redundant classes.
|
||||
edited->set_disable_class(E, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_action_confirm() {
|
||||
switch (last_action) {
|
||||
case ACTION_RESET: {
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_LOAD: {
|
||||
} break;
|
||||
case ACTION_SAVE: {
|
||||
} break;
|
||||
case ACTION_SAVE_AS: {
|
||||
} break;
|
||||
case ACTION_NEW: {
|
||||
profile_path->set_text("");
|
||||
edited.instantiate();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_DETECT: {
|
||||
_detect_classes();
|
||||
_update_edited_profile();
|
||||
} break;
|
||||
case ACTION_MAX: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_hide_requested() {
|
||||
_cancel_pressed(); // From AcceptDialog.
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) {
|
||||
TreeItem *class_item = class_list->create_item(p_parent);
|
||||
class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class));
|
||||
const String &text = p_class;
|
||||
|
||||
bool disabled = edited->is_class_disabled(p_class);
|
||||
if (disabled) {
|
||||
class_item->set_custom_color(0, class_list->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
|
||||
}
|
||||
|
||||
class_item->set_text(0, text);
|
||||
class_item->set_editable(0, true);
|
||||
class_item->set_selectable(0, true);
|
||||
class_item->set_metadata(0, p_class);
|
||||
|
||||
bool collapsed = edited->is_item_collapsed(p_class);
|
||||
class_item->set_collapsed(collapsed);
|
||||
|
||||
if (p_class == p_selected) {
|
||||
class_item->select(0);
|
||||
}
|
||||
if (disabled) {
|
||||
// Class disabled, do nothing else (do not show further).
|
||||
return;
|
||||
}
|
||||
|
||||
class_item->set_checked(0, true); // If it's not disabled, its checked.
|
||||
|
||||
List<StringName> child_classes;
|
||||
ClassDB::get_direct_inheriters_from_class(p_class, &child_classes);
|
||||
child_classes.sort_custom<StringName::AlphCompare>();
|
||||
|
||||
for (const StringName &name : child_classes) {
|
||||
if (String(name).begins_with("Editor") || ClassDB::get_api_type(name) != ClassDB::API_CORE) {
|
||||
continue;
|
||||
}
|
||||
_fill_classes_from(class_item, name, p_selected);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_selected() {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = class_list->get_selected();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
description_bit->parse_symbol("class|" + md.operator String() + "|");
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
|
||||
description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(build_option_description));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_edited() {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = class_list->get_edited();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool checked = item->is_checked(0);
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
String class_selected = md;
|
||||
edited->set_disable_class(class_selected, !checked);
|
||||
_update_edited_profile();
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
int build_option_selected = md;
|
||||
edited->set_disable_build_option(EditorBuildProfile::BuildOption(build_option_selected), !checked);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_item);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variant md = item->get_metadata(0);
|
||||
if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
String class_name = md;
|
||||
bool collapsed = item->is_collapsed();
|
||||
edited->set_item_collapsed(class_name, collapsed);
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_update_edited_profile() {
|
||||
String class_selected;
|
||||
int build_option_selected = -1;
|
||||
|
||||
if (class_list->get_selected()) {
|
||||
Variant md = class_list->get_selected()->get_metadata(0);
|
||||
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
|
||||
class_selected = md;
|
||||
} else if (md.get_type() == Variant::INT) {
|
||||
build_option_selected = md;
|
||||
}
|
||||
}
|
||||
|
||||
class_list->clear();
|
||||
|
||||
updating_build_options = true;
|
||||
|
||||
TreeItem *root = class_list->create_item();
|
||||
|
||||
HashMap<EditorBuildProfile::BuildOptionCategory, TreeItem *> subcats;
|
||||
for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_CATEGORY_MAX; i++) {
|
||||
TreeItem *build_cat;
|
||||
build_cat = class_list->create_item(root);
|
||||
|
||||
build_cat->set_text(0, EditorBuildProfile::get_build_option_category_name(EditorBuildProfile::BuildOptionCategory(i)));
|
||||
subcats[EditorBuildProfile::BuildOptionCategory(i)] = build_cat;
|
||||
}
|
||||
|
||||
for (int i = 0; i < EditorBuildProfile::BUILD_OPTION_MAX; i++) {
|
||||
TreeItem *build_option;
|
||||
build_option = class_list->create_item(subcats[EditorBuildProfile::get_build_option_category(EditorBuildProfile::BuildOption(i))]);
|
||||
|
||||
build_option->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
build_option->set_text(0, EditorBuildProfile::get_build_option_name(EditorBuildProfile::BuildOption(i)));
|
||||
build_option->set_selectable(0, true);
|
||||
build_option->set_editable(0, true);
|
||||
build_option->set_metadata(0, i);
|
||||
if (!edited->is_build_option_disabled(EditorBuildProfile::BuildOption(i))) {
|
||||
build_option->set_checked(0, true);
|
||||
}
|
||||
|
||||
if (i == build_option_selected) {
|
||||
build_option->select(0);
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *classes = class_list->create_item(root);
|
||||
classes->set_text(0, TTR("Nodes and Classes:"));
|
||||
|
||||
_fill_classes_from(classes, "Node", class_selected);
|
||||
_fill_classes_from(classes, "Resource", class_selected);
|
||||
|
||||
force_detect_classes->set_text(edited->get_force_detect_classes());
|
||||
|
||||
updating_build_options = false;
|
||||
|
||||
_class_list_item_selected();
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_force_detect_classes_changed(const String &p_text) {
|
||||
if (updating_build_options) {
|
||||
return;
|
||||
}
|
||||
edited->set_force_detect_classes(force_detect_classes->get_text());
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_import_profile(const String &p_path) {
|
||||
Ref<EditorBuildProfile> profile;
|
||||
profile.instantiate();
|
||||
Error err = profile->load_from_file(p_path);
|
||||
String basefile = p_path.get_file();
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("File '%s' format is invalid, import aborted."), basefile));
|
||||
return;
|
||||
}
|
||||
|
||||
profile_path->set_text(p_path);
|
||||
EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
|
||||
|
||||
edited = profile;
|
||||
_update_edited_profile();
|
||||
}
|
||||
|
||||
void EditorBuildProfileManager::_export_profile(const String &p_path) {
|
||||
ERR_FAIL_COND(edited.is_null());
|
||||
Error err = edited->save_to_file(p_path);
|
||||
if (err != OK) {
|
||||
EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving profile to path: '%s'."), p_path));
|
||||
} else {
|
||||
profile_path->set_text(p_path);
|
||||
EditorSettings::get_singleton()->set_project_metadata("build_profile", "last_file_path", p_path);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<EditorBuildProfile> EditorBuildProfileManager::get_current_profile() {
|
||||
return edited;
|
||||
}
|
||||
|
||||
EditorBuildProfileManager *EditorBuildProfileManager::singleton = nullptr;
|
||||
|
||||
void EditorBuildProfileManager::_bind_methods() {
|
||||
ClassDB::bind_method("_update_selected_profile", &EditorBuildProfileManager::_update_edited_profile);
|
||||
}
|
||||
|
||||
EditorBuildProfileManager::EditorBuildProfileManager() {
|
||||
VBoxContainer *main_vbc = memnew(VBoxContainer);
|
||||
add_child(main_vbc);
|
||||
|
||||
HBoxContainer *path_hbc = memnew(HBoxContainer);
|
||||
profile_path = memnew(LineEdit);
|
||||
path_hbc->add_child(profile_path);
|
||||
profile_path->set_editable(true);
|
||||
profile_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
profile_actions[ACTION_NEW] = memnew(Button(TTR("New")));
|
||||
path_hbc->add_child(profile_actions[ACTION_NEW]);
|
||||
profile_actions[ACTION_NEW]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_NEW));
|
||||
|
||||
profile_actions[ACTION_LOAD] = memnew(Button(TTR("Load")));
|
||||
path_hbc->add_child(profile_actions[ACTION_LOAD]);
|
||||
profile_actions[ACTION_LOAD]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_LOAD));
|
||||
|
||||
profile_actions[ACTION_SAVE] = memnew(Button(TTR("Save")));
|
||||
path_hbc->add_child(profile_actions[ACTION_SAVE]);
|
||||
profile_actions[ACTION_SAVE]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_SAVE));
|
||||
|
||||
profile_actions[ACTION_SAVE_AS] = memnew(Button(TTR("Save As")));
|
||||
path_hbc->add_child(profile_actions[ACTION_SAVE_AS]);
|
||||
profile_actions[ACTION_SAVE_AS]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_SAVE_AS));
|
||||
|
||||
main_vbc->add_margin_child(TTR("Profile:"), path_hbc);
|
||||
|
||||
main_vbc->add_child(memnew(HSeparator));
|
||||
|
||||
HBoxContainer *profiles_hbc = memnew(HBoxContainer);
|
||||
|
||||
profile_actions[ACTION_RESET] = memnew(Button(TTR("Reset to Defaults")));
|
||||
profiles_hbc->add_child(profile_actions[ACTION_RESET]);
|
||||
profile_actions[ACTION_RESET]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_RESET));
|
||||
|
||||
profile_actions[ACTION_DETECT] = memnew(Button(TTR("Detect from Project")));
|
||||
profiles_hbc->add_child(profile_actions[ACTION_DETECT]);
|
||||
profile_actions[ACTION_DETECT]->connect(SceneStringName(pressed), callable_mp(this, &EditorBuildProfileManager::_profile_action).bind(ACTION_DETECT));
|
||||
|
||||
main_vbc->add_margin_child(TTR("Actions:"), profiles_hbc);
|
||||
|
||||
class_list = memnew(Tree);
|
||||
class_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
class_list->set_hide_root(true);
|
||||
class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
|
||||
class_list->connect("cell_selected", callable_mp(this, &EditorBuildProfileManager::_class_list_item_selected));
|
||||
class_list->connect("item_edited", callable_mp(this, &EditorBuildProfileManager::_class_list_item_edited), CONNECT_DEFERRED);
|
||||
class_list->connect("item_collapsed", callable_mp(this, &EditorBuildProfileManager::_class_list_item_collapsed));
|
||||
// It will be displayed once the user creates or chooses a profile.
|
||||
main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true);
|
||||
|
||||
description_bit = memnew(EditorHelpBit);
|
||||
description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
|
||||
description_bit->connect("request_hide", callable_mp(this, &EditorBuildProfileManager::_hide_requested));
|
||||
main_vbc->add_margin_child(TTR("Description:"), description_bit, false);
|
||||
|
||||
confirm_dialog = memnew(ConfirmationDialog);
|
||||
add_child(confirm_dialog);
|
||||
confirm_dialog->set_title(TTR("Please Confirm:"));
|
||||
confirm_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorBuildProfileManager::_action_confirm));
|
||||
|
||||
import_profile = memnew(EditorFileDialog);
|
||||
add_child(import_profile);
|
||||
import_profile->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
|
||||
import_profile->add_filter("*.build", TTR("Engine Compilation Profile"));
|
||||
import_profile->connect("files_selected", callable_mp(this, &EditorBuildProfileManager::_import_profile));
|
||||
import_profile->set_title(TTR("Load Profile"));
|
||||
import_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
|
||||
export_profile = memnew(EditorFileDialog);
|
||||
add_child(export_profile);
|
||||
export_profile->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
export_profile->add_filter("*.build", TTR("Engine Compilation Profile"));
|
||||
export_profile->connect("file_selected", callable_mp(this, &EditorBuildProfileManager::_export_profile));
|
||||
export_profile->set_title(TTR("Export Profile"));
|
||||
export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
|
||||
|
||||
force_detect_classes = memnew(LineEdit);
|
||||
main_vbc->add_margin_child(TTR("Forced Classes on Detect:"), force_detect_classes);
|
||||
force_detect_classes->connect(SceneStringName(text_changed), callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed));
|
||||
|
||||
set_title(TTR("Edit Compilation Configuration Profile"));
|
||||
|
||||
singleton = this;
|
||||
}
|
||||
193
engine/editor/editor_build_profile.h
Normal file
193
engine/editor/editor_build_profile.h
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/**************************************************************************/
|
||||
/* editor_build_profile.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_BUILD_PROFILE_H
|
||||
#define EDITOR_BUILD_PROFILE_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "editor/editor_help.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorBuildProfile : public RefCounted {
|
||||
GDCLASS(EditorBuildProfile, RefCounted);
|
||||
|
||||
public:
|
||||
enum BuildOption {
|
||||
BUILD_OPTION_3D,
|
||||
BUILD_OPTION_PHYSICS_2D,
|
||||
BUILD_OPTION_PHYSICS_3D,
|
||||
BUILD_OPTION_NAVIGATION,
|
||||
BUILD_OPTION_XR,
|
||||
BUILD_OPTION_RENDERING_DEVICE,
|
||||
BUILD_OPTION_OPENGL,
|
||||
BUILD_OPTION_VULKAN,
|
||||
BUILD_OPTION_TEXT_SERVER_FALLBACK,
|
||||
BUILD_OPTION_TEXT_SERVER_ADVANCED,
|
||||
BUILD_OPTION_DYNAMIC_FONTS,
|
||||
BUILD_OPTION_WOFF2_FONTS,
|
||||
BUILD_OPTION_GRPAHITE_FONTS,
|
||||
BUILD_OPTION_MSDFGEN,
|
||||
BUILD_OPTION_MAX,
|
||||
};
|
||||
|
||||
enum BuildOptionCategory {
|
||||
BUILD_OPTION_CATEGORY_GENERAL,
|
||||
BUILD_OPTION_CATEGORY_TEXT_SERVER,
|
||||
BUILD_OPTION_CATEGORY_MAX,
|
||||
};
|
||||
|
||||
private:
|
||||
HashSet<StringName> disabled_classes;
|
||||
|
||||
HashSet<StringName> collapsed_classes;
|
||||
|
||||
String force_detect_classes;
|
||||
|
||||
bool build_options_disabled[BUILD_OPTION_MAX] = {};
|
||||
static const char *build_option_identifiers[BUILD_OPTION_MAX];
|
||||
static const bool build_option_disabled_by_default[BUILD_OPTION_MAX];
|
||||
static const bool build_option_disable_values[BUILD_OPTION_MAX];
|
||||
static const BuildOptionCategory build_option_category[BUILD_OPTION_MAX];
|
||||
|
||||
String _get_build_option_name(BuildOption p_build_option) { return get_build_option_name(p_build_option); }
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_disable_class(const StringName &p_class, bool p_disabled);
|
||||
bool is_class_disabled(const StringName &p_class) const;
|
||||
|
||||
void set_item_collapsed(const StringName &p_class, bool p_collapsed);
|
||||
bool is_item_collapsed(const StringName &p_class) const;
|
||||
|
||||
void set_disable_build_option(BuildOption p_build_option, bool p_disable);
|
||||
bool is_build_option_disabled(BuildOption p_build_option) const;
|
||||
|
||||
void set_force_detect_classes(const String &p_classes);
|
||||
String get_force_detect_classes() const;
|
||||
|
||||
void clear_disabled_classes();
|
||||
|
||||
Error save_to_file(const String &p_path);
|
||||
Error load_from_file(const String &p_path);
|
||||
|
||||
static String get_build_option_name(BuildOption p_build_option);
|
||||
static String get_build_option_description(BuildOption p_build_option);
|
||||
static bool get_build_option_disable_value(BuildOption p_build_option);
|
||||
static BuildOptionCategory get_build_option_category(BuildOption p_build_option);
|
||||
|
||||
static String get_build_option_category_name(BuildOptionCategory p_build_option_category);
|
||||
|
||||
EditorBuildProfile();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorBuildProfile::BuildOption)
|
||||
VARIANT_ENUM_CAST(EditorBuildProfile::BuildOptionCategory)
|
||||
|
||||
class EditorFileDialog;
|
||||
class EditorFileSystemDirectory;
|
||||
|
||||
class EditorBuildProfileManager : public AcceptDialog {
|
||||
GDCLASS(EditorBuildProfileManager, AcceptDialog);
|
||||
|
||||
enum Action {
|
||||
ACTION_NEW,
|
||||
ACTION_RESET,
|
||||
ACTION_LOAD,
|
||||
ACTION_SAVE,
|
||||
ACTION_SAVE_AS,
|
||||
ACTION_DETECT,
|
||||
ACTION_MAX
|
||||
};
|
||||
|
||||
Action last_action = ACTION_NEW;
|
||||
|
||||
ConfirmationDialog *confirm_dialog = nullptr;
|
||||
Button *profile_actions[ACTION_MAX];
|
||||
|
||||
Tree *class_list = nullptr;
|
||||
EditorHelpBit *description_bit = nullptr;
|
||||
|
||||
EditorFileDialog *import_profile = nullptr;
|
||||
EditorFileDialog *export_profile = nullptr;
|
||||
|
||||
LineEdit *profile_path = nullptr;
|
||||
|
||||
LineEdit *force_detect_classes = nullptr;
|
||||
|
||||
void _profile_action(int p_action);
|
||||
void _action_confirm();
|
||||
void _hide_requested();
|
||||
|
||||
void _update_edited_profile();
|
||||
void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected);
|
||||
|
||||
Ref<EditorBuildProfile> edited;
|
||||
|
||||
void _import_profile(const String &p_path);
|
||||
void _export_profile(const String &p_path);
|
||||
|
||||
bool updating_build_options = false;
|
||||
|
||||
void _class_list_item_selected();
|
||||
void _class_list_item_edited();
|
||||
void _class_list_item_collapsed(Object *p_item);
|
||||
void _detect_classes();
|
||||
|
||||
void _force_detect_classes_changed(const String &p_text);
|
||||
|
||||
struct DetectedFile {
|
||||
uint32_t timestamp = 0;
|
||||
String md5;
|
||||
Vector<String> classes;
|
||||
};
|
||||
|
||||
void _find_files(EditorFileSystemDirectory *p_dir, const HashMap<String, DetectedFile> &p_cache, HashMap<String, DetectedFile> &r_detected);
|
||||
|
||||
static EditorBuildProfileManager *singleton;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Ref<EditorBuildProfile> get_current_profile();
|
||||
|
||||
static EditorBuildProfileManager *get_singleton() { return singleton; }
|
||||
EditorBuildProfileManager();
|
||||
};
|
||||
|
||||
#endif // EDITOR_BUILD_PROFILE_H
|
||||
142
engine/editor/editor_builders.py
Normal file
142
engine/editor/editor_builders.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
"""Functions used to generate source files during build time"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import uuid
|
||||
import zlib
|
||||
|
||||
from methods import print_warning
|
||||
|
||||
|
||||
def make_doc_header(target, source, env):
|
||||
dst = str(target[0])
|
||||
with open(dst, "w", encoding="utf-8", newline="\n") as g:
|
||||
buf = ""
|
||||
docbegin = ""
|
||||
docend = ""
|
||||
for src in source:
|
||||
src = str(src)
|
||||
if not src.endswith(".xml"):
|
||||
continue
|
||||
with open(src, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
buf += content
|
||||
|
||||
buf = (docbegin + buf + docend).encode("utf-8")
|
||||
decomp_size = len(buf)
|
||||
|
||||
# Use maximum zlib compression level to further reduce file size
|
||||
# (at the cost of initial build times).
|
||||
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
|
||||
|
||||
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
|
||||
g.write("#ifndef _DOC_DATA_RAW_H\n")
|
||||
g.write("#define _DOC_DATA_RAW_H\n")
|
||||
g.write('static const char *_doc_data_hash = "' + str(hash(buf)) + '";\n')
|
||||
g.write("static const int _doc_data_compressed_size = " + str(len(buf)) + ";\n")
|
||||
g.write("static const int _doc_data_uncompressed_size = " + str(decomp_size) + ";\n")
|
||||
g.write("static const unsigned char _doc_data_compressed[] = {\n")
|
||||
for i in range(len(buf)):
|
||||
g.write("\t" + str(buf[i]) + ",\n")
|
||||
g.write("};\n")
|
||||
|
||||
g.write("#endif")
|
||||
|
||||
|
||||
def make_translations_header(target, source, env, category):
|
||||
dst = str(target[0])
|
||||
|
||||
with open(dst, "w", encoding="utf-8", newline="\n") as g:
|
||||
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
|
||||
g.write("#ifndef _{}_TRANSLATIONS_H\n".format(category.upper()))
|
||||
g.write("#define _{}_TRANSLATIONS_H\n".format(category.upper()))
|
||||
|
||||
sorted_paths = sorted([str(x) for x in source], key=lambda path: os.path.splitext(os.path.basename(path))[0])
|
||||
|
||||
msgfmt_available = shutil.which("msgfmt") is not None
|
||||
|
||||
if not msgfmt_available:
|
||||
print_warning("msgfmt is not found, using .po files instead of .mo")
|
||||
|
||||
xl_names = []
|
||||
for i in range(len(sorted_paths)):
|
||||
name = os.path.splitext(os.path.basename(sorted_paths[i]))[0]
|
||||
# msgfmt erases non-translated messages, so avoid using it if exporting the POT.
|
||||
if msgfmt_available and name != category:
|
||||
mo_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".mo")
|
||||
cmd = "msgfmt " + sorted_paths[i] + " --no-hash -o " + mo_path
|
||||
try:
|
||||
subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
|
||||
with open(mo_path, "rb") as f:
|
||||
buf = f.read()
|
||||
except OSError as e:
|
||||
print_warning(
|
||||
"msgfmt execution failed, using .po file instead of .mo: path=%r; [%s] %s"
|
||||
% (sorted_paths[i], e.__class__.__name__, e)
|
||||
)
|
||||
with open(sorted_paths[i], "rb") as f:
|
||||
buf = f.read()
|
||||
finally:
|
||||
try:
|
||||
os.remove(mo_path)
|
||||
except OSError as e:
|
||||
# Do not fail the entire build if it cannot delete a temporary file.
|
||||
print_warning(
|
||||
"Could not delete temporary .mo file: path=%r; [%s] %s" % (mo_path, e.__class__.__name__, e)
|
||||
)
|
||||
else:
|
||||
with open(sorted_paths[i], "rb") as f:
|
||||
buf = f.read()
|
||||
|
||||
if name == category:
|
||||
name = "source"
|
||||
|
||||
decomp_size = len(buf)
|
||||
# Use maximum zlib compression level to further reduce file size
|
||||
# (at the cost of initial build times).
|
||||
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
|
||||
|
||||
g.write("static const unsigned char _{}_translation_{}_compressed[] = {{\n".format(category, name))
|
||||
for j in range(len(buf)):
|
||||
g.write("\t" + str(buf[j]) + ",\n")
|
||||
|
||||
g.write("};\n")
|
||||
|
||||
xl_names.append([name, len(buf), str(decomp_size)])
|
||||
|
||||
g.write("struct {}TranslationList {{\n".format(category.capitalize()))
|
||||
g.write("\tconst char* lang;\n")
|
||||
g.write("\tint comp_size;\n")
|
||||
g.write("\tint uncomp_size;\n")
|
||||
g.write("\tconst unsigned char* data;\n")
|
||||
g.write("};\n\n")
|
||||
g.write("static {}TranslationList _{}_translations[] = {{\n".format(category.capitalize(), category))
|
||||
for x in xl_names:
|
||||
g.write(
|
||||
'\t{{ "{}", {}, {}, _{}_translation_{}_compressed }},\n'.format(
|
||||
x[0], str(x[1]), str(x[2]), category, x[0]
|
||||
)
|
||||
)
|
||||
g.write("\t{nullptr, 0, 0, nullptr}\n")
|
||||
g.write("};\n")
|
||||
|
||||
g.write("#endif")
|
||||
|
||||
|
||||
def make_editor_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "editor")
|
||||
|
||||
|
||||
def make_property_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "property")
|
||||
|
||||
|
||||
def make_doc_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "doc")
|
||||
|
||||
|
||||
def make_extractable_translations_header(target, source, env):
|
||||
make_translations_header(target, source, env, "extractable")
|
||||
392
engine/editor/editor_command_palette.cpp
Normal file
392
engine/editor/editor_command_palette.cpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
/**************************************************************************/
|
||||
/* editor_command_palette.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/editor_command_palette.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_toaster.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
EditorCommandPalette *EditorCommandPalette::singleton = nullptr;
|
||||
|
||||
static Rect2i prev_rect = Rect2i();
|
||||
static bool was_showed = false;
|
||||
|
||||
float EditorCommandPalette::_score_path(const String &p_search, const String &p_path) {
|
||||
float score = 0.9f + .1f * (p_search.length() / (float)p_path.length());
|
||||
|
||||
// Positive bias for matches close to the beginning of the file name.
|
||||
int pos = p_path.findn(p_search);
|
||||
if (pos != -1) {
|
||||
return score * (1.0f - 0.1f * (float(pos) / p_path.length()));
|
||||
}
|
||||
|
||||
// Positive bias for matches close to the end of the path.
|
||||
pos = p_path.rfindn(p_search);
|
||||
if (pos != -1) {
|
||||
return score * (0.8f - 0.1f * (float(p_path.length() - pos) / p_path.length()));
|
||||
}
|
||||
|
||||
// Remaining results belong to the same class of results.
|
||||
return score * 0.69f;
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_update_command_search(const String &search_text) {
|
||||
ERR_FAIL_COND(commands.is_empty());
|
||||
|
||||
HashMap<String, TreeItem *> sections;
|
||||
TreeItem *first_section = nullptr;
|
||||
|
||||
// Filter possible candidates.
|
||||
Vector<CommandEntry> entries;
|
||||
for (const KeyValue<String, Command> &E : commands) {
|
||||
CommandEntry r;
|
||||
r.key_name = E.key;
|
||||
r.display_name = E.value.name;
|
||||
r.shortcut_text = E.value.shortcut_text;
|
||||
r.last_used = E.value.last_used;
|
||||
|
||||
bool is_subsequence_of_key_name = search_text.is_subsequence_ofn(r.key_name);
|
||||
bool is_subsequence_of_display_name = search_text.is_subsequence_ofn(r.display_name);
|
||||
|
||||
if (is_subsequence_of_key_name || is_subsequence_of_display_name) {
|
||||
if (!search_text.is_empty()) {
|
||||
float key_name_score = is_subsequence_of_key_name ? _score_path(search_text, r.key_name.to_lower()) : .0f;
|
||||
float display_name_score = is_subsequence_of_display_name ? _score_path(search_text, r.display_name.to_lower()) : .0f;
|
||||
|
||||
r.score = MAX(key_name_score, display_name_score);
|
||||
}
|
||||
|
||||
entries.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *root = search_options->get_root();
|
||||
root->clear_children();
|
||||
|
||||
if (entries.is_empty()) {
|
||||
get_ok_button()->set_disabled(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search_text.is_empty()) {
|
||||
SortArray<CommandEntry, CommandEntryComparator> sorter;
|
||||
sorter.sort(entries.ptrw(), entries.size());
|
||||
} else {
|
||||
SortArray<CommandEntry, CommandHistoryComparator> sorter;
|
||||
sorter.sort(entries.ptrw(), entries.size());
|
||||
}
|
||||
|
||||
const int entry_limit = MIN(entries.size(), 300);
|
||||
for (int i = 0; i < entry_limit; i++) {
|
||||
String section_name = entries[i].key_name.get_slice("/", 0);
|
||||
TreeItem *section;
|
||||
|
||||
if (sections.has(section_name)) {
|
||||
section = sections[section_name];
|
||||
} else {
|
||||
section = search_options->create_item(root);
|
||||
|
||||
if (!first_section) {
|
||||
first_section = section;
|
||||
}
|
||||
|
||||
String item_name = section_name.capitalize();
|
||||
section->set_text(0, item_name);
|
||||
section->set_selectable(0, false);
|
||||
section->set_selectable(1, false);
|
||||
section->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
|
||||
section->set_custom_bg_color(1, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
|
||||
|
||||
sections[section_name] = section;
|
||||
}
|
||||
|
||||
TreeItem *ti = search_options->create_item(section);
|
||||
String shortcut_text = entries[i].shortcut_text == "None" ? "" : entries[i].shortcut_text;
|
||||
ti->set_text(0, entries[i].display_name);
|
||||
ti->set_metadata(0, entries[i].key_name);
|
||||
ti->set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
ti->set_text(1, shortcut_text);
|
||||
Color c = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.5);
|
||||
ti->set_custom_color(1, c);
|
||||
}
|
||||
|
||||
TreeItem *to_select = first_section->get_first_child();
|
||||
to_select->select(0);
|
||||
to_select->set_as_cursor(0);
|
||||
search_options->ensure_cursor_is_visible();
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_command", "command_name", "key_name", "binded_callable", "shortcut_text"), &EditorCommandPalette::_add_command, DEFVAL("None"));
|
||||
ClassDB::bind_method(D_METHOD("remove_command", "key_name"), &EditorCommandPalette::remove_command);
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (!is_visible()) {
|
||||
prev_rect = Rect2i(get_position(), get_size());
|
||||
was_showed = true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
command_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
} break;
|
||||
|
||||
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
||||
if (!EditorSettings::get_singleton()->check_changed_settings_in_group("shortcuts")) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (KeyValue<String, Command> &kv : commands) {
|
||||
Command &c = kv.value;
|
||||
if (c.shortcut.is_valid()) {
|
||||
c.shortcut_text = c.shortcut->get_as_text();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_sbox_input(const Ref<InputEvent> &p_ie) {
|
||||
Ref<InputEventKey> k = p_ie;
|
||||
if (k.is_valid()) {
|
||||
switch (k->get_keycode()) {
|
||||
case Key::UP:
|
||||
case Key::DOWN:
|
||||
case Key::PAGEUP:
|
||||
case Key::PAGEDOWN: {
|
||||
search_options->gui_input(k);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_confirmed() {
|
||||
TreeItem *selected_option = search_options->get_selected();
|
||||
const String command_key = selected_option != nullptr ? selected_option->get_metadata(0) : "";
|
||||
if (!command_key.is_empty()) {
|
||||
hide();
|
||||
callable_mp(this, &EditorCommandPalette::execute_command).call_deferred(command_key);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCommandPalette::open_popup() {
|
||||
if (was_showed) {
|
||||
popup(prev_rect);
|
||||
} else {
|
||||
popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f);
|
||||
}
|
||||
|
||||
command_search_box->clear();
|
||||
command_search_box->grab_focus();
|
||||
|
||||
search_options->scroll_to_item(search_options->get_root());
|
||||
}
|
||||
|
||||
void EditorCommandPalette::get_actions_list(List<String> *p_list) const {
|
||||
for (const KeyValue<String, Command> &E : commands) {
|
||||
p_list->push_back(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCommandPalette::remove_command(String p_key_name) {
|
||||
ERR_FAIL_COND_MSG(!commands.has(p_key_name), "The Command '" + String(p_key_name) + "' doesn't exists. Unable to remove it.");
|
||||
|
||||
commands.erase(p_key_name);
|
||||
}
|
||||
|
||||
void EditorCommandPalette::add_command(String p_command_name, String p_key_name, Callable p_action, Vector<Variant> arguments, const Ref<Shortcut> &p_shortcut) {
|
||||
ERR_FAIL_COND_MSG(commands.has(p_key_name), "The Command '" + String(p_command_name) + "' already exists. Unable to add it.");
|
||||
|
||||
const Variant **argptrs = (const Variant **)alloca(sizeof(Variant *) * arguments.size());
|
||||
for (int i = 0; i < arguments.size(); i++) {
|
||||
argptrs[i] = &arguments[i];
|
||||
}
|
||||
Command command;
|
||||
command.name = p_command_name;
|
||||
command.callable = p_action.bindp(argptrs, arguments.size());
|
||||
if (p_shortcut.is_null()) {
|
||||
command.shortcut_text = "None";
|
||||
} else {
|
||||
command.shortcut = p_shortcut;
|
||||
command.shortcut_text = p_shortcut->get_as_text();
|
||||
}
|
||||
|
||||
commands[p_key_name] = command;
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_add_command(String p_command_name, String p_key_name, Callable p_binded_action, String p_shortcut_text) {
|
||||
ERR_FAIL_COND_MSG(commands.has(p_key_name), "The Command '" + String(p_command_name) + "' already exists. Unable to add it.");
|
||||
|
||||
Command command;
|
||||
command.name = p_command_name;
|
||||
command.callable = p_binded_action;
|
||||
command.shortcut_text = p_shortcut_text;
|
||||
|
||||
// Commands added from plugins don't exist yet when the history is loaded, so we assign the last use time here if it was recorded.
|
||||
Dictionary command_history = EditorSettings::get_singleton()->get_project_metadata("command_palette", "command_history", Dictionary());
|
||||
if (command_history.has(p_key_name)) {
|
||||
command.last_used = command_history[p_key_name];
|
||||
}
|
||||
|
||||
commands[p_key_name] = command;
|
||||
}
|
||||
|
||||
void EditorCommandPalette::execute_command(const String &p_command_key) {
|
||||
ERR_FAIL_COND_MSG(!commands.has(p_command_key), p_command_key + " not found.");
|
||||
commands[p_command_key].last_used = OS::get_singleton()->get_unix_time();
|
||||
_save_history();
|
||||
|
||||
Variant ret;
|
||||
Callable::CallError ce;
|
||||
const Callable &callable = commands[p_command_key].callable;
|
||||
callable.callp(nullptr, 0, ret, ce);
|
||||
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
EditorToaster::get_singleton()->popup_str(vformat(TTR("Failed to execute command \"%s\":\n%s."), p_command_key, Variant::get_callable_error_text(callable, nullptr, 0, ce)), EditorToaster::SEVERITY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorCommandPalette::register_shortcuts_as_command() {
|
||||
for (const KeyValue<String, Pair<String, Ref<Shortcut>>> &E : unregistered_shortcuts) {
|
||||
String command_name = E.value.first;
|
||||
Ref<Shortcut> shortcut = E.value.second;
|
||||
Ref<InputEventShortcut> ev;
|
||||
ev.instantiate();
|
||||
ev->set_shortcut(shortcut);
|
||||
add_command(command_name, E.key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::push_input), varray(ev, false), shortcut);
|
||||
}
|
||||
unregistered_shortcuts.clear();
|
||||
|
||||
// Load command use history.
|
||||
Dictionary command_history = EditorSettings::get_singleton()->get_project_metadata("command_palette", "command_history", Dictionary());
|
||||
Array history_entries = command_history.keys();
|
||||
for (int i = 0; i < history_entries.size(); i++) {
|
||||
const String &history_key = history_entries[i];
|
||||
if (commands.has(history_key)) {
|
||||
commands[history_key].last_used = command_history[history_key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Shortcut> EditorCommandPalette::add_shortcut_command(const String &p_command, const String &p_key, Ref<Shortcut> p_shortcut) {
|
||||
if (is_inside_tree()) {
|
||||
Ref<InputEventShortcut> ev;
|
||||
ev.instantiate();
|
||||
ev->set_shortcut(p_shortcut);
|
||||
add_command(p_command, p_key, callable_mp(EditorNode::get_singleton()->get_viewport(), &Viewport::push_input), varray(ev, false), p_shortcut);
|
||||
} else {
|
||||
const String key_name = String(p_key);
|
||||
const String command_name = String(p_command);
|
||||
Pair pair = Pair(command_name, p_shortcut);
|
||||
unregistered_shortcuts[key_name] = pair;
|
||||
}
|
||||
return p_shortcut;
|
||||
}
|
||||
|
||||
void EditorCommandPalette::_save_history() const {
|
||||
Dictionary command_history;
|
||||
|
||||
for (const KeyValue<String, Command> &E : commands) {
|
||||
if (E.value.last_used > 0) {
|
||||
command_history[E.key] = E.value.last_used;
|
||||
}
|
||||
}
|
||||
EditorSettings::get_singleton()->set_project_metadata("command_palette", "command_history", command_history);
|
||||
}
|
||||
|
||||
EditorCommandPalette *EditorCommandPalette::get_singleton() {
|
||||
if (singleton == nullptr) {
|
||||
singleton = memnew(EditorCommandPalette);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
EditorCommandPalette::EditorCommandPalette() {
|
||||
set_hide_on_ok(false);
|
||||
connect(SceneStringName(confirmed), callable_mp(this, &EditorCommandPalette::_confirmed));
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
command_search_box = memnew(LineEdit);
|
||||
command_search_box->set_placeholder(TTR("Filter Commands"));
|
||||
command_search_box->connect(SceneStringName(gui_input), callable_mp(this, &EditorCommandPalette::_sbox_input));
|
||||
command_search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorCommandPalette::_update_command_search));
|
||||
command_search_box->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
command_search_box->set_clear_button_enabled(true);
|
||||
MarginContainer *margin_container_csb = memnew(MarginContainer);
|
||||
margin_container_csb->add_child(command_search_box);
|
||||
vbc->add_child(margin_container_csb);
|
||||
register_text_enter(command_search_box);
|
||||
|
||||
search_options = memnew(Tree);
|
||||
search_options->connect("item_activated", callable_mp(this, &EditorCommandPalette::_confirmed));
|
||||
search_options->connect(SceneStringName(item_selected), callable_mp((BaseButton *)get_ok_button(), &BaseButton::set_disabled).bind(false));
|
||||
search_options->connect("nothing_selected", callable_mp((BaseButton *)get_ok_button(), &BaseButton::set_disabled).bind(true));
|
||||
search_options->create_item();
|
||||
search_options->set_hide_root(true);
|
||||
search_options->set_columns(2);
|
||||
search_options->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
search_options->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
search_options->set_column_custom_minimum_width(0, int(8 * EDSCALE));
|
||||
vbc->add_child(search_options, true);
|
||||
}
|
||||
|
||||
Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name, Key p_keycode, String p_command_name) {
|
||||
if (p_command_name.is_empty()) {
|
||||
p_command_name = p_name;
|
||||
}
|
||||
|
||||
Ref<Shortcut> shortcut = ED_SHORTCUT(p_path, p_name, p_keycode);
|
||||
EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
|
||||
return shortcut;
|
||||
}
|
||||
|
||||
Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command_name) {
|
||||
if (p_command_name.is_empty()) {
|
||||
p_command_name = p_name;
|
||||
}
|
||||
|
||||
Ref<Shortcut> shortcut = ED_SHORTCUT_ARRAY(p_path, p_name, p_keycodes);
|
||||
EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
|
||||
return shortcut;
|
||||
}
|
||||
108
engine/editor/editor_command_palette.h
Normal file
108
engine/editor/editor_command_palette.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**************************************************************************/
|
||||
/* editor_command_palette.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_COMMAND_PALETTE_H
|
||||
#define EDITOR_COMMAND_PALETTE_H
|
||||
|
||||
#include "core/input/shortcut.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorCommandPalette : public ConfirmationDialog {
|
||||
GDCLASS(EditorCommandPalette, ConfirmationDialog);
|
||||
|
||||
static EditorCommandPalette *singleton;
|
||||
LineEdit *command_search_box = nullptr;
|
||||
Tree *search_options = nullptr;
|
||||
|
||||
struct Command {
|
||||
Callable callable;
|
||||
String name;
|
||||
Ref<Shortcut> shortcut;
|
||||
String shortcut_text;
|
||||
int last_used = 0; // Store time as int, because doubles have problems with text serialization.
|
||||
};
|
||||
|
||||
struct CommandEntry {
|
||||
String key_name;
|
||||
String display_name;
|
||||
String shortcut_text;
|
||||
int last_used = 0;
|
||||
float score = 0;
|
||||
};
|
||||
|
||||
struct CommandEntryComparator {
|
||||
_FORCE_INLINE_ bool operator()(const CommandEntry &A, const CommandEntry &B) const {
|
||||
return A.score > B.score;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommandHistoryComparator {
|
||||
_FORCE_INLINE_ bool operator()(const CommandEntry &A, const CommandEntry &B) const {
|
||||
if (A.last_used == B.last_used) {
|
||||
return A.display_name < B.display_name;
|
||||
} else {
|
||||
return A.last_used > B.last_used;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<String, Command> commands;
|
||||
HashMap<String, Pair<String, Ref<Shortcut>>> unregistered_shortcuts;
|
||||
|
||||
void _update_command_search(const String &search_text);
|
||||
float _score_path(const String &p_search, const String &p_path);
|
||||
void _sbox_input(const Ref<InputEvent> &p_ie);
|
||||
void _confirmed();
|
||||
void _add_command(String p_command_name, String p_key_name, Callable p_binded_action, String p_shortcut_text = "None");
|
||||
void _save_history() const;
|
||||
|
||||
EditorCommandPalette();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void open_popup();
|
||||
void get_actions_list(List<String> *p_list) const;
|
||||
void add_command(String p_command_name, String p_key_name, Callable p_action, Vector<Variant> arguments, const Ref<Shortcut> &p_shortcut);
|
||||
void execute_command(const String &p_command_name);
|
||||
void register_shortcuts_as_command();
|
||||
Ref<Shortcut> add_shortcut_command(const String &p_command, const String &p_key, Ref<Shortcut> p_shortcut);
|
||||
void remove_command(String p_key_name);
|
||||
static EditorCommandPalette *get_singleton();
|
||||
};
|
||||
|
||||
Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name, Key p_keycode = Key::NONE, String p_command = "");
|
||||
Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command = "");
|
||||
|
||||
#endif // EDITOR_COMMAND_PALETTE_H
|
||||
1368
engine/editor/editor_data.cpp
Normal file
1368
engine/editor/editor_data.cpp
Normal file
File diff suppressed because it is too large
Load diff
332
engine/editor/editor_data.h
Normal file
332
engine/editor/editor_data.h
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/**************************************************************************/
|
||||
/* editor_data.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DATA_H
|
||||
#define EDITOR_DATA_H
|
||||
|
||||
#include "core/templates/list.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class ConfigFile;
|
||||
class EditorPlugin;
|
||||
class EditorUndoRedoManager;
|
||||
|
||||
/**
|
||||
* Stores the history of objects which have been selected for editing in the Editor & the Inspector.
|
||||
*
|
||||
* Used in the editor to set & access the currently edited object, as well as the history of objects which have been edited.
|
||||
*/
|
||||
class EditorSelectionHistory {
|
||||
// Stores the object & property (if relevant).
|
||||
struct _Object {
|
||||
Ref<RefCounted> ref;
|
||||
ObjectID object;
|
||||
String property;
|
||||
bool inspector_only = false;
|
||||
};
|
||||
|
||||
// Represents the selection of an object for editing.
|
||||
struct HistoryElement {
|
||||
// The sub-resources of the parent object (first in the path) that have been edited.
|
||||
// For example, Node2D -> nested resource -> nested resource, if edited each individually in their own inspector.
|
||||
Vector<_Object> path;
|
||||
// The current point in the path. This is always equal to the last item in the path - it is never decremented.
|
||||
int level = 0;
|
||||
};
|
||||
friend class EditorData;
|
||||
|
||||
Vector<HistoryElement> history;
|
||||
int current_elem_idx; // The current history element being edited.
|
||||
|
||||
public:
|
||||
void cleanup_history();
|
||||
|
||||
bool is_at_beginning() const;
|
||||
bool is_at_end() const;
|
||||
|
||||
// Adds an object to the selection history. A property name can be passed if the target is a subresource of the given object.
|
||||
// If the object should not change the main screen plugin, it can be set as inspector only.
|
||||
void add_object(ObjectID p_object, const String &p_property = String(), bool p_inspector_only = false);
|
||||
void replace_object(ObjectID p_old_object, ObjectID p_new_object);
|
||||
|
||||
int get_history_len();
|
||||
int get_history_pos();
|
||||
|
||||
// Gets an object from the history. The most recent object would be the object with p_obj = get_history_len() - 1.
|
||||
ObjectID get_history_obj(int p_obj) const;
|
||||
|
||||
bool next();
|
||||
bool previous();
|
||||
ObjectID get_current();
|
||||
bool is_current_inspector_only() const;
|
||||
|
||||
// Gets the size of the path of the current history item.
|
||||
int get_path_size() const;
|
||||
// Gets the object of the current history item, if valid.
|
||||
ObjectID get_path_object(int p_index) const;
|
||||
// Gets the property of the current history item.
|
||||
String get_path_property(int p_index) const;
|
||||
|
||||
void clear();
|
||||
|
||||
EditorSelectionHistory();
|
||||
};
|
||||
|
||||
class EditorSelection;
|
||||
|
||||
class EditorData {
|
||||
public:
|
||||
struct CustomType {
|
||||
String name;
|
||||
Ref<Script> script;
|
||||
Ref<Texture2D> icon;
|
||||
};
|
||||
|
||||
struct EditedScene {
|
||||
Node *root = nullptr;
|
||||
String path;
|
||||
uint64_t file_modified_time = 0;
|
||||
Dictionary editor_states;
|
||||
List<Node *> selection;
|
||||
Vector<EditorSelectionHistory::HistoryElement> history_stored;
|
||||
int history_current = 0;
|
||||
Dictionary custom_state;
|
||||
NodePath live_edit_root;
|
||||
int history_id = 0;
|
||||
uint64_t last_checked_version = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
Vector<EditorPlugin *> editor_plugins;
|
||||
HashMap<StringName, EditorPlugin *> extension_editor_plugins;
|
||||
|
||||
struct PropertyData {
|
||||
String name;
|
||||
Variant value;
|
||||
};
|
||||
HashMap<String, Vector<CustomType>> custom_types;
|
||||
|
||||
List<PropertyData> clipboard;
|
||||
EditorUndoRedoManager *undo_redo_manager;
|
||||
Vector<Callable> undo_redo_callbacks;
|
||||
HashMap<StringName, Callable> move_element_functions;
|
||||
|
||||
Vector<EditedScene> edited_scene;
|
||||
int current_edited_scene = -1;
|
||||
int last_created_scene = 1;
|
||||
|
||||
bool _find_updated_instances(Node *p_root, Node *p_node, HashSet<String> &checked_paths);
|
||||
|
||||
HashMap<StringName, String> _script_class_icon_paths;
|
||||
HashMap<String, StringName> _script_class_file_to_path;
|
||||
HashMap<Ref<Script>, Ref<Texture>> _script_icon_cache;
|
||||
|
||||
Ref<Texture2D> _load_script_icon(const String &p_path) const;
|
||||
|
||||
public:
|
||||
EditorPlugin *get_handling_main_editor(Object *p_object);
|
||||
Vector<EditorPlugin *> get_handling_sub_editors(Object *p_object);
|
||||
EditorPlugin *get_editor_by_name(const String &p_name);
|
||||
|
||||
void copy_object_params(Object *p_object);
|
||||
void paste_object_params(Object *p_object);
|
||||
|
||||
Dictionary get_editor_plugin_states() const;
|
||||
Dictionary get_scene_editor_states(int p_idx) const;
|
||||
void set_editor_plugin_states(const Dictionary &p_states);
|
||||
void get_editor_breakpoints(List<String> *p_breakpoints);
|
||||
void clear_editor_states();
|
||||
void save_editor_external_data();
|
||||
void apply_changes_in_editors();
|
||||
|
||||
void add_editor_plugin(EditorPlugin *p_plugin);
|
||||
void remove_editor_plugin(EditorPlugin *p_plugin);
|
||||
|
||||
int get_editor_plugin_count() const;
|
||||
EditorPlugin *get_editor_plugin(int p_idx);
|
||||
|
||||
void add_extension_editor_plugin(const StringName &p_class_name, EditorPlugin *p_plugin);
|
||||
void remove_extension_editor_plugin(const StringName &p_class_name);
|
||||
bool has_extension_editor_plugin(const StringName &p_class_name);
|
||||
EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name);
|
||||
|
||||
void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
|
||||
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
|
||||
const Vector<Callable> get_undo_redo_inspector_hook_callback();
|
||||
|
||||
void add_move_array_element_function(const StringName &p_class, Callable p_callable); // Function should have this signature: void (Object* undo_redo, Object *modified_object, String array_prefix, int element_index, int new_position)
|
||||
void remove_move_array_element_function(const StringName &p_class);
|
||||
Callable get_move_array_element_function(const StringName &p_class) const;
|
||||
|
||||
void save_editor_global_states();
|
||||
|
||||
void add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon);
|
||||
Variant instantiate_custom_type(const String &p_type, const String &p_inherits);
|
||||
void remove_custom_type(const String &p_type);
|
||||
const HashMap<String, Vector<CustomType>> &get_custom_types() const { return custom_types; }
|
||||
const CustomType *get_custom_type_by_name(const String &p_name) const;
|
||||
const CustomType *get_custom_type_by_path(const String &p_path) const;
|
||||
bool is_type_recognized(const String &p_type) const;
|
||||
|
||||
void instantiate_object_properties(Object *p_object);
|
||||
|
||||
int add_edited_scene(int p_at_pos);
|
||||
void move_edited_scene_index(int p_idx, int p_to_idx);
|
||||
void remove_scene(int p_idx);
|
||||
void set_edited_scene(int p_idx);
|
||||
void set_edited_scene_root(Node *p_root);
|
||||
int get_edited_scene() const;
|
||||
int get_edited_scene_from_path(const String &p_path) const;
|
||||
Node *get_edited_scene_root(int p_idx = -1);
|
||||
int get_edited_scene_count() const;
|
||||
Vector<EditedScene> get_edited_scenes() const;
|
||||
|
||||
String get_scene_title(int p_idx, bool p_always_strip_extension = false) const;
|
||||
String get_scene_path(int p_idx) const;
|
||||
String get_scene_type(int p_idx) const;
|
||||
void set_scene_path(int p_idx, const String &p_path);
|
||||
Ref<Script> get_scene_root_script(int p_idx) const;
|
||||
void set_scene_modified_time(int p_idx, uint64_t p_time);
|
||||
uint64_t get_scene_modified_time(int p_idx) const;
|
||||
void clear_edited_scenes();
|
||||
void set_edited_scene_live_edit_root(const NodePath &p_root);
|
||||
NodePath get_edited_scene_live_edit_root();
|
||||
bool check_and_update_scene(int p_idx);
|
||||
void move_edited_scene_to_index(int p_idx);
|
||||
|
||||
bool call_build();
|
||||
|
||||
void set_scene_as_saved(int p_idx);
|
||||
bool is_scene_changed(int p_idx);
|
||||
|
||||
int get_scene_history_id_from_path(const String &p_path) const;
|
||||
int get_current_edited_scene_history_id() const;
|
||||
int get_scene_history_id(int p_idx) const;
|
||||
|
||||
void set_plugin_window_layout(Ref<ConfigFile> p_layout);
|
||||
void get_plugin_window_layout(Ref<ConfigFile> p_layout);
|
||||
|
||||
void save_edited_scene_state(EditorSelection *p_selection, EditorSelectionHistory *p_history, const Dictionary &p_custom);
|
||||
Dictionary restore_edited_scene_state(EditorSelection *p_selection, EditorSelectionHistory *p_history);
|
||||
void notify_edited_scene_changed();
|
||||
void notify_resource_saved(const Ref<Resource> &p_resource);
|
||||
void notify_scene_saved(const String &p_path);
|
||||
|
||||
bool script_class_is_parent(const String &p_class, const String &p_inherits);
|
||||
StringName script_class_get_base(const String &p_class) const;
|
||||
Variant script_class_instance(const String &p_class);
|
||||
|
||||
Ref<Script> script_class_load_script(const String &p_class) const;
|
||||
|
||||
StringName script_class_get_name(const String &p_path) const;
|
||||
void script_class_set_name(const String &p_path, const StringName &p_class);
|
||||
|
||||
String script_class_get_icon_path(const String &p_class) const;
|
||||
void script_class_set_icon_path(const String &p_class, const String &p_icon_path);
|
||||
void script_class_clear_icon_paths() { _script_class_icon_paths.clear(); }
|
||||
void script_class_save_icon_paths();
|
||||
void script_class_load_icon_paths();
|
||||
|
||||
Ref<Texture2D> extension_class_get_icon(const String &p_class) const;
|
||||
|
||||
Ref<Texture2D> get_script_icon(const Ref<Script> &p_script);
|
||||
void clear_script_icon_cache();
|
||||
|
||||
EditorData();
|
||||
~EditorData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores and provides access to the nodes currently selected in the editor.
|
||||
*
|
||||
* This provides a central location for storing "selected" nodes, as a selection can be triggered from multiple places,
|
||||
* such as the SceneTreeDock or a main screen editor plugin (e.g. CanvasItemEditor).
|
||||
*/
|
||||
class EditorSelection : public Object {
|
||||
GDCLASS(EditorSelection, Object);
|
||||
|
||||
// Contains the selected nodes and corresponding metadata.
|
||||
// Metadata objects come from calling _get_editor_data on the editor_plugins, passing the selected node.
|
||||
HashMap<Node *, Object *> selection;
|
||||
|
||||
// Tracks whether the selection change signal has been emitted.
|
||||
// Prevents multiple signals being called in one frame.
|
||||
bool emitted = false;
|
||||
|
||||
bool changed = false;
|
||||
bool node_list_changed = false;
|
||||
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
// Editor plugins which are related to selection.
|
||||
List<Object *> editor_plugins;
|
||||
List<Node *> selected_node_list;
|
||||
|
||||
void _update_node_list();
|
||||
TypedArray<Node> _get_transformable_selected_nodes();
|
||||
void _emit_change();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void add_node(Node *p_node);
|
||||
void remove_node(Node *p_node);
|
||||
bool is_selected(Node *p_node) const;
|
||||
|
||||
template <typename T>
|
||||
T *get_node_editor_data(Node *p_node) {
|
||||
if (!selection.has(p_node)) {
|
||||
return nullptr;
|
||||
}
|
||||
return Object::cast_to<T>(selection[p_node]);
|
||||
}
|
||||
|
||||
// Adds an editor plugin which can provide metadata for selected nodes.
|
||||
void add_editor_plugin(Object *p_object);
|
||||
|
||||
void update();
|
||||
void clear();
|
||||
|
||||
// Returns all the selected nodes.
|
||||
TypedArray<Node> get_selected_nodes();
|
||||
// Returns only the top level selected nodes.
|
||||
// That is, if the selection includes some node and a child of that node, only the parent is returned.
|
||||
List<Node *> &get_selected_node_list();
|
||||
// Returns all the selected nodes (list version of "get_selected_nodes").
|
||||
List<Node *> get_full_selected_node_list();
|
||||
// Returns the map of selected objects and their metadata.
|
||||
HashMap<Node *, Object *> &get_selection() { return selection; }
|
||||
|
||||
EditorSelection();
|
||||
~EditorSelection();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DATA_H
|
||||
1143
engine/editor/editor_dock_manager.cpp
Normal file
1143
engine/editor/editor_dock_manager.cpp
Normal file
File diff suppressed because it is too large
Load diff
211
engine/editor/editor_dock_manager.h
Normal file
211
engine/editor/editor_dock_manager.h
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/**************************************************************************/
|
||||
/* editor_dock_manager.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_DOCK_MANAGER_H
|
||||
#define EDITOR_DOCK_MANAGER_H
|
||||
|
||||
#include "scene/gui/popup.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
|
||||
class Button;
|
||||
class ConfigFile;
|
||||
class Control;
|
||||
class PopupMenu;
|
||||
class TabContainer;
|
||||
class VBoxContainer;
|
||||
class WindowWrapper;
|
||||
|
||||
class DockSplitContainer : public SplitContainer {
|
||||
GDCLASS(DockSplitContainer, SplitContainer);
|
||||
|
||||
private:
|
||||
bool is_updating = false;
|
||||
|
||||
protected:
|
||||
void _update_visibility();
|
||||
|
||||
virtual void add_child_notify(Node *p_child) override;
|
||||
virtual void remove_child_notify(Node *p_child) override;
|
||||
};
|
||||
|
||||
class DockContextPopup;
|
||||
|
||||
class EditorDockManager : public Object {
|
||||
GDCLASS(EditorDockManager, Object);
|
||||
|
||||
public:
|
||||
enum DockSlot {
|
||||
DOCK_SLOT_NONE = -1,
|
||||
DOCK_SLOT_LEFT_UL,
|
||||
DOCK_SLOT_LEFT_BL,
|
||||
DOCK_SLOT_LEFT_UR,
|
||||
DOCK_SLOT_LEFT_BR,
|
||||
DOCK_SLOT_RIGHT_UL,
|
||||
DOCK_SLOT_RIGHT_BL,
|
||||
DOCK_SLOT_RIGHT_UR,
|
||||
DOCK_SLOT_RIGHT_BR,
|
||||
DOCK_SLOT_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
friend class DockContextPopup;
|
||||
|
||||
struct DockInfo {
|
||||
String title;
|
||||
bool open = false;
|
||||
bool enabled = true;
|
||||
bool at_bottom = false;
|
||||
int previous_tab_index = -1;
|
||||
bool previous_at_bottom = false;
|
||||
WindowWrapper *dock_window = nullptr;
|
||||
int dock_slot_index = DOCK_SLOT_NONE;
|
||||
Ref<Shortcut> shortcut;
|
||||
Ref<Texture2D> icon; // Only used when `icon_name` is empty.
|
||||
StringName icon_name;
|
||||
};
|
||||
|
||||
static EditorDockManager *singleton;
|
||||
|
||||
// To access splits easily by index.
|
||||
Vector<DockSplitContainer *> vsplits;
|
||||
Vector<DockSplitContainer *> hsplits;
|
||||
|
||||
Vector<WindowWrapper *> dock_windows;
|
||||
TabContainer *dock_slot[DOCK_SLOT_MAX];
|
||||
HashMap<Control *, DockInfo> all_docks;
|
||||
bool docks_visible = true;
|
||||
|
||||
DockContextPopup *dock_context_popup = nullptr;
|
||||
PopupMenu *docks_menu = nullptr;
|
||||
Vector<Control *> docks_menu_docks;
|
||||
Control *closed_dock_parent = nullptr;
|
||||
|
||||
void _dock_split_dragged(int p_offset);
|
||||
void _dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container);
|
||||
void _bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button);
|
||||
void _dock_container_update_visibility(TabContainer *p_dock_container);
|
||||
void _update_layout();
|
||||
|
||||
void _update_docks_menu();
|
||||
void _docks_menu_option(int p_id);
|
||||
|
||||
void _window_close_request(WindowWrapper *p_wrapper);
|
||||
Control *_close_window(WindowWrapper *p_wrapper);
|
||||
void _open_dock_in_window(Control *p_dock, bool p_show_window = true, bool p_reset_size = false);
|
||||
void _restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump);
|
||||
|
||||
void _dock_move_to_bottom(Control *p_dock, bool p_visible);
|
||||
void _dock_remove_from_bottom(Control *p_dock);
|
||||
bool _is_dock_at_bottom(Control *p_dock);
|
||||
|
||||
void _move_dock_tab_index(Control *p_dock, int p_tab_index, bool p_set_current);
|
||||
void _move_dock(Control *p_dock, Control *p_target, int p_tab_index = -1, bool p_set_current = true);
|
||||
|
||||
void _update_tab_style(Control *p_dock);
|
||||
|
||||
public:
|
||||
static EditorDockManager *get_singleton() { return singleton; }
|
||||
|
||||
void update_tab_styles();
|
||||
void set_tab_icon_max_width(int p_max_width);
|
||||
|
||||
void add_vsplit(DockSplitContainer *p_split);
|
||||
void add_hsplit(DockSplitContainer *p_split);
|
||||
void register_dock_slot(DockSlot p_dock_slot, TabContainer *p_tab_container);
|
||||
int get_vsplit_count() const;
|
||||
PopupMenu *get_docks_menu();
|
||||
|
||||
void save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const;
|
||||
void load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section);
|
||||
|
||||
void set_dock_enabled(Control *p_dock, bool p_enabled);
|
||||
void close_dock(Control *p_dock);
|
||||
void open_dock(Control *p_dock, bool p_set_current = true);
|
||||
void focus_dock(Control *p_dock);
|
||||
|
||||
TabContainer *get_dock_tab_container(Control *p_dock) const;
|
||||
|
||||
void bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock);
|
||||
|
||||
void set_docks_visible(bool p_show);
|
||||
bool are_docks_visible() const;
|
||||
|
||||
void add_dock(Control *p_dock, const String &p_title = "", DockSlot p_slot = DOCK_SLOT_NONE, const Ref<Shortcut> &p_shortcut = nullptr, const StringName &p_icon_name = StringName());
|
||||
void remove_dock(Control *p_dock);
|
||||
|
||||
void set_dock_tab_icon(Control *p_dock, const Ref<Texture2D> &p_icon);
|
||||
|
||||
EditorDockManager();
|
||||
};
|
||||
|
||||
class DockContextPopup : public PopupPanel {
|
||||
GDCLASS(DockContextPopup, PopupPanel);
|
||||
|
||||
VBoxContainer *dock_select_popup_vb = nullptr;
|
||||
|
||||
Button *make_float_button = nullptr;
|
||||
Button *tab_move_left_button = nullptr;
|
||||
Button *tab_move_right_button = nullptr;
|
||||
Button *close_button = nullptr;
|
||||
Button *dock_to_bottom_button = nullptr;
|
||||
|
||||
Control *dock_select = nullptr;
|
||||
Rect2 dock_select_rects[EditorDockManager::DOCK_SLOT_MAX];
|
||||
int dock_select_rect_over_idx = -1;
|
||||
|
||||
Control *context_dock = nullptr;
|
||||
|
||||
EditorDockManager *dock_manager = nullptr;
|
||||
|
||||
void _tab_move_left();
|
||||
void _tab_move_right();
|
||||
void _close_dock();
|
||||
void _float_dock();
|
||||
void _move_dock_to_bottom();
|
||||
|
||||
void _dock_select_input(const Ref<InputEvent> &p_input);
|
||||
void _dock_select_mouse_exited();
|
||||
void _dock_select_draw();
|
||||
|
||||
void _update_buttons();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void select_current_dock_in_dock_slot(int p_dock_slot);
|
||||
void set_dock(Control *p_dock);
|
||||
Control *get_dock() const;
|
||||
void docks_updated();
|
||||
|
||||
DockContextPopup();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DOCK_MANAGER_H
|
||||
1064
engine/editor/editor_feature_profile.cpp
Normal file
1064
engine/editor/editor_feature_profile.cpp
Normal file
File diff suppressed because it is too large
Load diff
190
engine/editor/editor_feature_profile.h
Normal file
190
engine/editor/editor_feature_profile.h
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/**************************************************************************/
|
||||
/* editor_feature_profile.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_FEATURE_PROFILE_H
|
||||
#define EDITOR_FEATURE_PROFILE_H
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "editor/editor_help.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorFileDialog;
|
||||
|
||||
class EditorFeatureProfile : public RefCounted {
|
||||
GDCLASS(EditorFeatureProfile, RefCounted);
|
||||
|
||||
public:
|
||||
enum Feature {
|
||||
FEATURE_3D,
|
||||
FEATURE_SCRIPT,
|
||||
FEATURE_ASSET_LIB,
|
||||
FEATURE_SCENE_TREE,
|
||||
FEATURE_NODE_DOCK,
|
||||
FEATURE_FILESYSTEM_DOCK,
|
||||
FEATURE_IMPORT_DOCK,
|
||||
FEATURE_HISTORY_DOCK,
|
||||
FEATURE_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
HashSet<StringName> disabled_classes;
|
||||
HashSet<StringName> disabled_editors;
|
||||
HashMap<StringName, HashSet<StringName>> disabled_properties;
|
||||
|
||||
HashSet<StringName> collapsed_classes;
|
||||
|
||||
bool features_disabled[FEATURE_MAX];
|
||||
static const char *feature_names[FEATURE_MAX];
|
||||
static const char *feature_descriptions[FEATURE_MAX];
|
||||
static const char *feature_identifiers[FEATURE_MAX];
|
||||
|
||||
String _get_feature_name(Feature p_feature) { return get_feature_name(p_feature); }
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_disable_class(const StringName &p_class, bool p_disabled);
|
||||
bool is_class_disabled(const StringName &p_class) const;
|
||||
|
||||
void set_disable_class_editor(const StringName &p_class, bool p_disabled);
|
||||
bool is_class_editor_disabled(const StringName &p_class) const;
|
||||
|
||||
void set_disable_class_property(const StringName &p_class, const StringName &p_property, bool p_disabled);
|
||||
bool is_class_property_disabled(const StringName &p_class, const StringName &p_property) const;
|
||||
bool has_class_properties_disabled(const StringName &p_class) const;
|
||||
|
||||
void set_item_collapsed(const StringName &p_class, bool p_collapsed);
|
||||
bool is_item_collapsed(const StringName &p_class) const;
|
||||
|
||||
void set_disable_feature(Feature p_feature, bool p_disable);
|
||||
bool is_feature_disabled(Feature p_feature) const;
|
||||
|
||||
Error save_to_file(const String &p_path);
|
||||
Error load_from_file(const String &p_path);
|
||||
|
||||
static String get_feature_name(Feature p_feature);
|
||||
static String get_feature_description(Feature p_feature);
|
||||
|
||||
EditorFeatureProfile();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorFeatureProfile::Feature)
|
||||
|
||||
class EditorFeatureProfileManager : public AcceptDialog {
|
||||
GDCLASS(EditorFeatureProfileManager, AcceptDialog);
|
||||
|
||||
enum Action {
|
||||
PROFILE_CLEAR,
|
||||
PROFILE_SET,
|
||||
PROFILE_IMPORT,
|
||||
PROFILE_EXPORT,
|
||||
PROFILE_NEW,
|
||||
PROFILE_ERASE,
|
||||
PROFILE_MAX
|
||||
};
|
||||
|
||||
enum ClassOptions {
|
||||
CLASS_OPTION_DISABLE_EDITOR
|
||||
};
|
||||
|
||||
ConfirmationDialog *erase_profile_dialog = nullptr;
|
||||
ConfirmationDialog *new_profile_dialog = nullptr;
|
||||
LineEdit *new_profile_name = nullptr;
|
||||
|
||||
LineEdit *current_profile_name = nullptr;
|
||||
OptionButton *profile_list = nullptr;
|
||||
Button *profile_actions[PROFILE_MAX];
|
||||
|
||||
HSplitContainer *h_split = nullptr;
|
||||
|
||||
VBoxContainer *class_list_vbc = nullptr;
|
||||
Tree *class_list = nullptr;
|
||||
VBoxContainer *property_list_vbc = nullptr;
|
||||
Tree *property_list = nullptr;
|
||||
EditorHelpBit *description_bit = nullptr;
|
||||
Label *no_profile_selected_help = nullptr;
|
||||
|
||||
EditorFileDialog *import_profiles = nullptr;
|
||||
EditorFileDialog *export_profile = nullptr;
|
||||
|
||||
void _profile_action(int p_action);
|
||||
void _profile_selected(int p_what);
|
||||
void _hide_requested();
|
||||
|
||||
String current_profile;
|
||||
void _update_profile_list(const String &p_select_profile = String());
|
||||
void _update_selected_profile();
|
||||
void _update_profile_tree_from(TreeItem *p_edited);
|
||||
void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected, int p_class_insert_index = -1);
|
||||
|
||||
Ref<EditorFeatureProfile> current;
|
||||
Ref<EditorFeatureProfile> edited;
|
||||
|
||||
void _erase_selected_profile();
|
||||
void _create_new_profile();
|
||||
String _get_selected_profile();
|
||||
|
||||
void _import_profiles(const Vector<String> &p_paths);
|
||||
void _export_profile(const String &p_path);
|
||||
|
||||
bool updating_features = false;
|
||||
|
||||
void _class_list_item_selected();
|
||||
void _class_list_item_edited();
|
||||
void _class_list_item_collapsed(Object *p_item);
|
||||
void _property_item_edited();
|
||||
void _save_and_update();
|
||||
|
||||
Timer *update_timer = nullptr;
|
||||
void _emit_current_profile_changed();
|
||||
|
||||
static EditorFeatureProfileManager *singleton;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
Ref<EditorFeatureProfile> get_current_profile();
|
||||
String get_current_profile_name() const;
|
||||
void set_current_profile(const String &p_profile_name, bool p_validate_profile);
|
||||
void notify_changed();
|
||||
|
||||
static EditorFeatureProfileManager *get_singleton() { return singleton; }
|
||||
EditorFeatureProfileManager();
|
||||
};
|
||||
|
||||
#endif // EDITOR_FEATURE_PROFILE_H
|
||||
3068
engine/editor/editor_file_system.cpp
Normal file
3068
engine/editor/editor_file_system.cpp
Normal file
File diff suppressed because it is too large
Load diff
370
engine/editor/editor_file_system.h
Normal file
370
engine/editor/editor_file_system.h
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
/**************************************************************************/
|
||||
/* editor_file_system.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_FILE_SYSTEM_H
|
||||
#define EDITOR_FILE_SYSTEM_H
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class FileAccess;
|
||||
|
||||
struct EditorProgressBG;
|
||||
class EditorFileSystemDirectory : public Object {
|
||||
GDCLASS(EditorFileSystemDirectory, Object);
|
||||
|
||||
String name;
|
||||
uint64_t modified_time;
|
||||
bool verified = false; //used for checking changes
|
||||
|
||||
EditorFileSystemDirectory *parent = nullptr;
|
||||
Vector<EditorFileSystemDirectory *> subdirs;
|
||||
|
||||
struct FileInfo {
|
||||
String file;
|
||||
StringName type;
|
||||
StringName resource_script_class; // If any resource has script with a global class name, its found here.
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
uint64_t modified_time = 0;
|
||||
uint64_t import_modified_time = 0;
|
||||
bool import_valid = false;
|
||||
String import_group_file;
|
||||
Vector<String> deps;
|
||||
bool verified = false; //used for checking changes
|
||||
// These are for script resources only.
|
||||
String script_class_name;
|
||||
String script_class_extends;
|
||||
String script_class_icon_path;
|
||||
String icon_path;
|
||||
};
|
||||
|
||||
Vector<FileInfo *> files;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
friend class EditorFileSystem;
|
||||
|
||||
public:
|
||||
String get_name();
|
||||
String get_path() const;
|
||||
|
||||
int get_subdir_count() const;
|
||||
EditorFileSystemDirectory *get_subdir(int p_idx);
|
||||
int get_file_count() const;
|
||||
String get_file(int p_idx) const;
|
||||
String get_file_path(int p_idx) const;
|
||||
StringName get_file_type(int p_idx) const;
|
||||
StringName get_file_resource_script_class(int p_idx) const;
|
||||
Vector<String> get_file_deps(int p_idx) const;
|
||||
bool get_file_import_is_valid(int p_idx) const;
|
||||
uint64_t get_file_modified_time(int p_idx) const;
|
||||
uint64_t get_file_import_modified_time(int p_idx) const;
|
||||
String get_file_script_class_name(int p_idx) const; //used for scripts
|
||||
String get_file_script_class_extends(int p_idx) const; //used for scripts
|
||||
String get_file_script_class_icon_path(int p_idx) const; //used for scripts
|
||||
String get_file_icon_path(int p_idx) const; //used for FileSystemDock
|
||||
|
||||
EditorFileSystemDirectory *get_parent();
|
||||
|
||||
int find_file_index(const String &p_file) const;
|
||||
int find_dir_index(const String &p_dir) const;
|
||||
|
||||
void force_update();
|
||||
|
||||
EditorFileSystemDirectory();
|
||||
~EditorFileSystemDirectory();
|
||||
};
|
||||
|
||||
class EditorFileSystemImportFormatSupportQuery : public RefCounted {
|
||||
GDCLASS(EditorFileSystemImportFormatSupportQuery, RefCounted);
|
||||
|
||||
protected:
|
||||
GDVIRTUAL0RC(bool, _is_active)
|
||||
GDVIRTUAL0RC(Vector<String>, _get_file_extensions)
|
||||
GDVIRTUAL0RC(bool, _query)
|
||||
static void _bind_methods() {
|
||||
GDVIRTUAL_BIND(_is_active);
|
||||
GDVIRTUAL_BIND(_get_file_extensions);
|
||||
GDVIRTUAL_BIND(_query);
|
||||
}
|
||||
|
||||
public:
|
||||
virtual bool is_active() const {
|
||||
bool ret = false;
|
||||
GDVIRTUAL_REQUIRED_CALL(_is_active, ret);
|
||||
return ret;
|
||||
}
|
||||
virtual Vector<String> get_file_extensions() const {
|
||||
Vector<String> ret;
|
||||
GDVIRTUAL_REQUIRED_CALL(_get_file_extensions, ret);
|
||||
return ret;
|
||||
}
|
||||
virtual bool query() {
|
||||
bool ret = false;
|
||||
GDVIRTUAL_REQUIRED_CALL(_query, ret);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
class EditorFileSystem : public Node {
|
||||
GDCLASS(EditorFileSystem, Node);
|
||||
|
||||
_THREAD_SAFE_CLASS_
|
||||
|
||||
struct ItemAction {
|
||||
enum Action {
|
||||
ACTION_NONE,
|
||||
ACTION_DIR_ADD,
|
||||
ACTION_DIR_REMOVE,
|
||||
ACTION_FILE_ADD,
|
||||
ACTION_FILE_REMOVE,
|
||||
ACTION_FILE_TEST_REIMPORT,
|
||||
ACTION_FILE_RELOAD
|
||||
};
|
||||
|
||||
Action action = ACTION_NONE;
|
||||
EditorFileSystemDirectory *dir = nullptr;
|
||||
String file;
|
||||
EditorFileSystemDirectory *new_dir = nullptr;
|
||||
EditorFileSystemDirectory::FileInfo *new_file = nullptr;
|
||||
};
|
||||
|
||||
struct ScannedDirectory {
|
||||
String name;
|
||||
String full_path;
|
||||
Vector<ScannedDirectory *> subdirs;
|
||||
List<String> files;
|
||||
|
||||
~ScannedDirectory();
|
||||
};
|
||||
|
||||
bool use_threads = false;
|
||||
Thread thread;
|
||||
static void _thread_func(void *_userdata);
|
||||
|
||||
EditorFileSystemDirectory *new_filesystem = nullptr;
|
||||
ScannedDirectory *first_scan_root_dir = nullptr;
|
||||
|
||||
bool scanning = false;
|
||||
bool importing = false;
|
||||
bool first_scan = true;
|
||||
bool scan_changes_pending = false;
|
||||
float scan_total;
|
||||
String filesystem_settings_version_for_import;
|
||||
bool revalidate_import_files = false;
|
||||
int nb_files_total = 0;
|
||||
|
||||
void _scan_filesystem();
|
||||
void _first_scan_filesystem();
|
||||
void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names);
|
||||
|
||||
HashSet<String> late_update_files;
|
||||
|
||||
void _save_late_updated_files();
|
||||
|
||||
EditorFileSystemDirectory *filesystem = nullptr;
|
||||
|
||||
static EditorFileSystem *singleton;
|
||||
|
||||
/* Used for reading the filesystem cache file */
|
||||
struct FileCache {
|
||||
String type;
|
||||
String resource_script_class;
|
||||
ResourceUID::ID uid = ResourceUID::INVALID_ID;
|
||||
uint64_t modification_time = 0;
|
||||
uint64_t import_modification_time = 0;
|
||||
Vector<String> deps;
|
||||
bool import_valid = false;
|
||||
String import_group_file;
|
||||
String script_class_name;
|
||||
String script_class_extends;
|
||||
String script_class_icon_path;
|
||||
};
|
||||
|
||||
HashMap<String, FileCache> file_cache;
|
||||
HashSet<String> dep_update_list;
|
||||
|
||||
struct ScanProgress {
|
||||
float hi = 0;
|
||||
int current = 0;
|
||||
EditorProgressBG *progress = nullptr;
|
||||
void increment();
|
||||
};
|
||||
|
||||
void _save_filesystem_cache();
|
||||
void _save_filesystem_cache(EditorFileSystemDirectory *p_dir, Ref<FileAccess> p_file);
|
||||
|
||||
bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const;
|
||||
|
||||
void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
|
||||
|
||||
void _delete_internal_files(const String &p_file);
|
||||
int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
|
||||
|
||||
HashSet<String> textfile_extensions;
|
||||
HashSet<String> valid_extensions;
|
||||
HashSet<String> import_extensions;
|
||||
|
||||
int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da);
|
||||
void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
|
||||
|
||||
Thread thread_sources;
|
||||
bool scanning_changes = false;
|
||||
SafeFlag scanning_changes_done;
|
||||
|
||||
static void _thread_func_sources(void *_userdata);
|
||||
|
||||
List<String> sources_changed;
|
||||
List<ItemAction> scan_actions;
|
||||
|
||||
bool _update_scan_actions();
|
||||
|
||||
void _update_extensions();
|
||||
|
||||
Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr);
|
||||
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
|
||||
|
||||
bool _test_for_reimport(const String &p_path, bool p_only_imported_files);
|
||||
|
||||
bool reimport_on_missing_imported_files;
|
||||
|
||||
Vector<String> _get_dependencies(const String &p_path);
|
||||
|
||||
struct ImportFile {
|
||||
String path;
|
||||
String importer;
|
||||
bool threaded = false;
|
||||
int order = 0;
|
||||
bool operator<(const ImportFile &p_if) const {
|
||||
return order == p_if.order ? (importer < p_if.importer) : (order < p_if.order);
|
||||
}
|
||||
};
|
||||
|
||||
struct ScriptInfo {
|
||||
String type;
|
||||
String script_class_name;
|
||||
String script_class_extends;
|
||||
String script_class_icon_path;
|
||||
};
|
||||
|
||||
Mutex update_script_mutex;
|
||||
HashMap<String, ScriptInfo> update_script_paths;
|
||||
HashSet<String> update_script_paths_documentation;
|
||||
void _queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path);
|
||||
void _update_script_classes();
|
||||
void _update_script_documentation();
|
||||
void _process_update_pending();
|
||||
|
||||
Mutex update_scene_mutex;
|
||||
HashSet<String> update_scene_paths;
|
||||
void _queue_update_scene_groups(const String &p_path);
|
||||
void _update_scene_groups();
|
||||
void _update_pending_scene_groups();
|
||||
void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
|
||||
|
||||
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
|
||||
|
||||
static Error _resource_import(const String &p_path);
|
||||
|
||||
bool using_fat32_or_exfat; // Workaround for projects in FAT32 or exFAT filesystem (pendrives, most of the time)
|
||||
|
||||
void _find_group_files(EditorFileSystemDirectory *efd, HashMap<String, Vector<String>> &group_files, HashSet<String> &groups_to_reimport);
|
||||
|
||||
void _move_group_files(EditorFileSystemDirectory *efd, const String &p_group_file, const String &p_new_location);
|
||||
|
||||
HashSet<String> group_file_cache;
|
||||
HashMap<String, String> file_icon_cache;
|
||||
|
||||
struct ImportThreadData {
|
||||
const ImportFile *reimport_files;
|
||||
int reimport_from;
|
||||
SafeNumeric<int> max_index;
|
||||
};
|
||||
|
||||
void _reimport_thread(uint32_t p_index, ImportThreadData *p_import_data);
|
||||
|
||||
static ResourceUID::ID _resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate);
|
||||
|
||||
bool _scan_extensions();
|
||||
bool _scan_import_support(const Vector<String> &reimports);
|
||||
|
||||
Vector<Ref<EditorFileSystemImportFormatSupportQuery>> import_support_queries;
|
||||
|
||||
void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
|
||||
void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
|
||||
void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
|
||||
String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info);
|
||||
|
||||
void _register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorFileSystem *get_singleton() { return singleton; }
|
||||
|
||||
EditorFileSystemDirectory *get_filesystem();
|
||||
bool is_scanning() const;
|
||||
bool is_importing() const { return importing; }
|
||||
bool doing_first_scan() const { return first_scan; }
|
||||
float get_scanning_progress() const;
|
||||
void scan();
|
||||
void scan_changes();
|
||||
void update_file(const String &p_file);
|
||||
void update_files(const Vector<String> &p_script_paths);
|
||||
HashSet<String> get_valid_extensions() const;
|
||||
void register_global_class_script(const String &p_search_path, const String &p_target_path);
|
||||
|
||||
EditorFileSystemDirectory *get_filesystem_path(const String &p_path);
|
||||
String get_file_type(const String &p_file) const;
|
||||
EditorFileSystemDirectory *find_file(const String &p_file, int *r_index) const;
|
||||
|
||||
void reimport_files(const Vector<String> &p_files);
|
||||
Error reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters);
|
||||
|
||||
void reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params);
|
||||
|
||||
bool is_group_file(const String &p_path) const;
|
||||
void move_group_file(const String &p_path, const String &p_new_path);
|
||||
|
||||
static bool _should_skip_directory(const String &p_path);
|
||||
|
||||
void add_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query);
|
||||
void remove_import_format_support_query(Ref<EditorFileSystemImportFormatSupportQuery> p_query);
|
||||
EditorFileSystem();
|
||||
~EditorFileSystem();
|
||||
};
|
||||
|
||||
#endif // EDITOR_FILE_SYSTEM_H
|
||||
299
engine/editor/editor_folding.cpp
Normal file
299
engine/editor/editor_folding.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/**************************************************************************/
|
||||
/* editor_folding.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_folding.h"
|
||||
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/editor_paths.h"
|
||||
|
||||
Vector<String> EditorFolding::_get_unfolds(const Object *p_object) {
|
||||
Vector<String> sections;
|
||||
sections.resize(p_object->editor_get_section_folding().size());
|
||||
if (sections.size()) {
|
||||
String *w = sections.ptrw();
|
||||
int idx = 0;
|
||||
for (const String &E : p_object->editor_get_section_folding()) {
|
||||
w[idx++] = E;
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
void EditorFolding::save_resource_folding(const Ref<Resource> &p_resource, const String &p_path) {
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
Vector<String> unfolds = _get_unfolds(p_resource.ptr());
|
||||
config->set_value("folding", "sections_unfolded", unfolds);
|
||||
|
||||
String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg";
|
||||
file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file);
|
||||
config->save(file);
|
||||
}
|
||||
|
||||
void EditorFolding::_set_unfolds(Object *p_object, const Vector<String> &p_unfolds) {
|
||||
int uc = p_unfolds.size();
|
||||
const String *r = p_unfolds.ptr();
|
||||
p_object->editor_clear_section_folding();
|
||||
for (int i = 0; i < uc; i++) {
|
||||
p_object->editor_set_section_unfold(r[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFolding::load_resource_folding(Ref<Resource> p_resource, const String &p_path) {
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg";
|
||||
file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file);
|
||||
|
||||
if (config->load(file) != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<String> unfolds;
|
||||
|
||||
if (config->has_section_key("folding", "sections_unfolded")) {
|
||||
unfolds = config->get_value("folding", "sections_unfolded");
|
||||
}
|
||||
_set_unfolds(p_resource.ptr(), unfolds);
|
||||
}
|
||||
|
||||
void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet<Ref<Resource>> &resources) {
|
||||
if (p_root != p_node) {
|
||||
if (!p_node->get_owner()) {
|
||||
return; //not owned, bye
|
||||
}
|
||||
if (p_node->get_owner() != p_root && !p_root->is_editable_instance(p_node)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_node->is_displayed_folded()) {
|
||||
nodes_folded.push_back(p_root->get_path_to(p_node));
|
||||
}
|
||||
Vector<String> unfolds = _get_unfolds(p_node);
|
||||
|
||||
if (unfolds.size()) {
|
||||
p_folds.push_back(p_root->get_path_to(p_node));
|
||||
p_folds.push_back(unfolds);
|
||||
}
|
||||
|
||||
List<PropertyInfo> plist;
|
||||
p_node->get_property_list(&plist);
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (E.usage & PROPERTY_USAGE_EDITOR) {
|
||||
if (E.type == Variant::OBJECT) {
|
||||
Ref<Resource> res = p_node->get(E.name);
|
||||
if (res.is_valid() && !resources.has(res) && !res->get_path().is_empty() && !res->get_path().is_resource_file()) {
|
||||
Vector<String> res_unfolds = _get_unfolds(res.ptr());
|
||||
resource_folds.push_back(res->get_path());
|
||||
resource_folds.push_back(res_unfolds);
|
||||
resources.insert(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||
_fill_folds(p_root, p_node->get_child(i), p_folds, resource_folds, nodes_folded, resources);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFolding::save_scene_folding(const Node *p_scene, const String &p_path) {
|
||||
ERR_FAIL_NULL(p_scene);
|
||||
|
||||
Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
|
||||
if (!file_check->file_exists(p_path)) { //This can happen when creating scene from FilesystemDock. It has path, but no file.
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
Array unfolds, res_unfolds;
|
||||
HashSet<Ref<Resource>> resources;
|
||||
Array nodes_folded;
|
||||
_fill_folds(p_scene, p_scene, unfolds, res_unfolds, nodes_folded, resources);
|
||||
|
||||
config->set_value("folding", "node_unfolds", unfolds);
|
||||
config->set_value("folding", "resource_unfolds", res_unfolds);
|
||||
config->set_value("folding", "nodes_folded", nodes_folded);
|
||||
|
||||
String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg";
|
||||
file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file);
|
||||
config->save(file);
|
||||
}
|
||||
|
||||
void EditorFolding::load_scene_folding(Node *p_scene, const String &p_path) {
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
String path = EditorPaths::get_singleton()->get_project_settings_dir();
|
||||
String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg";
|
||||
file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file);
|
||||
|
||||
if (config->load(file) != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array unfolds;
|
||||
if (config->has_section_key("folding", "node_unfolds")) {
|
||||
unfolds = config->get_value("folding", "node_unfolds");
|
||||
}
|
||||
Array res_unfolds;
|
||||
if (config->has_section_key("folding", "resource_unfolds")) {
|
||||
res_unfolds = config->get_value("folding", "resource_unfolds");
|
||||
}
|
||||
Array nodes_folded;
|
||||
if (config->has_section_key("folding", "nodes_folded")) {
|
||||
nodes_folded = config->get_value("folding", "nodes_folded");
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(unfolds.size() & 1);
|
||||
ERR_FAIL_COND(res_unfolds.size() & 1);
|
||||
|
||||
for (int i = 0; i < unfolds.size(); i += 2) {
|
||||
NodePath path2 = unfolds[i];
|
||||
Vector<String> un = unfolds[i + 1];
|
||||
Node *node = p_scene->get_node_or_null(path2);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
_set_unfolds(node, un);
|
||||
}
|
||||
|
||||
for (int i = 0; i < res_unfolds.size(); i += 2) {
|
||||
String path2 = res_unfolds[i];
|
||||
Ref<Resource> res = ResourceCache::get_ref(path2);
|
||||
if (res.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<String> unfolds2 = res_unfolds[i + 1];
|
||||
_set_unfolds(res.ptr(), unfolds2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nodes_folded.size(); i++) {
|
||||
NodePath fold_path = nodes_folded[i];
|
||||
if (p_scene->has_node(fold_path)) {
|
||||
Node *node = p_scene->get_node(fold_path);
|
||||
node->set_display_folded(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorFolding::has_folding_data(const String &p_path) {
|
||||
String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg";
|
||||
file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file);
|
||||
return FileAccess::exists(file);
|
||||
}
|
||||
|
||||
void EditorFolding::_do_object_unfolds(Object *p_object, HashSet<Ref<Resource>> &resources) {
|
||||
List<PropertyInfo> plist;
|
||||
p_object->get_property_list(&plist);
|
||||
String group_base;
|
||||
String group;
|
||||
|
||||
HashSet<String> unfold_group;
|
||||
|
||||
for (const PropertyInfo &E : plist) {
|
||||
if (E.usage & PROPERTY_USAGE_CATEGORY) {
|
||||
group = "";
|
||||
group_base = "";
|
||||
}
|
||||
if (E.usage & PROPERTY_USAGE_GROUP) {
|
||||
group = E.name;
|
||||
group_base = E.hint_string;
|
||||
if (group_base.ends_with("_")) {
|
||||
group_base = group_base.substr(0, group_base.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//can unfold
|
||||
if (E.usage & PROPERTY_USAGE_EDITOR) {
|
||||
if (!group.is_empty()) { //group
|
||||
if (group_base.is_empty() || E.name.begins_with(group_base)) {
|
||||
bool can_revert = EditorPropertyRevert::can_property_revert(p_object, E.name);
|
||||
if (can_revert) {
|
||||
unfold_group.insert(group);
|
||||
}
|
||||
}
|
||||
} else { //path
|
||||
int last = E.name.rfind("/");
|
||||
if (last != -1) {
|
||||
bool can_revert = EditorPropertyRevert::can_property_revert(p_object, E.name);
|
||||
if (can_revert) {
|
||||
unfold_group.insert(E.name.substr(0, last));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (E.type == Variant::OBJECT) {
|
||||
Ref<Resource> res = p_object->get(E.name);
|
||||
if (res.is_valid() && !resources.has(res) && !res->get_path().is_empty() && !res->get_path().is_resource_file()) {
|
||||
resources.insert(res);
|
||||
_do_object_unfolds(res.ptr(), resources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const String &E : unfold_group) {
|
||||
p_object->editor_set_section_unfold(E, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFolding::_do_node_unfolds(Node *p_root, Node *p_node, HashSet<Ref<Resource>> &resources) {
|
||||
if (p_root != p_node) {
|
||||
if (!p_node->get_owner()) {
|
||||
return; //not owned, bye
|
||||
}
|
||||
if (p_node->get_owner() != p_root && !p_root->is_editable_instance(p_node)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_do_object_unfolds(p_node, resources);
|
||||
|
||||
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||
_do_node_unfolds(p_root, p_node->get_child(i), resources);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFolding::unfold_scene(Node *p_scene) {
|
||||
HashSet<Ref<Resource>> resources;
|
||||
_do_node_unfolds(p_scene, p_scene, resources);
|
||||
}
|
||||
|
||||
EditorFolding::EditorFolding() {
|
||||
}
|
||||
59
engine/editor/editor_folding.h
Normal file
59
engine/editor/editor_folding.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**************************************************************************/
|
||||
/* editor_folding.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_FOLDING_H
|
||||
#define EDITOR_FOLDING_H
|
||||
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class EditorFolding {
|
||||
Vector<String> _get_unfolds(const Object *p_object);
|
||||
void _set_unfolds(Object *p_object, const Vector<String> &p_unfolds);
|
||||
|
||||
void _fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet<Ref<Resource>> &resources);
|
||||
|
||||
void _do_object_unfolds(Object *p_object, HashSet<Ref<Resource>> &resources);
|
||||
void _do_node_unfolds(Node *p_root, Node *p_node, HashSet<Ref<Resource>> &resources);
|
||||
|
||||
public:
|
||||
void save_resource_folding(const Ref<Resource> &p_resource, const String &p_path);
|
||||
void load_resource_folding(Ref<Resource> p_resource, const String &p_path);
|
||||
|
||||
void save_scene_folding(const Node *p_scene, const String &p_path);
|
||||
void load_scene_folding(Node *p_scene, const String &p_path);
|
||||
|
||||
void unfold_scene(Node *p_scene);
|
||||
|
||||
bool has_folding_data(const String &p_path);
|
||||
|
||||
EditorFolding();
|
||||
};
|
||||
|
||||
#endif // EDITOR_FOLDING_H
|
||||
4214
engine/editor/editor_help.cpp
Normal file
4214
engine/editor/editor_help.cpp
Normal file
File diff suppressed because it is too large
Load diff
385
engine/editor/editor_help.h
Normal file
385
engine/editor/editor_help.h
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
/**************************************************************************/
|
||||
/* editor_help.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_HELP_H
|
||||
#define EDITOR_HELP_H
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "editor/code_editor.h"
|
||||
#include "editor/doc_tools.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/popup.h"
|
||||
#include "scene/gui/rich_text_label.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/gui/text_edit.h"
|
||||
#include "scene/main/timer.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For gdscript, mono.
|
||||
|
||||
class FindBar : public HBoxContainer {
|
||||
GDCLASS(FindBar, HBoxContainer);
|
||||
|
||||
LineEdit *search_text = nullptr;
|
||||
Button *find_prev = nullptr;
|
||||
Button *find_next = nullptr;
|
||||
Label *matches_label = nullptr;
|
||||
TextureButton *hide_button = nullptr;
|
||||
String prev_search;
|
||||
|
||||
RichTextLabel *rich_text_label = nullptr;
|
||||
|
||||
int results_count = 0;
|
||||
|
||||
void _hide_bar();
|
||||
|
||||
void _search_text_changed(const String &p_text);
|
||||
void _search_text_submitted(const String &p_text);
|
||||
|
||||
void _update_results_count();
|
||||
void _update_matches_label();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void unhandled_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
bool _search(bool p_search_previous = false);
|
||||
|
||||
public:
|
||||
void set_rich_text_label(RichTextLabel *p_rich_text_label);
|
||||
|
||||
void popup_search();
|
||||
|
||||
bool search_prev();
|
||||
bool search_next();
|
||||
|
||||
FindBar();
|
||||
};
|
||||
|
||||
class EditorHelp : public VBoxContainer {
|
||||
GDCLASS(EditorHelp, VBoxContainer);
|
||||
|
||||
enum MethodType {
|
||||
METHOD_TYPE_METHOD,
|
||||
METHOD_TYPE_CONSTRUCTOR,
|
||||
METHOD_TYPE_OPERATOR,
|
||||
METHOD_TYPE_MAX
|
||||
};
|
||||
|
||||
bool select_locked = false;
|
||||
|
||||
String prev_search;
|
||||
|
||||
String edited_class;
|
||||
|
||||
Vector<Pair<String, int>> section_line;
|
||||
HashMap<String, int> method_line;
|
||||
HashMap<String, int> signal_line;
|
||||
HashMap<String, int> property_line;
|
||||
HashMap<String, int> theme_property_line;
|
||||
HashMap<String, int> constant_line;
|
||||
HashMap<String, int> annotation_line;
|
||||
HashMap<String, int> enum_line;
|
||||
HashMap<String, HashMap<String, int>> enum_values_line;
|
||||
int description_line = 0;
|
||||
|
||||
RichTextLabel *class_desc = nullptr;
|
||||
HSplitContainer *h_split = nullptr;
|
||||
static DocTools *doc;
|
||||
static DocTools *ext_doc;
|
||||
|
||||
ConfirmationDialog *search_dialog = nullptr;
|
||||
LineEdit *search = nullptr;
|
||||
FindBar *find_bar = nullptr;
|
||||
HBoxContainer *status_bar = nullptr;
|
||||
Button *toggle_scripts_button = nullptr;
|
||||
|
||||
String base_path;
|
||||
|
||||
struct ThemeCache {
|
||||
Ref<StyleBox> background_style;
|
||||
|
||||
Color text_color;
|
||||
Color title_color;
|
||||
Color headline_color;
|
||||
Color comment_color;
|
||||
Color symbol_color;
|
||||
Color value_color;
|
||||
Color qualifier_color;
|
||||
Color type_color;
|
||||
Color override_color;
|
||||
|
||||
Ref<Font> doc_font;
|
||||
Ref<Font> doc_bold_font;
|
||||
Ref<Font> doc_italic_font;
|
||||
Ref<Font> doc_title_font;
|
||||
Ref<Font> doc_code_font;
|
||||
Ref<Font> doc_kbd_font;
|
||||
|
||||
int doc_font_size = 0;
|
||||
int doc_title_font_size = 0;
|
||||
int doc_code_font_size = 0;
|
||||
int doc_kbd_font_size = 0;
|
||||
} theme_cache;
|
||||
|
||||
int scroll_to = -1;
|
||||
|
||||
void _help_callback(const String &p_topic);
|
||||
|
||||
void _add_text(const String &p_bbcode);
|
||||
bool scroll_locked = false;
|
||||
|
||||
//void _button_pressed(int p_idx);
|
||||
void _add_type(const String &p_type, const String &p_enum = String(), bool p_is_bitfield = false);
|
||||
void _add_type_icon(const String &p_type, int p_size = 0, const String &p_fallback = "");
|
||||
void _add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override = true);
|
||||
|
||||
void _add_bulletpoint();
|
||||
|
||||
void _push_normal_font();
|
||||
void _pop_normal_font();
|
||||
void _push_title_font();
|
||||
void _pop_title_font();
|
||||
void _push_code_font();
|
||||
void _pop_code_font();
|
||||
|
||||
void _class_desc_finished();
|
||||
void _class_list_select(const String &p_select);
|
||||
void _class_desc_select(const String &p_select);
|
||||
void _class_desc_input(const Ref<InputEvent> &p_input);
|
||||
void _class_desc_resized(bool p_force_update_theme);
|
||||
int display_margin = 0;
|
||||
|
||||
Error _goto_desc(const String &p_class);
|
||||
//void _update_history_buttons();
|
||||
void _update_method_list(MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods);
|
||||
void _update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods);
|
||||
void _update_doc();
|
||||
|
||||
void _request_help(const String &p_string);
|
||||
void _search(bool p_search_previous = false);
|
||||
|
||||
String _fix_constant(const String &p_constant) const;
|
||||
void _toggle_scripts_pressed();
|
||||
|
||||
static int doc_generation_count;
|
||||
static String doc_version_hash;
|
||||
static Thread worker_thread;
|
||||
|
||||
static void _wait_for_thread();
|
||||
static void _load_doc_thread(void *p_udata);
|
||||
static void _gen_doc_thread(void *p_udata);
|
||||
static void _gen_extensions_docs();
|
||||
static void _compute_doc_version_hash();
|
||||
|
||||
struct PropertyCompare {
|
||||
_FORCE_INLINE_ bool operator()(const DocData::PropertyDoc &p_l, const DocData::PropertyDoc &p_r) const {
|
||||
// Sort overridden properties above all else.
|
||||
if (p_l.overridden == p_r.overridden) {
|
||||
return p_l.name.naturalcasecmp_to(p_r.name) < 0;
|
||||
}
|
||||
return p_l.overridden;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual void _update_theme_item_cache() override;
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static void generate_doc(bool p_use_cache = true);
|
||||
static DocTools *get_doc_data();
|
||||
static void cleanup_doc();
|
||||
static String get_cache_full_path();
|
||||
|
||||
static void load_xml_buffer(const uint8_t *p_buffer, int p_size);
|
||||
static void remove_class(const String &p_class);
|
||||
|
||||
void go_to_help(const String &p_help);
|
||||
void go_to_class(const String &p_class);
|
||||
void update_doc();
|
||||
|
||||
Vector<Pair<String, int>> get_sections();
|
||||
void scroll_to_section(int p_section_index);
|
||||
|
||||
void popup_search();
|
||||
void search_again(bool p_search_previous = false);
|
||||
|
||||
String get_class();
|
||||
|
||||
void set_focused() { class_desc->grab_focus(); }
|
||||
|
||||
int get_scroll() const;
|
||||
void set_scroll(int p_scroll);
|
||||
|
||||
void update_toggle_scripts_button();
|
||||
|
||||
static void init_gdext_pointers();
|
||||
|
||||
EditorHelp();
|
||||
~EditorHelp();
|
||||
};
|
||||
|
||||
class EditorHelpBit : public VBoxContainer {
|
||||
GDCLASS(EditorHelpBit, VBoxContainer);
|
||||
|
||||
struct DocType {
|
||||
String type;
|
||||
String enumeration;
|
||||
bool is_bitfield = false;
|
||||
};
|
||||
|
||||
struct ArgumentData {
|
||||
String name;
|
||||
DocType doc_type;
|
||||
String default_value;
|
||||
};
|
||||
|
||||
struct HelpData {
|
||||
String description;
|
||||
String deprecated_message;
|
||||
String experimental_message;
|
||||
DocType doc_type; // For method return type.
|
||||
Vector<ArgumentData> arguments; // For methods and signals.
|
||||
};
|
||||
|
||||
inline static HashMap<StringName, HelpData> doc_class_cache;
|
||||
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache;
|
||||
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache;
|
||||
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache;
|
||||
inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache;
|
||||
|
||||
RichTextLabel *title = nullptr;
|
||||
RichTextLabel *content = nullptr;
|
||||
|
||||
String symbol_class_name;
|
||||
String symbol_type;
|
||||
String symbol_visible_type;
|
||||
String symbol_name;
|
||||
|
||||
HelpData help_data;
|
||||
|
||||
float content_min_height = 0.0;
|
||||
float content_max_height = 0.0;
|
||||
|
||||
static HelpData _get_class_help_data(const StringName &p_class_name);
|
||||
static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name);
|
||||
static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name);
|
||||
static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name);
|
||||
static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name);
|
||||
|
||||
void _add_type_to_title(const DocType &p_doc_type);
|
||||
void _update_labels();
|
||||
void _go_to_help(const String &p_what);
|
||||
void _meta_clicked(const String &p_select);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void parse_symbol(const String &p_symbol);
|
||||
void set_custom_text(const String &p_type, const String &p_name, const String &p_description);
|
||||
void set_description(const String &p_text);
|
||||
_FORCE_INLINE_ String get_description() const { return help_data.description; }
|
||||
|
||||
void set_content_height_limits(float p_min, float p_max);
|
||||
void update_content_height();
|
||||
|
||||
EditorHelpBit(const String &p_symbol = String());
|
||||
};
|
||||
|
||||
// Standard tooltips do not allow you to hover over them.
|
||||
// This class is intended as a temporary workaround.
|
||||
class EditorHelpBitTooltip : public PopupPanel {
|
||||
GDCLASS(EditorHelpBitTooltip, PopupPanel);
|
||||
|
||||
Timer *timer = nullptr;
|
||||
int _pushing_input = 0;
|
||||
bool _need_free = false;
|
||||
|
||||
void _start_timer();
|
||||
void _safe_queue_free();
|
||||
void _target_gui_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
public:
|
||||
static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target);
|
||||
|
||||
void popup_under_cursor();
|
||||
|
||||
EditorHelpBitTooltip(Control *p_target);
|
||||
};
|
||||
|
||||
#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
|
||||
class EditorSyntaxHighlighter;
|
||||
|
||||
class EditorHelpHighlighter {
|
||||
public:
|
||||
enum Language {
|
||||
LANGUAGE_GDSCRIPT,
|
||||
LANGUAGE_CSHARP,
|
||||
LANGUAGE_MAX,
|
||||
};
|
||||
|
||||
private:
|
||||
using HighlightData = Vector<Pair<int, Color>>;
|
||||
|
||||
static EditorHelpHighlighter *singleton;
|
||||
|
||||
HashMap<String, HighlightData> highlight_data_caches[LANGUAGE_MAX];
|
||||
|
||||
TextEdit *text_edits[LANGUAGE_MAX];
|
||||
Ref<Script> scripts[LANGUAGE_MAX];
|
||||
Ref<EditorSyntaxHighlighter> highlighters[LANGUAGE_MAX];
|
||||
|
||||
HighlightData _get_highlight_data(Language p_language, const String &p_source, bool p_use_cache);
|
||||
|
||||
public:
|
||||
static void create_singleton();
|
||||
static void free_singleton();
|
||||
static EditorHelpHighlighter *get_singleton();
|
||||
|
||||
void highlight(RichTextLabel *p_rich_text_label, Language p_language, const String &p_source, bool p_use_cache);
|
||||
void reset_cache();
|
||||
|
||||
EditorHelpHighlighter();
|
||||
virtual ~EditorHelpHighlighter();
|
||||
};
|
||||
#endif // defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
|
||||
|
||||
#endif // EDITOR_HELP_H
|
||||
1007
engine/editor/editor_help_search.cpp
Normal file
1007
engine/editor/editor_help_search.cpp
Normal file
File diff suppressed because it is too large
Load diff
202
engine/editor/editor_help_search.h
Normal file
202
engine/editor/editor_help_search.h
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/**************************************************************************/
|
||||
/* editor_help_search.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_HELP_SEARCH_H
|
||||
#define EDITOR_HELP_SEARCH_H
|
||||
|
||||
#include "core/templates/rb_map.h"
|
||||
#include "editor/code_editor.h"
|
||||
#include "editor/editor_help.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorHelpSearch : public ConfirmationDialog {
|
||||
GDCLASS(EditorHelpSearch, ConfirmationDialog);
|
||||
|
||||
enum SearchFlags {
|
||||
SEARCH_CLASSES = 1 << 0,
|
||||
SEARCH_CONSTRUCTORS = 1 << 1,
|
||||
SEARCH_METHODS = 1 << 2,
|
||||
SEARCH_OPERATORS = 1 << 3,
|
||||
SEARCH_SIGNALS = 1 << 4,
|
||||
SEARCH_CONSTANTS = 1 << 5,
|
||||
SEARCH_PROPERTIES = 1 << 6,
|
||||
SEARCH_THEME_ITEMS = 1 << 7,
|
||||
SEARCH_ANNOTATIONS = 1 << 8,
|
||||
SEARCH_ALL = SEARCH_CLASSES | SEARCH_CONSTRUCTORS | SEARCH_METHODS | SEARCH_OPERATORS | SEARCH_SIGNALS | SEARCH_CONSTANTS | SEARCH_PROPERTIES | SEARCH_THEME_ITEMS | SEARCH_ANNOTATIONS,
|
||||
SEARCH_CASE_SENSITIVE = 1 << 29,
|
||||
SEARCH_SHOW_HIERARCHY = 1 << 30
|
||||
};
|
||||
|
||||
LineEdit *search_box = nullptr;
|
||||
Button *case_sensitive_button = nullptr;
|
||||
Button *hierarchy_button = nullptr;
|
||||
OptionButton *filter_combo = nullptr;
|
||||
Tree *results_tree = nullptr;
|
||||
bool old_search = false;
|
||||
String old_term;
|
||||
|
||||
class Runner;
|
||||
Ref<Runner> search;
|
||||
|
||||
struct TreeCache {
|
||||
HashMap<String, TreeItem *> item_cache;
|
||||
|
||||
void clear();
|
||||
|
||||
~TreeCache() {
|
||||
clear();
|
||||
}
|
||||
} tree_cache;
|
||||
|
||||
void _update_results();
|
||||
|
||||
void _search_box_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _search_box_text_changed(const String &p_text);
|
||||
void _filter_combo_item_selected(int p_option);
|
||||
void _confirmed();
|
||||
|
||||
bool _all_terms_in_name(const Vector<String> &p_terms, const String &p_name) const;
|
||||
void _match_method_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::MethodDoc> &p_methods, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
|
||||
void _match_const_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ConstantDoc> &p_constants, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
|
||||
void _match_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::PropertyDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
|
||||
void _match_theme_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ThemeItemDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
|
||||
|
||||
Dictionary _native_search_cb(const String &p_search_string, int p_result_limit);
|
||||
void _native_action_cb(const String &p_item_string);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void popup_dialog();
|
||||
void popup_dialog(const String &p_term);
|
||||
|
||||
EditorHelpSearch();
|
||||
};
|
||||
|
||||
class EditorHelpSearch::Runner : public RefCounted {
|
||||
enum Phase {
|
||||
PHASE_MATCH_CLASSES_INIT,
|
||||
PHASE_MATCH_CLASSES,
|
||||
PHASE_CLASS_ITEMS_INIT,
|
||||
PHASE_CLASS_ITEMS,
|
||||
PHASE_MEMBER_ITEMS_INIT,
|
||||
PHASE_MEMBER_ITEMS,
|
||||
PHASE_SELECT_MATCH,
|
||||
PHASE_MAX
|
||||
};
|
||||
int phase = 0;
|
||||
|
||||
template <typename T>
|
||||
struct MemberMatch {
|
||||
T *doc = nullptr;
|
||||
bool name = false;
|
||||
String keyword;
|
||||
};
|
||||
|
||||
struct ClassMatch {
|
||||
DocData::ClassDoc *doc = nullptr;
|
||||
bool name = false;
|
||||
String keyword;
|
||||
Vector<MemberMatch<DocData::MethodDoc>> constructors;
|
||||
Vector<MemberMatch<DocData::MethodDoc>> methods;
|
||||
Vector<MemberMatch<DocData::MethodDoc>> operators;
|
||||
Vector<MemberMatch<DocData::MethodDoc>> signals;
|
||||
Vector<MemberMatch<DocData::ConstantDoc>> constants;
|
||||
Vector<MemberMatch<DocData::PropertyDoc>> properties;
|
||||
Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
|
||||
Vector<MemberMatch<DocData::MethodDoc>> annotations;
|
||||
|
||||
bool required() {
|
||||
return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size();
|
||||
}
|
||||
};
|
||||
|
||||
Control *ui_service = nullptr;
|
||||
Tree *results_tree = nullptr;
|
||||
TreeCache *tree_cache = nullptr;
|
||||
String term;
|
||||
Vector<String> terms;
|
||||
int search_flags;
|
||||
|
||||
Color disabled_color;
|
||||
|
||||
HashMap<String, DocData::ClassDoc>::Iterator iterator_doc;
|
||||
LocalVector<RBSet<String, NaturalNoCaseComparator>::Element *> iterator_stack;
|
||||
HashMap<String, ClassMatch> matches;
|
||||
HashMap<String, ClassMatch>::Iterator iterator_match;
|
||||
TreeItem *root_item = nullptr;
|
||||
HashMap<String, TreeItem *> class_items;
|
||||
TreeItem *matched_item = nullptr;
|
||||
float match_highest_score = 0;
|
||||
|
||||
bool _is_class_disabled_by_feature_profile(const StringName &p_class);
|
||||
|
||||
void _populate_cache();
|
||||
bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item);
|
||||
|
||||
bool _slice();
|
||||
bool _phase_match_classes_init();
|
||||
bool _phase_match_classes();
|
||||
bool _phase_class_items_init();
|
||||
bool _phase_class_items();
|
||||
bool _phase_member_items_init();
|
||||
bool _phase_member_items();
|
||||
bool _phase_select_match();
|
||||
|
||||
String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const;
|
||||
String _build_keywords_tooltip(const String &p_keywords) const;
|
||||
|
||||
void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
|
||||
bool _all_terms_in_name(const String &p_name) const;
|
||||
String _match_keywords_in_all_terms(const String &p_keywords) const;
|
||||
bool _match_string(const String &p_term, const String &p_string) const;
|
||||
String _match_keywords(const String &p_term, const String &p_keywords) const;
|
||||
void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false);
|
||||
TreeItem *_create_class_hierarchy(const ClassMatch &p_match);
|
||||
TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword);
|
||||
TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match);
|
||||
TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
|
||||
TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
|
||||
TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match);
|
||||
TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match);
|
||||
TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match);
|
||||
TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
|
||||
|
||||
public:
|
||||
bool work(uint64_t slot = 100000);
|
||||
|
||||
Runner(Control *p_icon_service, Tree *p_results_tree, TreeCache *p_tree_cache, const String &p_term, int p_search_flags);
|
||||
};
|
||||
|
||||
#endif // EDITOR_HELP_SEARCH_H
|
||||
41
engine/editor/editor_inspector.compat.inc
Normal file
41
engine/editor/editor_inspector.compat.inc
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**************************************************************************/
|
||||
/* editor_inspector.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void EditorInspectorPlugin::_add_property_editor_bind_compat_92322(const String &p_for_property, Control *p_prop, bool p_add_to_end) {
|
||||
add_property_editor(p_for_property, p_prop, p_add_to_end, "");
|
||||
}
|
||||
|
||||
void EditorInspectorPlugin::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end"), &EditorInspectorPlugin::_add_property_editor_bind_compat_92322, DEFVAL(false));
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
4387
engine/editor/editor_inspector.cpp
Normal file
4387
engine/editor/editor_inspector.cpp
Normal file
File diff suppressed because it is too large
Load diff
660
engine/editor/editor_inspector.h
Normal file
660
engine/editor/editor_inspector.h
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_INSPECTOR_H
|
||||
#define EDITOR_INSPECTOR_H
|
||||
|
||||
#include "editor_property_name_processor.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
|
||||
class AcceptDialog;
|
||||
class Button;
|
||||
class ConfirmationDialog;
|
||||
class EditorInspector;
|
||||
class EditorValidationPanel;
|
||||
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 EditorProperty : public Container {
|
||||
GDCLASS(EditorProperty, Container);
|
||||
|
||||
public:
|
||||
enum MenuItems {
|
||||
MENU_COPY_VALUE,
|
||||
MENU_PASTE_VALUE,
|
||||
MENU_COPY_PROPERTY_PATH,
|
||||
MENU_PIN_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 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 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_pin_flags();
|
||||
|
||||
protected:
|
||||
bool has_borders = 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();
|
||||
|
||||
public:
|
||||
void emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field = StringName(), bool p_changing = false);
|
||||
|
||||
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;
|
||||
|
||||
Object *get_edited_object();
|
||||
StringName get_edited_property() const;
|
||||
inline Variant get_edited_property_value() const { return object->get(property); }
|
||||
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_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,
|
||||
};
|
||||
|
||||
Ref<Texture2D> icon;
|
||||
String label;
|
||||
String doc_class_name;
|
||||
PopupMenu *menu = nullptr;
|
||||
|
||||
void _handle_menu_option(int p_option);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
public:
|
||||
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);
|
||||
|
||||
String label;
|
||||
String section;
|
||||
bool vbox_added = false; // Optimization.
|
||||
Color bg_color;
|
||||
bool foldable = false;
|
||||
int indent_depth = 0;
|
||||
int level = 1;
|
||||
|
||||
Timer *dropping_unfold_timer = nullptr;
|
||||
bool dropping = false;
|
||||
bool dropping_for_unfold = false;
|
||||
|
||||
HashSet<StringName> revertable_properties;
|
||||
|
||||
void _test_unfold();
|
||||
int _get_header_height();
|
||||
Ref<Texture2D> _get_arrow();
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
virtual Size2 get_minimum_size() 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);
|
||||
VBoxContainer *get_vbox();
|
||||
void unfold();
|
||||
void fold();
|
||||
void set_bg_color(const Color &p_bg_color);
|
||||
|
||||
bool has_revertable_properties() const;
|
||||
void property_can_revert_changed(const String &p_path, bool p_can_revert);
|
||||
|
||||
EditorInspectorSection();
|
||||
~EditorInspectorSection();
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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 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 _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_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_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = "");
|
||||
VBoxContainer *get_vbox(int p_index);
|
||||
|
||||
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);
|
||||
|
||||
enum {
|
||||
MAX_PLUGINS = 1024
|
||||
};
|
||||
static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
|
||||
static int inspector_plugin_count;
|
||||
|
||||
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 sub_inspector = false;
|
||||
bool wide_editors = false;
|
||||
bool deletable_properties = false;
|
||||
|
||||
float refresh_countdown;
|
||||
bool update_tree_pending = false;
|
||||
StringName _prop_edited;
|
||||
StringName property_selected;
|
||||
int property_focusable;
|
||||
int update_scroll_request;
|
||||
|
||||
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);
|
||||
|
||||
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 _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 _filter_changed(const String &p_text);
|
||||
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);
|
||||
|
||||
ConfirmationDialog *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();
|
||||
void _check_meta_name();
|
||||
|
||||
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 Button *create_inspector_action_button(const String &p_text);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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_sub_inspector(bool p_enable);
|
||||
bool is_sub_inspector() const { return sub_inspector; }
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
#endif // EDITOR_INSPECTOR_H
|
||||
621
engine/editor/editor_interface.cpp
Normal file
621
engine/editor/editor_interface.cpp
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
/**************************************************************************/
|
||||
/* editor_interface.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_interface.h"
|
||||
|
||||
#include "editor/editor_command_palette.h"
|
||||
#include "editor/editor_feature_profile.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_paths.h"
|
||||
#include "editor/editor_resource_preview.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/filesystem_dock.h"
|
||||
#include "editor/gui/editor_run_bar.h"
|
||||
#include "editor/gui/editor_scene_tabs.h"
|
||||
#include "editor/gui/scene_tree_editor.h"
|
||||
#include "editor/inspector_dock.h"
|
||||
#include "editor/plugins/node_3d_editor_plugin.h"
|
||||
#include "editor/property_selector.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "main/main.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/theme.h"
|
||||
|
||||
EditorInterface *EditorInterface::singleton = nullptr;
|
||||
|
||||
void EditorInterface::restart_editor(bool p_save) {
|
||||
if (p_save) {
|
||||
EditorNode::get_singleton()->save_all_scenes();
|
||||
}
|
||||
EditorNode::get_singleton()->restart_editor();
|
||||
}
|
||||
|
||||
// Editor tools.
|
||||
|
||||
EditorCommandPalette *EditorInterface::get_command_palette() const {
|
||||
return EditorCommandPalette::get_singleton();
|
||||
}
|
||||
|
||||
EditorFileSystem *EditorInterface::get_resource_file_system() const {
|
||||
return EditorFileSystem::get_singleton();
|
||||
}
|
||||
|
||||
EditorPaths *EditorInterface::get_editor_paths() const {
|
||||
return EditorPaths::get_singleton();
|
||||
}
|
||||
|
||||
EditorResourcePreview *EditorInterface::get_resource_previewer() const {
|
||||
return EditorResourcePreview::get_singleton();
|
||||
}
|
||||
|
||||
EditorSelection *EditorInterface::get_selection() const {
|
||||
return EditorNode::get_singleton()->get_editor_selection();
|
||||
}
|
||||
|
||||
Ref<EditorSettings> EditorInterface::get_editor_settings() const {
|
||||
return EditorSettings::get_singleton();
|
||||
}
|
||||
|
||||
TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) {
|
||||
Vector<Ref<Mesh>> meshes;
|
||||
|
||||
for (int i = 0; i < p_meshes.size(); i++) {
|
||||
meshes.push_back(p_meshes[i]);
|
||||
}
|
||||
|
||||
Vector<Ref<Texture2D>> textures = make_mesh_previews(meshes, nullptr, p_preview_size);
|
||||
TypedArray<Texture2D> ret;
|
||||
for (int i = 0; i < textures.size(); i++) {
|
||||
ret.push_back(textures[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector<Ref<Texture2D>> EditorInterface::make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size) {
|
||||
int size = p_preview_size;
|
||||
|
||||
RID scenario = RS::get_singleton()->scenario_create();
|
||||
|
||||
RID viewport = RS::get_singleton()->viewport_create();
|
||||
RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ALWAYS);
|
||||
RS::get_singleton()->viewport_set_scenario(viewport, scenario);
|
||||
RS::get_singleton()->viewport_set_size(viewport, size, size);
|
||||
RS::get_singleton()->viewport_set_transparent_background(viewport, true);
|
||||
RS::get_singleton()->viewport_set_active(viewport, true);
|
||||
RID viewport_texture = RS::get_singleton()->viewport_get_texture(viewport);
|
||||
|
||||
RID camera = RS::get_singleton()->camera_create();
|
||||
RS::get_singleton()->viewport_attach_camera(viewport, camera);
|
||||
|
||||
RID light = RS::get_singleton()->directional_light_create();
|
||||
RID light_instance = RS::get_singleton()->instance_create2(light, scenario);
|
||||
|
||||
RID light2 = RS::get_singleton()->directional_light_create();
|
||||
RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7));
|
||||
RID light_instance2 = RS::get_singleton()->instance_create2(light2, scenario);
|
||||
|
||||
EditorProgress ep("mlib", TTR("Creating Mesh Previews"), p_meshes.size());
|
||||
|
||||
Vector<Ref<Texture2D>> textures;
|
||||
|
||||
for (int i = 0; i < p_meshes.size(); i++) {
|
||||
const Ref<Mesh> &mesh = p_meshes[i];
|
||||
if (!mesh.is_valid()) {
|
||||
textures.push_back(Ref<Texture2D>());
|
||||
continue;
|
||||
}
|
||||
|
||||
Transform3D mesh_xform;
|
||||
if (p_transforms != nullptr) {
|
||||
mesh_xform = (*p_transforms)[i];
|
||||
}
|
||||
|
||||
RID inst = RS::get_singleton()->instance_create2(mesh->get_rid(), scenario);
|
||||
RS::get_singleton()->instance_set_transform(inst, mesh_xform);
|
||||
|
||||
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 / 6);
|
||||
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI / 6) * xform.basis;
|
||||
AABB rot_aabb = xform.xform(aabb);
|
||||
float m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5;
|
||||
if (m == 0) {
|
||||
textures.push_back(Ref<Texture2D>());
|
||||
continue;
|
||||
}
|
||||
xform.origin = -xform.basis.xform(ofs); //-ofs*m;
|
||||
xform.origin.z -= rot_aabb.size.z * 2;
|
||||
xform.invert();
|
||||
xform = mesh_xform * xform;
|
||||
|
||||
RS::get_singleton()->camera_set_transform(camera, xform * Transform3D(Basis(), Vector3(0, 0, 3)));
|
||||
RS::get_singleton()->camera_set_orthogonal(camera, m * 2, 0.01, 1000.0);
|
||||
|
||||
RS::get_singleton()->instance_set_transform(light_instance, xform * Transform3D().looking_at(Vector3(-2, -1, -1), Vector3(0, 1, 0)));
|
||||
RS::get_singleton()->instance_set_transform(light_instance2, xform * Transform3D().looking_at(Vector3(+1, -1, -2), Vector3(0, 1, 0)));
|
||||
|
||||
ep.step(TTR("Thumbnail..."), i);
|
||||
DisplayServer::get_singleton()->process_events();
|
||||
Main::iteration();
|
||||
Main::iteration();
|
||||
Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture);
|
||||
ERR_CONTINUE(!img.is_valid() || img->is_empty());
|
||||
Ref<ImageTexture> it = ImageTexture::create_from_image(img);
|
||||
|
||||
RS::get_singleton()->free(inst);
|
||||
|
||||
textures.push_back(it);
|
||||
}
|
||||
|
||||
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(scenario);
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
void EditorInterface::set_plugin_enabled(const String &p_plugin, bool p_enabled) {
|
||||
EditorNode::get_singleton()->set_addon_plugin_enabled(p_plugin, p_enabled, true);
|
||||
}
|
||||
|
||||
bool EditorInterface::is_plugin_enabled(const String &p_plugin) const {
|
||||
return EditorNode::get_singleton()->is_addon_plugin_enabled(p_plugin);
|
||||
}
|
||||
|
||||
// Editor GUI.
|
||||
|
||||
Ref<Theme> EditorInterface::get_editor_theme() const {
|
||||
return EditorNode::get_singleton()->get_editor_theme();
|
||||
}
|
||||
|
||||
Control *EditorInterface::get_base_control() const {
|
||||
return EditorNode::get_singleton()->get_gui_base();
|
||||
}
|
||||
|
||||
VBoxContainer *EditorInterface::get_editor_main_screen() const {
|
||||
return EditorNode::get_singleton()->get_main_screen_control();
|
||||
}
|
||||
|
||||
ScriptEditor *EditorInterface::get_script_editor() const {
|
||||
return ScriptEditor::get_singleton();
|
||||
}
|
||||
|
||||
SubViewport *EditorInterface::get_editor_viewport_2d() const {
|
||||
return EditorNode::get_singleton()->get_scene_root();
|
||||
}
|
||||
|
||||
SubViewport *EditorInterface::get_editor_viewport_3d(int p_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(Node3DEditor::VIEWPORTS_COUNT), nullptr);
|
||||
return Node3DEditor::get_singleton()->get_editor_viewport(p_idx)->get_viewport_node();
|
||||
}
|
||||
|
||||
void EditorInterface::set_main_screen_editor(const String &p_name) {
|
||||
EditorNode::get_singleton()->select_editor_by_name(p_name);
|
||||
}
|
||||
|
||||
void EditorInterface::set_distraction_free_mode(bool p_enter) {
|
||||
EditorNode::get_singleton()->set_distraction_free_mode(p_enter);
|
||||
}
|
||||
|
||||
bool EditorInterface::is_distraction_free_mode_enabled() const {
|
||||
return EditorNode::get_singleton()->is_distraction_free_mode_enabled();
|
||||
}
|
||||
|
||||
bool EditorInterface::is_multi_window_enabled() const {
|
||||
return EditorNode::get_singleton()->is_multi_window_enabled();
|
||||
}
|
||||
|
||||
float EditorInterface::get_editor_scale() const {
|
||||
return EDSCALE;
|
||||
}
|
||||
|
||||
void EditorInterface::popup_dialog(Window *p_dialog, const Rect2i &p_screen_rect) {
|
||||
p_dialog->popup_exclusive(EditorNode::get_singleton(), p_screen_rect);
|
||||
}
|
||||
|
||||
void EditorInterface::popup_dialog_centered(Window *p_dialog, const Size2i &p_minsize) {
|
||||
p_dialog->popup_exclusive_centered(EditorNode::get_singleton(), p_minsize);
|
||||
}
|
||||
|
||||
void EditorInterface::popup_dialog_centered_ratio(Window *p_dialog, float p_ratio) {
|
||||
p_dialog->popup_exclusive_centered_ratio(EditorNode::get_singleton(), p_ratio);
|
||||
}
|
||||
|
||||
void EditorInterface::popup_dialog_centered_clamped(Window *p_dialog, const Size2i &p_size, float p_fallback_ratio) {
|
||||
p_dialog->popup_exclusive_centered_clamped(EditorNode::get_singleton(), p_size, p_fallback_ratio);
|
||||
}
|
||||
|
||||
String EditorInterface::get_current_feature_profile() const {
|
||||
return EditorFeatureProfileManager::get_singleton()->get_current_profile_name();
|
||||
}
|
||||
|
||||
void EditorInterface::set_current_feature_profile(const String &p_profile_name) {
|
||||
EditorFeatureProfileManager::get_singleton()->set_current_profile(p_profile_name, true);
|
||||
}
|
||||
|
||||
// Editor dialogs.
|
||||
|
||||
void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) {
|
||||
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_valid_types first.
|
||||
if (node_selector) {
|
||||
node_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_node_selected).bind(p_callback));
|
||||
node_selector->disconnect(SNAME("canceled"), callable_mp(this, &EditorInterface::_node_selection_canceled).bind(p_callback));
|
||||
get_base_control()->remove_child(node_selector);
|
||||
node_selector->queue_free();
|
||||
}
|
||||
node_selector = memnew(SceneTreeDialog);
|
||||
|
||||
Vector<StringName> valid_types;
|
||||
int length = p_valid_types.size();
|
||||
valid_types.resize(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
valid_types.write[i] = p_valid_types[i];
|
||||
}
|
||||
node_selector->set_valid_types(valid_types);
|
||||
|
||||
get_base_control()->add_child(node_selector);
|
||||
|
||||
node_selector->popup_scenetree_dialog();
|
||||
|
||||
const Callable selected_callback = callable_mp(this, &EditorInterface::_node_selected).bind(p_callback);
|
||||
node_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
|
||||
|
||||
const Callable canceled_callback = callable_mp(this, &EditorInterface::_node_selection_canceled).bind(p_callback);
|
||||
node_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED);
|
||||
}
|
||||
|
||||
void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) {
|
||||
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_type_filter first.
|
||||
if (property_selector) {
|
||||
property_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_property_selected).bind(p_callback));
|
||||
property_selector->disconnect(SNAME("canceled"), callable_mp(this, &EditorInterface::_property_selection_canceled).bind(p_callback));
|
||||
get_base_control()->remove_child(property_selector);
|
||||
property_selector->queue_free();
|
||||
}
|
||||
property_selector = memnew(PropertySelector);
|
||||
|
||||
Vector<Variant::Type> type_filter;
|
||||
int length = p_type_filter.size();
|
||||
type_filter.resize(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
type_filter.write[i] = (Variant::Type)p_type_filter[i];
|
||||
}
|
||||
property_selector->set_type_filter(type_filter);
|
||||
|
||||
get_base_control()->add_child(property_selector);
|
||||
|
||||
property_selector->select_property_from_instance(p_object);
|
||||
|
||||
const Callable selected_callback = callable_mp(this, &EditorInterface::_property_selected).bind(p_callback);
|
||||
property_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
|
||||
|
||||
const Callable canceled_callback = callable_mp(this, &EditorInterface::_property_selection_canceled).bind(p_callback);
|
||||
property_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED);
|
||||
}
|
||||
|
||||
void EditorInterface::_node_selected(const NodePath &p_node_path, const Callable &p_callback) {
|
||||
const NodePath path = get_edited_scene_root()->get_path().rel_path_to(p_node_path);
|
||||
_call_dialog_callback(p_callback, path, "node selected");
|
||||
}
|
||||
|
||||
void EditorInterface::_node_selection_canceled(const Callable &p_callback) {
|
||||
_call_dialog_callback(p_callback, NodePath(), "node selection canceled");
|
||||
}
|
||||
|
||||
void EditorInterface::_property_selected(const String &p_property_name, const Callable &p_callback) {
|
||||
_call_dialog_callback(p_callback, NodePath(p_property_name).get_as_property_path(), "property selected");
|
||||
}
|
||||
|
||||
void EditorInterface::_property_selection_canceled(const Callable &p_callback) {
|
||||
_call_dialog_callback(p_callback, NodePath(), "property selection canceled");
|
||||
}
|
||||
|
||||
void EditorInterface::_call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context) {
|
||||
Callable::CallError ce;
|
||||
Variant ret;
|
||||
const Variant *args[1] = { &p_selected };
|
||||
p_callback.callp(args, 1, ret, ce);
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_PRINT(vformat("Error calling %s callback: %s", p_context, Variant::get_callable_error_text(p_callback, args, 1, ce)));
|
||||
}
|
||||
}
|
||||
|
||||
// Editor docks.
|
||||
|
||||
FileSystemDock *EditorInterface::get_file_system_dock() const {
|
||||
return FileSystemDock::get_singleton();
|
||||
}
|
||||
|
||||
void EditorInterface::select_file(const String &p_file) {
|
||||
FileSystemDock::get_singleton()->select_file(p_file);
|
||||
}
|
||||
|
||||
Vector<String> EditorInterface::get_selected_paths() const {
|
||||
return FileSystemDock::get_singleton()->get_selected_paths();
|
||||
}
|
||||
|
||||
String EditorInterface::get_current_path() const {
|
||||
return FileSystemDock::get_singleton()->get_current_path();
|
||||
}
|
||||
|
||||
String EditorInterface::get_current_directory() const {
|
||||
return FileSystemDock::get_singleton()->get_current_directory();
|
||||
}
|
||||
|
||||
EditorInspector *EditorInterface::get_inspector() const {
|
||||
return InspectorDock::get_inspector_singleton();
|
||||
}
|
||||
|
||||
// Object/Resource/Node editing.
|
||||
|
||||
void EditorInterface::inspect_object(Object *p_obj, const String &p_for_property, bool p_inspector_only) {
|
||||
EditorNode::get_singleton()->push_item(p_obj, p_for_property, p_inspector_only);
|
||||
}
|
||||
|
||||
void EditorInterface::edit_resource(const Ref<Resource> &p_resource) {
|
||||
EditorNode::get_singleton()->edit_resource(p_resource);
|
||||
}
|
||||
|
||||
void EditorInterface::edit_node(Node *p_node) {
|
||||
EditorNode::get_singleton()->edit_node(p_node);
|
||||
}
|
||||
|
||||
void EditorInterface::edit_script(const Ref<Script> &p_script, int p_line, int p_col, bool p_grab_focus) {
|
||||
ScriptEditor::get_singleton()->edit(p_script, p_line - 1, p_col - 1, p_grab_focus);
|
||||
}
|
||||
|
||||
void EditorInterface::open_scene_from_path(const String &scene_path) {
|
||||
if (EditorNode::get_singleton()->is_changing_scene()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->open_request(scene_path);
|
||||
}
|
||||
|
||||
void EditorInterface::reload_scene_from_path(const String &scene_path) {
|
||||
if (EditorNode::get_singleton()->is_changing_scene()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->reload_scene(scene_path);
|
||||
}
|
||||
|
||||
Node *EditorInterface::get_edited_scene_root() const {
|
||||
return EditorNode::get_singleton()->get_edited_scene();
|
||||
}
|
||||
|
||||
PackedStringArray EditorInterface::get_open_scenes() const {
|
||||
PackedStringArray ret;
|
||||
Vector<EditorData::EditedScene> scenes = EditorNode::get_editor_data().get_edited_scenes();
|
||||
|
||||
int scns_amount = scenes.size();
|
||||
for (int idx_scn = 0; idx_scn < scns_amount; idx_scn++) {
|
||||
if (scenes[idx_scn].root == nullptr) {
|
||||
continue;
|
||||
}
|
||||
ret.push_back(scenes[idx_scn].root->get_scene_file_path());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error EditorInterface::save_scene() {
|
||||
if (!get_edited_scene_root()) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
if (get_edited_scene_root()->get_scene_file_path().is_empty()) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
save_scene_as(get_edited_scene_root()->get_scene_file_path());
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EditorInterface::save_scene_as(const String &p_scene, bool p_with_preview) {
|
||||
EditorNode::get_singleton()->save_scene_to_path(p_scene, p_with_preview);
|
||||
}
|
||||
|
||||
void EditorInterface::mark_scene_as_unsaved() {
|
||||
EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(EditorNode::get_editor_data().get_current_edited_scene_history_id());
|
||||
EditorSceneTabs::get_singleton()->update_scene_tabs();
|
||||
}
|
||||
|
||||
void EditorInterface::save_all_scenes() {
|
||||
EditorNode::get_singleton()->save_all_scenes();
|
||||
}
|
||||
|
||||
// Scene playback.
|
||||
|
||||
void EditorInterface::play_main_scene() {
|
||||
EditorRunBar::get_singleton()->play_main_scene();
|
||||
}
|
||||
|
||||
void EditorInterface::play_current_scene() {
|
||||
EditorRunBar::get_singleton()->play_current_scene();
|
||||
}
|
||||
|
||||
void EditorInterface::play_custom_scene(const String &scene_path) {
|
||||
EditorRunBar::get_singleton()->play_custom_scene(scene_path);
|
||||
}
|
||||
|
||||
void EditorInterface::stop_playing_scene() {
|
||||
EditorRunBar::get_singleton()->stop_playing();
|
||||
}
|
||||
|
||||
bool EditorInterface::is_playing_scene() const {
|
||||
return EditorRunBar::get_singleton()->is_playing();
|
||||
}
|
||||
|
||||
String EditorInterface::get_playing_scene() const {
|
||||
return EditorRunBar::get_singleton()->get_playing_scene();
|
||||
}
|
||||
|
||||
void EditorInterface::set_movie_maker_enabled(bool p_enabled) {
|
||||
EditorRunBar::get_singleton()->set_movie_maker_enabled(p_enabled);
|
||||
}
|
||||
|
||||
bool EditorInterface::is_movie_maker_enabled() const {
|
||||
return EditorRunBar::get_singleton()->is_movie_maker_enabled();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void EditorInterface::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
|
||||
const String pf = p_function;
|
||||
if (p_idx == 0) {
|
||||
if (pf == "set_main_screen_editor") {
|
||||
for (String E : { "\"2D\"", "\"3D\"", "\"Script\"", "\"AssetLib\"" }) {
|
||||
r_options->push_back(E);
|
||||
}
|
||||
} else if (pf == "get_editor_viewport_3d") {
|
||||
for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {
|
||||
r_options->push_back(String::num_int64(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Object::get_argument_options(p_function, p_idx, r_options);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Base.
|
||||
|
||||
void EditorInterface::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("restart_editor", "save"), &EditorInterface::restart_editor, DEFVAL(true));
|
||||
|
||||
// Editor tools.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_command_palette"), &EditorInterface::get_command_palette);
|
||||
ClassDB::bind_method(D_METHOD("get_resource_filesystem"), &EditorInterface::get_resource_file_system);
|
||||
ClassDB::bind_method(D_METHOD("get_editor_paths"), &EditorInterface::get_editor_paths);
|
||||
ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer);
|
||||
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
|
||||
ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_plugin_enabled", "plugin", "enabled"), &EditorInterface::set_plugin_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_plugin_enabled", "plugin"), &EditorInterface::is_plugin_enabled);
|
||||
|
||||
// Editor GUI.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_editor_theme"), &EditorInterface::get_editor_theme);
|
||||
ClassDB::bind_method(D_METHOD("get_base_control"), &EditorInterface::get_base_control);
|
||||
ClassDB::bind_method(D_METHOD("get_editor_main_screen"), &EditorInterface::get_editor_main_screen);
|
||||
ClassDB::bind_method(D_METHOD("get_script_editor"), &EditorInterface::get_script_editor);
|
||||
ClassDB::bind_method(D_METHOD("get_editor_viewport_2d"), &EditorInterface::get_editor_viewport_2d);
|
||||
ClassDB::bind_method(D_METHOD("get_editor_viewport_3d", "idx"), &EditorInterface::get_editor_viewport_3d, DEFVAL(0));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_main_screen_editor", "name"), &EditorInterface::set_main_screen_editor);
|
||||
ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode);
|
||||
ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_multi_window_enabled"), &EditorInterface::is_multi_window_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_editor_scale"), &EditorInterface::get_editor_scale);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("popup_dialog", "dialog", "rect"), &EditorInterface::popup_dialog, DEFVAL(Rect2i()));
|
||||
ClassDB::bind_method(D_METHOD("popup_dialog_centered", "dialog", "minsize"), &EditorInterface::popup_dialog_centered, DEFVAL(Size2i()));
|
||||
ClassDB::bind_method(D_METHOD("popup_dialog_centered_ratio", "dialog", "ratio"), &EditorInterface::popup_dialog_centered_ratio, DEFVAL(0.8));
|
||||
ClassDB::bind_method(D_METHOD("popup_dialog_centered_clamped", "dialog", "minsize", "fallback_ratio"), &EditorInterface::popup_dialog_centered_clamped, DEFVAL(Size2i()), DEFVAL(0.75));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_current_feature_profile"), &EditorInterface::get_current_feature_profile);
|
||||
ClassDB::bind_method(D_METHOD("set_current_feature_profile", "profile_name"), &EditorInterface::set_current_feature_profile);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distraction_free_mode"), "set_distraction_free_mode", "is_distraction_free_mode_enabled");
|
||||
|
||||
// Editor dialogs.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()));
|
||||
ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()));
|
||||
|
||||
// Editor docks.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_file_system_dock"), &EditorInterface::get_file_system_dock);
|
||||
ClassDB::bind_method(D_METHOD("select_file", "file"), &EditorInterface::select_file);
|
||||
ClassDB::bind_method(D_METHOD("get_selected_paths"), &EditorInterface::get_selected_paths);
|
||||
ClassDB::bind_method(D_METHOD("get_current_path"), &EditorInterface::get_current_path);
|
||||
ClassDB::bind_method(D_METHOD("get_current_directory"), &EditorInterface::get_current_directory);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_inspector"), &EditorInterface::get_inspector);
|
||||
|
||||
// Object/Resource/Node editing.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("inspect_object", "object", "for_property", "inspector_only"), &EditorInterface::inspect_object, DEFVAL(String()), DEFVAL(false));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("edit_resource", "resource"), &EditorInterface::edit_resource);
|
||||
ClassDB::bind_method(D_METHOD("edit_node", "node"), &EditorInterface::edit_node);
|
||||
ClassDB::bind_method(D_METHOD("edit_script", "script", "line", "column", "grab_focus"), &EditorInterface::edit_script, DEFVAL(-1), DEFVAL(0), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("open_scene_from_path", "scene_filepath"), &EditorInterface::open_scene_from_path);
|
||||
ClassDB::bind_method(D_METHOD("reload_scene_from_path", "scene_filepath"), &EditorInterface::reload_scene_from_path);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_open_scenes"), &EditorInterface::get_open_scenes);
|
||||
ClassDB::bind_method(D_METHOD("get_edited_scene_root"), &EditorInterface::get_edited_scene_root);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("save_scene"), &EditorInterface::save_scene);
|
||||
ClassDB::bind_method(D_METHOD("save_scene_as", "path", "with_preview"), &EditorInterface::save_scene_as, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("save_all_scenes"), &EditorInterface::save_all_scenes);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("mark_scene_as_unsaved"), &EditorInterface::mark_scene_as_unsaved);
|
||||
|
||||
// Scene playback.
|
||||
|
||||
ClassDB::bind_method(D_METHOD("play_main_scene"), &EditorInterface::play_main_scene);
|
||||
ClassDB::bind_method(D_METHOD("play_current_scene"), &EditorInterface::play_current_scene);
|
||||
ClassDB::bind_method(D_METHOD("play_custom_scene", "scene_filepath"), &EditorInterface::play_custom_scene);
|
||||
ClassDB::bind_method(D_METHOD("stop_playing_scene"), &EditorInterface::stop_playing_scene);
|
||||
ClassDB::bind_method(D_METHOD("is_playing_scene"), &EditorInterface::is_playing_scene);
|
||||
ClassDB::bind_method(D_METHOD("get_playing_scene"), &EditorInterface::get_playing_scene);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_movie_maker_enabled", "enabled"), &EditorInterface::set_movie_maker_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_movie_maker_enabled"), &EditorInterface::is_movie_maker_enabled);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_maker_enabled"), "set_movie_maker_enabled", "is_movie_maker_enabled");
|
||||
}
|
||||
|
||||
void EditorInterface::create() {
|
||||
memnew(EditorInterface);
|
||||
}
|
||||
|
||||
void EditorInterface::free() {
|
||||
ERR_FAIL_NULL(singleton);
|
||||
memdelete(singleton);
|
||||
}
|
||||
|
||||
EditorInterface::EditorInterface() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
}
|
||||
184
engine/editor/editor_interface.h
Normal file
184
engine/editor/editor_interface.h
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/**************************************************************************/
|
||||
/* editor_interface.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_INTERFACE_H
|
||||
#define EDITOR_INTERFACE_H
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
class Control;
|
||||
class EditorCommandPalette;
|
||||
class EditorFileSystem;
|
||||
class EditorInspector;
|
||||
class EditorPaths;
|
||||
class EditorPlugin;
|
||||
class EditorResourcePreview;
|
||||
class EditorSelection;
|
||||
class EditorSettings;
|
||||
class FileSystemDock;
|
||||
class Mesh;
|
||||
class Node;
|
||||
class PropertySelector;
|
||||
class SceneTreeDialog;
|
||||
class ScriptEditor;
|
||||
class SubViewport;
|
||||
class Texture2D;
|
||||
class Theme;
|
||||
class VBoxContainer;
|
||||
class Window;
|
||||
|
||||
class EditorInterface : public Object {
|
||||
GDCLASS(EditorInterface, Object);
|
||||
|
||||
static EditorInterface *singleton;
|
||||
|
||||
// Editor dialogs.
|
||||
|
||||
PropertySelector *property_selector = nullptr;
|
||||
SceneTreeDialog *node_selector = nullptr;
|
||||
|
||||
void _node_selected(const NodePath &p_node_paths, const Callable &p_callback);
|
||||
void _node_selection_canceled(const Callable &p_callback);
|
||||
void _property_selected(const String &p_property_name, const Callable &p_callback);
|
||||
void _property_selection_canceled(const Callable &p_callback);
|
||||
void _call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context);
|
||||
|
||||
// Editor tools.
|
||||
|
||||
TypedArray<Texture2D> _make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorInterface *get_singleton() { return singleton; }
|
||||
|
||||
void restart_editor(bool p_save = true);
|
||||
|
||||
// Editor tools.
|
||||
|
||||
EditorCommandPalette *get_command_palette() const;
|
||||
EditorFileSystem *get_resource_file_system() const;
|
||||
EditorPaths *get_editor_paths() const;
|
||||
EditorResourcePreview *get_resource_previewer() const;
|
||||
EditorSelection *get_selection() const;
|
||||
Ref<EditorSettings> get_editor_settings() const;
|
||||
|
||||
Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
|
||||
|
||||
void set_plugin_enabled(const String &p_plugin, bool p_enabled);
|
||||
bool is_plugin_enabled(const String &p_plugin) const;
|
||||
|
||||
// Editor GUI.
|
||||
|
||||
Ref<Theme> get_editor_theme() const;
|
||||
|
||||
Control *get_base_control() const;
|
||||
VBoxContainer *get_editor_main_screen() const;
|
||||
ScriptEditor *get_script_editor() const;
|
||||
SubViewport *get_editor_viewport_2d() const;
|
||||
SubViewport *get_editor_viewport_3d(int p_idx = 0) const;
|
||||
|
||||
void set_main_screen_editor(const String &p_name);
|
||||
void set_distraction_free_mode(bool p_enter);
|
||||
bool is_distraction_free_mode_enabled() const;
|
||||
bool is_multi_window_enabled() const;
|
||||
|
||||
float get_editor_scale() const;
|
||||
|
||||
void popup_dialog(Window *p_dialog, const Rect2i &p_screen_rect = Rect2i());
|
||||
void popup_dialog_centered(Window *p_dialog, const Size2i &p_minsize = Size2i());
|
||||
void popup_dialog_centered_ratio(Window *p_dialog, float p_ratio = 0.8);
|
||||
void popup_dialog_centered_clamped(Window *p_dialog, const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75);
|
||||
|
||||
String get_current_feature_profile() const;
|
||||
void set_current_feature_profile(const String &p_profile_name);
|
||||
|
||||
// Editor dialogs.
|
||||
|
||||
void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>());
|
||||
// Must use Vector<int> because exposing Vector<Variant::Type> is not supported.
|
||||
void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array());
|
||||
|
||||
// Editor docks.
|
||||
|
||||
FileSystemDock *get_file_system_dock() const;
|
||||
void select_file(const String &p_file);
|
||||
Vector<String> get_selected_paths() const;
|
||||
String get_current_path() const;
|
||||
String get_current_directory() const;
|
||||
|
||||
EditorInspector *get_inspector() const;
|
||||
|
||||
// Object/Resource/Node editing.
|
||||
|
||||
void inspect_object(Object *p_obj, const String &p_for_property = String(), bool p_inspector_only = false);
|
||||
|
||||
void edit_resource(const Ref<Resource> &p_resource);
|
||||
void edit_node(Node *p_node);
|
||||
void edit_script(const Ref<Script> &p_script, int p_line = -1, int p_col = 0, bool p_grab_focus = true);
|
||||
void open_scene_from_path(const String &scene_path);
|
||||
void reload_scene_from_path(const String &scene_path);
|
||||
|
||||
PackedStringArray get_open_scenes() const;
|
||||
Node *get_edited_scene_root() const;
|
||||
|
||||
Error save_scene();
|
||||
void save_scene_as(const String &p_scene, bool p_with_preview = true);
|
||||
void mark_scene_as_unsaved();
|
||||
void save_all_scenes();
|
||||
|
||||
// Scene playback.
|
||||
|
||||
void play_main_scene();
|
||||
void play_current_scene();
|
||||
void play_custom_scene(const String &scene_path);
|
||||
void stop_playing_scene();
|
||||
bool is_playing_scene() const;
|
||||
String get_playing_scene() const;
|
||||
|
||||
void set_movie_maker_enabled(bool p_enabled);
|
||||
bool is_movie_maker_enabled() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
// Base.
|
||||
static void create();
|
||||
static void free();
|
||||
|
||||
EditorInterface();
|
||||
};
|
||||
|
||||
#endif // EDITOR_INTERFACE_H
|
||||
141
engine/editor/editor_layouts_dialog.cpp
Normal file
141
engine/editor/editor_layouts_dialog.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/**************************************************************************/
|
||||
/* editor_layouts_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 "editor_layouts_dialog.h"
|
||||
|
||||
#include "core/io/config_file.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
void EditorLayoutsDialog::_line_gui_input(const Ref<InputEvent> &p_event) {
|
||||
Ref<InputEventKey> k = p_event;
|
||||
|
||||
if (k.is_valid()) {
|
||||
if (k->is_action_pressed(SNAME("ui_text_submit"), false, true)) {
|
||||
if (get_hide_on_ok()) {
|
||||
hide();
|
||||
}
|
||||
ok_pressed();
|
||||
set_input_as_handled();
|
||||
} else if (k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
|
||||
hide();
|
||||
set_input_as_handled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::_update_ok_disable_state() {
|
||||
if (layout_names->is_anything_selected()) {
|
||||
get_ok_button()->set_disabled(false);
|
||||
} else {
|
||||
get_ok_button()->set_disabled(!name->is_visible() || name->get_text().strip_edges().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::_deselect_layout_names() {
|
||||
// The deselect method does not emit any signal, therefore we need update the disable state as well.
|
||||
layout_names->deselect_all();
|
||||
_update_ok_disable_state();
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("name_confirmed", PropertyInfo(Variant::STRING, "name")));
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::ok_pressed() {
|
||||
if (layout_names->is_anything_selected()) {
|
||||
Vector<int> const selected_items = layout_names->get_selected_items();
|
||||
for (int i = 0; i < selected_items.size(); ++i) {
|
||||
emit_signal(SNAME("name_confirmed"), layout_names->get_item_text(selected_items[i]));
|
||||
}
|
||||
} else if (name->is_visible() && !name->get_text().strip_edges().is_empty()) {
|
||||
emit_signal(SNAME("name_confirmed"), name->get_text().strip_edges());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::_post_popup() {
|
||||
ConfirmationDialog::_post_popup();
|
||||
layout_names->clear();
|
||||
name->clear();
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config());
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> layouts;
|
||||
config.ptr()->get_sections(&layouts);
|
||||
|
||||
for (const String &E : layouts) {
|
||||
layout_names->add_item(E);
|
||||
}
|
||||
if (name->is_visible()) {
|
||||
name->grab_focus();
|
||||
} else {
|
||||
layout_names->grab_focus();
|
||||
}
|
||||
}
|
||||
|
||||
EditorLayoutsDialog::EditorLayoutsDialog() {
|
||||
makevb = memnew(VBoxContainer);
|
||||
add_child(makevb);
|
||||
|
||||
layout_names = memnew(ItemList);
|
||||
layout_names->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
layout_names->set_auto_height(true);
|
||||
layout_names->set_custom_minimum_size(Size2(300 * EDSCALE, 50 * EDSCALE));
|
||||
layout_names->set_visible(true);
|
||||
layout_names->set_offset(SIDE_TOP, 5);
|
||||
layout_names->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
layout_names->set_select_mode(ItemList::SELECT_MULTI);
|
||||
layout_names->set_allow_rmb_select(true);
|
||||
layout_names->connect("multi_selected", callable_mp(this, &EditorLayoutsDialog::_update_ok_disable_state).unbind(2));
|
||||
MarginContainer *mc = makevb->add_margin_child(TTR("Select existing layout:"), layout_names);
|
||||
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
name = memnew(LineEdit);
|
||||
makevb->add_child(name);
|
||||
name->set_placeholder(TTR("Or enter new layout name"));
|
||||
name->set_offset(SIDE_TOP, 5);
|
||||
name->set_anchor_and_offset(SIDE_LEFT, Control::ANCHOR_BEGIN, 5);
|
||||
name->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, -5);
|
||||
name->connect(SceneStringName(gui_input), callable_mp(this, &EditorLayoutsDialog::_line_gui_input));
|
||||
name->connect(SceneStringName(focus_entered), callable_mp(this, &EditorLayoutsDialog::_deselect_layout_names));
|
||||
name->connect(SceneStringName(text_changed), callable_mp(this, &EditorLayoutsDialog::_update_ok_disable_state).unbind(1));
|
||||
}
|
||||
|
||||
void EditorLayoutsDialog::set_name_line_enabled(bool p_enabled) {
|
||||
name->set_visible(p_enabled);
|
||||
}
|
||||
61
engine/editor/editor_layouts_dialog.h
Normal file
61
engine/editor/editor_layouts_dialog.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* editor_layouts_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_LAYOUTS_DIALOG_H
|
||||
#define EDITOR_LAYOUTS_DIALOG_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class LineEdit;
|
||||
class ItemList;
|
||||
|
||||
class EditorLayoutsDialog : public ConfirmationDialog {
|
||||
GDCLASS(EditorLayoutsDialog, ConfirmationDialog);
|
||||
|
||||
LineEdit *name = nullptr;
|
||||
ItemList *layout_names = nullptr;
|
||||
VBoxContainer *makevb = nullptr;
|
||||
|
||||
void _line_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _update_ok_disable_state();
|
||||
void _deselect_layout_names();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
virtual void ok_pressed() override;
|
||||
virtual void _post_popup() override;
|
||||
|
||||
public:
|
||||
EditorLayoutsDialog();
|
||||
|
||||
void set_name_line_enabled(bool p_enabled);
|
||||
};
|
||||
|
||||
#endif // EDITOR_LAYOUTS_DIALOG_H
|
||||
563
engine/editor/editor_locale_dialog.cpp
Normal file
563
engine/editor/editor_locale_dialog.cpp
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
/**************************************************************************/
|
||||
/* editor_locale_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 "editor_locale_dialog.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
void EditorLocaleDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("locale_selected", PropertyInfo(Variant::STRING, "locale")));
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::ok_pressed() {
|
||||
if (edit_filters->is_pressed()) {
|
||||
return; // Do not update, if in filter edit mode.
|
||||
}
|
||||
|
||||
String locale;
|
||||
if (lang_code->get_text().is_empty()) {
|
||||
return; // Language code is required.
|
||||
}
|
||||
locale = lang_code->get_text();
|
||||
|
||||
if (!script_code->get_text().is_empty()) {
|
||||
locale += "_" + script_code->get_text();
|
||||
}
|
||||
if (!country_code->get_text().is_empty()) {
|
||||
locale += "_" + country_code->get_text();
|
||||
}
|
||||
if (!variant_code->get_text().is_empty()) {
|
||||
locale += "_" + variant_code->get_text();
|
||||
}
|
||||
|
||||
emit_signal(SNAME("locale_selected"), TranslationServer::get_singleton()->standardize_locale(locale));
|
||||
hide();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_item_selected() {
|
||||
if (updating_lists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edit_filters->is_pressed()) {
|
||||
return; // Do not update, if in filter edit mode.
|
||||
}
|
||||
|
||||
TreeItem *l = lang_list->get_selected();
|
||||
if (l) {
|
||||
lang_code->set_text(l->get_metadata(0).operator String());
|
||||
}
|
||||
|
||||
TreeItem *s = script_list->get_selected();
|
||||
if (s) {
|
||||
script_code->set_text(s->get_metadata(0).operator String());
|
||||
}
|
||||
|
||||
TreeItem *c = cnt_list->get_selected();
|
||||
if (c) {
|
||||
country_code->set_text(c->get_metadata(0).operator String());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_toggle_advanced(bool p_checked) {
|
||||
if (!p_checked) {
|
||||
script_code->set_text("");
|
||||
variant_code->set_text("");
|
||||
}
|
||||
_update_tree();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_post_popup() {
|
||||
ConfirmationDialog::_post_popup();
|
||||
|
||||
if (!locale_set) {
|
||||
lang_code->set_text("");
|
||||
script_code->set_text("");
|
||||
country_code->set_text("");
|
||||
variant_code->set_text("");
|
||||
}
|
||||
edit_filters->set_pressed(false);
|
||||
_update_tree();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_filter_lang_option_changed() {
|
||||
TreeItem *t = lang_list->get_edited();
|
||||
String lang = t->get_metadata(0);
|
||||
bool checked = t->is_checked(0);
|
||||
|
||||
Variant prev;
|
||||
Array f_lang_all;
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
|
||||
f_lang_all = GLOBAL_GET("internationalization/locale/language_filter");
|
||||
prev = f_lang_all;
|
||||
}
|
||||
|
||||
int l_idx = f_lang_all.find(lang);
|
||||
|
||||
if (checked) {
|
||||
if (l_idx == -1) {
|
||||
f_lang_all.append(lang);
|
||||
}
|
||||
} else {
|
||||
if (l_idx != -1) {
|
||||
f_lang_all.remove_at(l_idx);
|
||||
}
|
||||
}
|
||||
|
||||
f_lang_all.sort();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Changed Locale Language Filter"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", f_lang_all);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", prev);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_filter_script_option_changed() {
|
||||
TreeItem *t = script_list->get_edited();
|
||||
String scr_code = t->get_metadata(0);
|
||||
bool checked = t->is_checked(0);
|
||||
|
||||
Variant prev;
|
||||
Array f_script_all;
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
|
||||
f_script_all = GLOBAL_GET("internationalization/locale/script_filter");
|
||||
prev = f_script_all;
|
||||
}
|
||||
|
||||
int l_idx = f_script_all.find(scr_code);
|
||||
|
||||
if (checked) {
|
||||
if (l_idx == -1) {
|
||||
f_script_all.append(scr_code);
|
||||
}
|
||||
} else {
|
||||
if (l_idx != -1) {
|
||||
f_script_all.remove_at(l_idx);
|
||||
}
|
||||
}
|
||||
|
||||
f_script_all.sort();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Changed Locale Script Filter"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", f_script_all);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", prev);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_filter_cnt_option_changed() {
|
||||
TreeItem *t = cnt_list->get_edited();
|
||||
String cnt = t->get_metadata(0);
|
||||
bool checked = t->is_checked(0);
|
||||
|
||||
Variant prev;
|
||||
Array f_cnt_all;
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
|
||||
f_cnt_all = GLOBAL_GET("internationalization/locale/country_filter");
|
||||
prev = f_cnt_all;
|
||||
}
|
||||
|
||||
int l_idx = f_cnt_all.find(cnt);
|
||||
|
||||
if (checked) {
|
||||
if (l_idx == -1) {
|
||||
f_cnt_all.append(cnt);
|
||||
}
|
||||
} else {
|
||||
if (l_idx != -1) {
|
||||
f_cnt_all.remove_at(l_idx);
|
||||
}
|
||||
}
|
||||
|
||||
f_cnt_all.sort();
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Changed Locale Country Filter"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", f_cnt_all);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", prev);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_filter_mode_changed(int p_mode) {
|
||||
int f_mode = filter_mode->get_selected_id();
|
||||
Variant prev;
|
||||
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
|
||||
prev = GLOBAL_GET("internationalization/locale/locale_filter_mode");
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Changed Locale Filter Mode"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", f_mode);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", prev);
|
||||
undo_redo->commit_action();
|
||||
|
||||
_update_tree();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_edit_filters(bool p_checked) {
|
||||
_update_tree();
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::_update_tree() {
|
||||
updating_lists = true;
|
||||
|
||||
int filter = SHOW_ALL_LOCALES;
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) {
|
||||
filter = GLOBAL_GET("internationalization/locale/locale_filter_mode");
|
||||
}
|
||||
Array f_lang_all;
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) {
|
||||
f_lang_all = GLOBAL_GET("internationalization/locale/language_filter");
|
||||
}
|
||||
Array f_cnt_all;
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) {
|
||||
f_cnt_all = GLOBAL_GET("internationalization/locale/country_filter");
|
||||
}
|
||||
Array f_script_all;
|
||||
if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) {
|
||||
f_script_all = GLOBAL_GET("internationalization/locale/script_filter");
|
||||
}
|
||||
bool is_edit_mode = edit_filters->is_pressed();
|
||||
|
||||
filter_mode->select(filter);
|
||||
|
||||
// Hide text advanced edit and disable OK button if in filter edit mode.
|
||||
advanced->set_visible(!is_edit_mode);
|
||||
hb_locale->set_visible(!is_edit_mode && advanced->is_pressed());
|
||||
vb_script_list->set_visible(advanced->is_pressed());
|
||||
get_ok_button()->set_disabled(is_edit_mode);
|
||||
|
||||
// Update language list.
|
||||
lang_list->clear();
|
||||
TreeItem *l_root = lang_list->create_item(nullptr);
|
||||
lang_list->set_hide_root(true);
|
||||
|
||||
Vector<String> languages = TranslationServer::get_singleton()->get_all_languages();
|
||||
for (const String &E : languages) {
|
||||
if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_lang_all.has(E) || f_lang_all.is_empty()) {
|
||||
const String &lang = TranslationServer::get_singleton()->get_language_name(E);
|
||||
TreeItem *t = lang_list->create_item(l_root);
|
||||
if (is_edit_mode) {
|
||||
t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
t->set_editable(0, true);
|
||||
t->set_checked(0, f_lang_all.has(E));
|
||||
} else if (lang_code->get_text() == E) {
|
||||
t->select(0);
|
||||
}
|
||||
t->set_text(0, vformat("%s [%s]", lang, E));
|
||||
t->set_metadata(0, E);
|
||||
}
|
||||
}
|
||||
|
||||
// Update script list.
|
||||
script_list->clear();
|
||||
TreeItem *s_root = script_list->create_item(nullptr);
|
||||
script_list->set_hide_root(true);
|
||||
|
||||
if (!is_edit_mode) {
|
||||
TreeItem *t = script_list->create_item(s_root);
|
||||
t->set_text(0, TTR("[Default]"));
|
||||
t->set_metadata(0, "");
|
||||
}
|
||||
|
||||
Vector<String> scripts = TranslationServer::get_singleton()->get_all_scripts();
|
||||
for (const String &E : scripts) {
|
||||
if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_script_all.has(E) || f_script_all.is_empty()) {
|
||||
const String &scr_code = TranslationServer::get_singleton()->get_script_name(E);
|
||||
TreeItem *t = script_list->create_item(s_root);
|
||||
if (is_edit_mode) {
|
||||
t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
t->set_editable(0, true);
|
||||
t->set_checked(0, f_script_all.has(E));
|
||||
} else if (script_code->get_text() == E) {
|
||||
t->select(0);
|
||||
}
|
||||
t->set_text(0, vformat("%s [%s]", scr_code, E));
|
||||
t->set_metadata(0, E);
|
||||
}
|
||||
}
|
||||
|
||||
// Update country list.
|
||||
cnt_list->clear();
|
||||
TreeItem *c_root = cnt_list->create_item(nullptr);
|
||||
cnt_list->set_hide_root(true);
|
||||
|
||||
if (!is_edit_mode) {
|
||||
TreeItem *t = cnt_list->create_item(c_root);
|
||||
t->set_text(0, TTR("[Default]"));
|
||||
t->set_metadata(0, "");
|
||||
}
|
||||
|
||||
Vector<String> countries = TranslationServer::get_singleton()->get_all_countries();
|
||||
for (const String &E : countries) {
|
||||
if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_cnt_all.has(E) || f_cnt_all.is_empty()) {
|
||||
const String &cnt = TranslationServer::get_singleton()->get_country_name(E);
|
||||
TreeItem *t = cnt_list->create_item(c_root);
|
||||
if (is_edit_mode) {
|
||||
t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
|
||||
t->set_editable(0, true);
|
||||
t->set_checked(0, f_cnt_all.has(E));
|
||||
} else if (country_code->get_text() == E) {
|
||||
t->select(0);
|
||||
}
|
||||
t->set_text(0, vformat("%s [%s]", cnt, E));
|
||||
t->set_metadata(0, E);
|
||||
}
|
||||
}
|
||||
updating_lists = false;
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::set_locale(const String &p_locale) {
|
||||
const String &locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
|
||||
if (locale.is_empty()) {
|
||||
locale_set = false;
|
||||
|
||||
lang_code->set_text("");
|
||||
script_code->set_text("");
|
||||
country_code->set_text("");
|
||||
variant_code->set_text("");
|
||||
} else {
|
||||
locale_set = true;
|
||||
|
||||
Vector<String> locale_elements = p_locale.split("_");
|
||||
lang_code->set_text(locale_elements[0]);
|
||||
if (locale_elements.size() >= 2) {
|
||||
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
|
||||
script_code->set_text(locale_elements[1]);
|
||||
advanced->set_pressed(true);
|
||||
}
|
||||
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
|
||||
country_code->set_text(locale_elements[1]);
|
||||
}
|
||||
}
|
||||
if (locale_elements.size() >= 3) {
|
||||
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
|
||||
country_code->set_text(locale_elements[2]);
|
||||
} else {
|
||||
variant_code->set_text(locale_elements[2].to_lower());
|
||||
advanced->set_pressed(true);
|
||||
}
|
||||
}
|
||||
if (locale_elements.size() >= 4) {
|
||||
variant_code->set_text(locale_elements[3].to_lower());
|
||||
advanced->set_pressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLocaleDialog::popup_locale_dialog() {
|
||||
popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8);
|
||||
}
|
||||
|
||||
EditorLocaleDialog::EditorLocaleDialog() {
|
||||
set_title(TTR("Select a Locale"));
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
{
|
||||
HBoxContainer *hb_filter = memnew(HBoxContainer);
|
||||
{
|
||||
filter_mode = memnew(OptionButton);
|
||||
filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES);
|
||||
filter_mode->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES);
|
||||
filter_mode->select(0);
|
||||
filter_mode->connect(SceneStringName(item_selected), callable_mp(this, &EditorLocaleDialog::_filter_mode_changed));
|
||||
hb_filter->add_child(filter_mode);
|
||||
}
|
||||
{
|
||||
edit_filters = memnew(CheckButton);
|
||||
edit_filters->set_text(TTR("Edit Filters"));
|
||||
edit_filters->set_toggle_mode(true);
|
||||
edit_filters->set_pressed(false);
|
||||
edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters));
|
||||
hb_filter->add_child(edit_filters);
|
||||
}
|
||||
{
|
||||
advanced = memnew(CheckButton);
|
||||
advanced->set_text(TTR("Advanced"));
|
||||
advanced->set_toggle_mode(true);
|
||||
advanced->set_pressed(false);
|
||||
advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
|
||||
hb_filter->add_child(advanced);
|
||||
}
|
||||
vb->add_child(hb_filter);
|
||||
}
|
||||
{
|
||||
HBoxContainer *hb_lists = memnew(HBoxContainer);
|
||||
hb_lists->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
VBoxContainer *vb_lang_list = memnew(VBoxContainer);
|
||||
vb_lang_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *lang_lbl = memnew(Label);
|
||||
lang_lbl->set_text(TTR("Language:"));
|
||||
vb_lang_list->add_child(lang_lbl);
|
||||
}
|
||||
{
|
||||
lang_list = memnew(Tree);
|
||||
lang_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
lang_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
|
||||
lang_list->set_columns(1);
|
||||
lang_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_lang_option_changed));
|
||||
vb_lang_list->add_child(lang_list);
|
||||
}
|
||||
hb_lists->add_child(vb_lang_list);
|
||||
}
|
||||
{
|
||||
vb_script_list = memnew(VBoxContainer);
|
||||
vb_script_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *script_lbl = memnew(Label);
|
||||
// TRANSLATORS: This is the label for a list of writing systems.
|
||||
script_lbl->set_text(TTR("Script:", "Locale"));
|
||||
vb_script_list->add_child(script_lbl);
|
||||
}
|
||||
{
|
||||
script_list = memnew(Tree);
|
||||
script_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
script_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
|
||||
script_list->set_columns(1);
|
||||
script_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_script_option_changed));
|
||||
vb_script_list->add_child(script_list);
|
||||
}
|
||||
hb_lists->add_child(vb_script_list);
|
||||
}
|
||||
{
|
||||
VBoxContainer *vb_cnt_list = memnew(VBoxContainer);
|
||||
vb_cnt_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *cnt_lbl = memnew(Label);
|
||||
cnt_lbl->set_text(TTR("Country:"));
|
||||
vb_cnt_list->add_child(cnt_lbl);
|
||||
}
|
||||
{
|
||||
cnt_list = memnew(Tree);
|
||||
cnt_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
cnt_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
cnt_list->connect("cell_selected", callable_mp(this, &EditorLocaleDialog::_item_selected));
|
||||
cnt_list->set_columns(1);
|
||||
cnt_list->connect("item_edited", callable_mp(this, &EditorLocaleDialog::_filter_cnt_option_changed));
|
||||
vb_cnt_list->add_child(cnt_list);
|
||||
}
|
||||
hb_lists->add_child(vb_cnt_list);
|
||||
}
|
||||
vb->add_child(hb_lists);
|
||||
}
|
||||
{
|
||||
hb_locale = memnew(HBoxContainer);
|
||||
hb_locale->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
{
|
||||
VBoxContainer *vb_language = memnew(VBoxContainer);
|
||||
vb_language->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *language_lbl = memnew(Label);
|
||||
language_lbl->set_text(TTR("Language"));
|
||||
vb_language->add_child(language_lbl);
|
||||
}
|
||||
{
|
||||
lang_code = memnew(LineEdit);
|
||||
lang_code->set_max_length(3);
|
||||
lang_code->set_tooltip_text("Language");
|
||||
vb_language->add_child(lang_code);
|
||||
}
|
||||
hb_locale->add_child(vb_language);
|
||||
}
|
||||
{
|
||||
VBoxContainer *vb_script = memnew(VBoxContainer);
|
||||
vb_script->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *script_lbl = memnew(Label);
|
||||
// TRANSLATORS: This refers to a writing system.
|
||||
script_lbl->set_text(TTR("Script", "Locale"));
|
||||
vb_script->add_child(script_lbl);
|
||||
}
|
||||
{
|
||||
script_code = memnew(LineEdit);
|
||||
script_code->set_max_length(4);
|
||||
script_code->set_tooltip_text("Script");
|
||||
vb_script->add_child(script_code);
|
||||
}
|
||||
hb_locale->add_child(vb_script);
|
||||
}
|
||||
{
|
||||
VBoxContainer *vb_country = memnew(VBoxContainer);
|
||||
vb_country->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *country_lbl = memnew(Label);
|
||||
country_lbl->set_text(TTR("Country"));
|
||||
vb_country->add_child(country_lbl);
|
||||
}
|
||||
{
|
||||
country_code = memnew(LineEdit);
|
||||
country_code->set_max_length(2);
|
||||
country_code->set_tooltip_text("Country");
|
||||
vb_country->add_child(country_code);
|
||||
}
|
||||
hb_locale->add_child(vb_country);
|
||||
}
|
||||
{
|
||||
VBoxContainer *vb_variant = memnew(VBoxContainer);
|
||||
vb_variant->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
{
|
||||
Label *variant_lbl = memnew(Label);
|
||||
variant_lbl->set_text(TTR("Variant"));
|
||||
vb_variant->add_child(variant_lbl);
|
||||
}
|
||||
{
|
||||
variant_code = memnew(LineEdit);
|
||||
variant_code->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
variant_code->set_placeholder("Variant");
|
||||
variant_code->set_tooltip_text("Variant");
|
||||
vb_variant->add_child(variant_code);
|
||||
}
|
||||
hb_locale->add_child(vb_variant);
|
||||
}
|
||||
}
|
||||
vb->add_child(hb_locale);
|
||||
}
|
||||
add_child(vb);
|
||||
_update_tree();
|
||||
|
||||
set_ok_button_text(TTR("Select"));
|
||||
}
|
||||
90
engine/editor/editor_locale_dialog.h
Normal file
90
engine/editor/editor_locale_dialog.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**************************************************************************/
|
||||
/* editor_locale_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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_LOCALE_DIALOG_H
|
||||
#define EDITOR_LOCALE_DIALOG_H
|
||||
|
||||
#include "core/string/translation.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class Button;
|
||||
class HBoxContainer;
|
||||
class VBoxContainer;
|
||||
class LineEdit;
|
||||
class Tree;
|
||||
class OptionButton;
|
||||
|
||||
class EditorLocaleDialog : public ConfirmationDialog {
|
||||
GDCLASS(EditorLocaleDialog, ConfirmationDialog);
|
||||
|
||||
enum LocaleFilter {
|
||||
SHOW_ALL_LOCALES,
|
||||
SHOW_ONLY_SELECTED_LOCALES,
|
||||
};
|
||||
|
||||
HBoxContainer *hb_locale = nullptr;
|
||||
VBoxContainer *vb_script_list = nullptr;
|
||||
OptionButton *filter_mode = nullptr;
|
||||
Button *edit_filters = nullptr;
|
||||
Button *advanced = nullptr;
|
||||
LineEdit *lang_code = nullptr;
|
||||
LineEdit *script_code = nullptr;
|
||||
LineEdit *country_code = nullptr;
|
||||
LineEdit *variant_code = nullptr;
|
||||
Tree *lang_list = nullptr;
|
||||
Tree *script_list = nullptr;
|
||||
Tree *cnt_list = nullptr;
|
||||
|
||||
bool locale_set = false;
|
||||
bool updating_lists = false;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
virtual void _post_popup() override;
|
||||
virtual void ok_pressed() override;
|
||||
|
||||
void _item_selected();
|
||||
void _filter_lang_option_changed();
|
||||
void _filter_script_option_changed();
|
||||
void _filter_cnt_option_changed();
|
||||
void _filter_mode_changed(int p_mode);
|
||||
void _edit_filters(bool p_checked);
|
||||
void _toggle_advanced(bool p_checked);
|
||||
|
||||
void _update_tree();
|
||||
|
||||
public:
|
||||
EditorLocaleDialog();
|
||||
|
||||
void set_locale(const String &p_locale);
|
||||
void popup_locale_dialog();
|
||||
};
|
||||
|
||||
#endif // EDITOR_LOCALE_DIALOG_H
|
||||
574
engine/editor/editor_log.cpp
Normal file
574
engine/editor/editor_log.cpp
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
/**************************************************************************/
|
||||
/* editor_log.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_log.h"
|
||||
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/version.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_paths.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/center_container.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/resources/font.h"
|
||||
|
||||
void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
EditorLog *self = static_cast<EditorLog *>(p_self);
|
||||
|
||||
String err_str;
|
||||
if (p_errorexp && p_errorexp[0]) {
|
||||
err_str = String::utf8(p_errorexp);
|
||||
} else {
|
||||
err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error);
|
||||
}
|
||||
|
||||
if (p_editor_notify) {
|
||||
err_str += " (User)";
|
||||
}
|
||||
|
||||
MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR;
|
||||
|
||||
if (!Thread::is_main_thread()) {
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp(self, &EditorLog::add_message), err_str, message_type);
|
||||
} else {
|
||||
self->add_message(err_str, message_type);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::_update_theme() {
|
||||
const Ref<Font> normal_font = get_theme_font(SNAME("output_source"), EditorStringName(EditorFonts));
|
||||
if (normal_font.is_valid()) {
|
||||
log->add_theme_font_override("normal_font", normal_font);
|
||||
}
|
||||
|
||||
const Ref<Font> bold_font = get_theme_font(SNAME("output_source_bold"), EditorStringName(EditorFonts));
|
||||
if (bold_font.is_valid()) {
|
||||
log->add_theme_font_override("bold_font", bold_font);
|
||||
}
|
||||
|
||||
const Ref<Font> italics_font = get_theme_font(SNAME("output_source_italic"), EditorStringName(EditorFonts));
|
||||
if (italics_font.is_valid()) {
|
||||
log->add_theme_font_override("italics_font", italics_font);
|
||||
}
|
||||
|
||||
const Ref<Font> bold_italics_font = get_theme_font(SNAME("output_source_bold_italic"), EditorStringName(EditorFonts));
|
||||
if (bold_italics_font.is_valid()) {
|
||||
log->add_theme_font_override("bold_italics_font", bold_italics_font);
|
||||
}
|
||||
|
||||
const Ref<Font> mono_font = get_theme_font(SNAME("output_source_mono"), EditorStringName(EditorFonts));
|
||||
if (mono_font.is_valid()) {
|
||||
log->add_theme_font_override("mono_font", mono_font);
|
||||
}
|
||||
|
||||
// Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines.
|
||||
// This also better matches terminal output, which does not use any form of padding.
|
||||
log->add_theme_constant_override("text_highlight_h_padding", 0);
|
||||
log->add_theme_constant_override("text_highlight_v_padding", 0);
|
||||
|
||||
const int font_size = get_theme_font_size(SNAME("output_source_size"), EditorStringName(EditorFonts));
|
||||
log->begin_bulk_theme_override();
|
||||
log->add_theme_font_size_override("normal_font_size", font_size);
|
||||
log->add_theme_font_size_override("bold_font_size", font_size);
|
||||
log->add_theme_font_size_override("italics_font_size", font_size);
|
||||
log->add_theme_font_size_override("mono_font_size", font_size);
|
||||
log->end_bulk_theme_override();
|
||||
|
||||
type_filter_map[MSG_TYPE_STD]->toggle_button->set_icon(get_editor_theme_icon(SNAME("Popup")));
|
||||
type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_icon(get_editor_theme_icon(SNAME("StatusError")));
|
||||
type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_icon(get_editor_theme_icon(SNAME("StatusWarning")));
|
||||
type_filter_map[MSG_TYPE_EDITOR]->toggle_button->set_icon(get_editor_theme_icon(SNAME("Edit")));
|
||||
|
||||
type_filter_map[MSG_TYPE_STD]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
|
||||
type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
|
||||
type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
|
||||
type_filter_map[MSG_TYPE_EDITOR]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
|
||||
|
||||
clear_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
|
||||
copy_button->set_icon(get_editor_theme_icon(SNAME("ActionCopy")));
|
||||
collapse_button->set_icon(get_editor_theme_icon(SNAME("CombineLines")));
|
||||
show_search_button->set_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
|
||||
|
||||
theme_cache.error_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
|
||||
theme_cache.error_icon = get_editor_theme_icon(SNAME("Error"));
|
||||
theme_cache.warning_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
|
||||
theme_cache.warning_icon = get_editor_theme_icon(SNAME("Warning"));
|
||||
theme_cache.message_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.6);
|
||||
}
|
||||
|
||||
void EditorLog::_editor_settings_changed() {
|
||||
int new_line_limit = int(EDITOR_GET("run/output/max_lines"));
|
||||
if (new_line_limit != line_limit) {
|
||||
line_limit = new_line_limit;
|
||||
_rebuild_log();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
_update_theme();
|
||||
_load_state();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
_update_theme();
|
||||
_rebuild_log();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::_set_collapse(bool p_collapse) {
|
||||
collapse = p_collapse;
|
||||
_start_state_save_timer();
|
||||
_rebuild_log();
|
||||
}
|
||||
|
||||
void EditorLog::_start_state_save_timer() {
|
||||
if (!is_loading_state) {
|
||||
save_state_timer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::_save_state() {
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
// Load and amend existing config if it exists.
|
||||
config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
|
||||
|
||||
const String section = "editor_log";
|
||||
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
|
||||
config->set_value(section, "log_filter_" + itos(E.key), E.value->is_active());
|
||||
}
|
||||
|
||||
config->set_value(section, "collapse", collapse);
|
||||
config->set_value(section, "show_search", search_box->is_visible());
|
||||
|
||||
config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
|
||||
}
|
||||
|
||||
void EditorLog::_load_state() {
|
||||
is_loading_state = true;
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
|
||||
|
||||
// Run the below code even if config->load returns an error, since we want the defaults to be set even if the file does not exist yet.
|
||||
const String section = "editor_log";
|
||||
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
|
||||
E.value->set_active(config->get_value(section, "log_filter_" + itos(E.key), true));
|
||||
}
|
||||
|
||||
collapse = config->get_value(section, "collapse", false);
|
||||
collapse_button->set_pressed(collapse);
|
||||
bool show_search = config->get_value(section, "show_search", true);
|
||||
search_box->set_visible(show_search);
|
||||
show_search_button->set_pressed(show_search);
|
||||
|
||||
is_loading_state = false;
|
||||
}
|
||||
|
||||
void EditorLog::_meta_clicked(const String &p_meta) {
|
||||
OS::get_singleton()->shell_open(p_meta);
|
||||
}
|
||||
|
||||
void EditorLog::_clear_request() {
|
||||
log->clear();
|
||||
messages.clear();
|
||||
_reset_message_counts();
|
||||
tool_button->set_icon(Ref<Texture2D>());
|
||||
}
|
||||
|
||||
void EditorLog::_copy_request() {
|
||||
String text = log->get_selected_text();
|
||||
|
||||
if (text.is_empty()) {
|
||||
text = log->get_parsed_text();
|
||||
}
|
||||
|
||||
if (!text.is_empty()) {
|
||||
DisplayServer::get_singleton()->clipboard_set(text);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::clear() {
|
||||
_clear_request();
|
||||
}
|
||||
|
||||
void EditorLog::_process_message(const String &p_msg, MessageType p_type, bool p_clear) {
|
||||
if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg && messages[messages.size() - 1].type == p_type) {
|
||||
// If previous message is the same as the new one, increase previous count rather than adding another
|
||||
// instance to the messages list.
|
||||
LogMessage &previous = messages.write[messages.size() - 1];
|
||||
previous.count++;
|
||||
|
||||
_add_log_line(previous, collapse);
|
||||
} else {
|
||||
// Different message to the previous one received.
|
||||
LogMessage message(p_msg, p_type, p_clear);
|
||||
_add_log_line(message);
|
||||
messages.push_back(message);
|
||||
}
|
||||
|
||||
type_filter_map[p_type]->set_message_count(type_filter_map[p_type]->get_message_count() + 1);
|
||||
}
|
||||
|
||||
void EditorLog::add_message(const String &p_msg, MessageType p_type) {
|
||||
// Make text split by new lines their own message.
|
||||
// See #41321 for reasoning. At time of writing, multiple print()'s in running projects
|
||||
// get grouped together and sent to the editor log as one message. This can mess with the
|
||||
// search functionality (see the comments on the PR above for more details). This behavior
|
||||
// also matches that of other IDE's.
|
||||
Vector<String> lines = p_msg.split("\n", true);
|
||||
int line_count = lines.size();
|
||||
|
||||
for (int i = 0; i < line_count; i++) {
|
||||
_process_message(lines[i], p_type, i == line_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::set_tool_button(Button *p_tool_button) {
|
||||
tool_button = p_tool_button;
|
||||
}
|
||||
|
||||
void EditorLog::register_undo_redo(UndoRedo *p_undo_redo) {
|
||||
p_undo_redo->set_commit_notify_callback(_undo_redo_cbk, this);
|
||||
}
|
||||
|
||||
void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) {
|
||||
EditorLog *self = static_cast<EditorLog *>(p_self);
|
||||
self->add_message(p_name, EditorLog::MSG_TYPE_EDITOR);
|
||||
}
|
||||
|
||||
void EditorLog::_rebuild_log() {
|
||||
if (messages.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log->clear();
|
||||
|
||||
int line_count = 0;
|
||||
int start_message_index = 0;
|
||||
int initial_skip = 0;
|
||||
|
||||
// Search backward for starting place.
|
||||
for (start_message_index = messages.size() - 1; start_message_index >= 0; start_message_index--) {
|
||||
LogMessage msg = messages[start_message_index];
|
||||
if (collapse) {
|
||||
if (_check_display_message(msg)) {
|
||||
line_count++;
|
||||
}
|
||||
} else {
|
||||
// If not collapsing, log each instance on a line.
|
||||
for (int i = 0; i < msg.count; i++) {
|
||||
if (_check_display_message(msg)) {
|
||||
line_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (line_count >= line_limit) {
|
||||
initial_skip = line_count - line_limit;
|
||||
break;
|
||||
}
|
||||
if (start_message_index == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int msg_idx = start_message_index; msg_idx < messages.size(); msg_idx++) {
|
||||
LogMessage msg = messages[msg_idx];
|
||||
|
||||
if (collapse) {
|
||||
// If collapsing, only log one instance of the message.
|
||||
_add_log_line(msg);
|
||||
} else {
|
||||
// If not collapsing, log each instance on a line.
|
||||
for (int i = initial_skip; i < msg.count; i++) {
|
||||
initial_skip = 0;
|
||||
_add_log_line(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorLog::_check_display_message(LogMessage &p_message) {
|
||||
bool filter_active = type_filter_map[p_message.type]->is_active();
|
||||
String search_text = search_box->get_text();
|
||||
bool search_match = search_text.is_empty() || p_message.text.containsn(search_text);
|
||||
return filter_active && search_match;
|
||||
}
|
||||
|
||||
void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
|
||||
if (!is_inside_tree()) {
|
||||
// The log will be built all at once when it enters the tree and has its theme items.
|
||||
return;
|
||||
}
|
||||
|
||||
if (unlikely(log->is_updating())) {
|
||||
// The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption.
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add the message to the log if it passes the filters.
|
||||
if (!_check_display_message(p_message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_replace_previous) {
|
||||
// Remove last line if replacing, as it will be replace by the next added line.
|
||||
// Why "- 2"? RichTextLabel is weird. When you add a line with add_newline(), it also adds an element to the list of lines which is null/blank,
|
||||
// but it still counts as a line. So if you remove the last line (count - 1) you are actually removing nothing...
|
||||
log->remove_paragraph(log->get_paragraph_count() - 2);
|
||||
}
|
||||
|
||||
switch (p_message.type) {
|
||||
case MSG_TYPE_STD: {
|
||||
} break;
|
||||
case MSG_TYPE_STD_RICH: {
|
||||
} break;
|
||||
case MSG_TYPE_ERROR: {
|
||||
log->push_color(theme_cache.error_color);
|
||||
Ref<Texture2D> icon = theme_cache.error_icon;
|
||||
log->add_image(icon);
|
||||
log->add_text(" ");
|
||||
tool_button->set_icon(icon);
|
||||
} break;
|
||||
case MSG_TYPE_WARNING: {
|
||||
log->push_color(theme_cache.warning_color);
|
||||
Ref<Texture2D> icon = theme_cache.warning_icon;
|
||||
log->add_image(icon);
|
||||
log->add_text(" ");
|
||||
tool_button->set_icon(icon);
|
||||
} break;
|
||||
case MSG_TYPE_EDITOR: {
|
||||
// Distinguish editor messages from messages printed by the project
|
||||
log->push_color(theme_cache.message_color);
|
||||
} break;
|
||||
}
|
||||
|
||||
// If collapsing, add the count of this message in bold at the start of the line.
|
||||
if (collapse && p_message.count > 1) {
|
||||
log->push_bold();
|
||||
log->add_text(vformat("(%s) ", itos(p_message.count)));
|
||||
log->pop();
|
||||
}
|
||||
|
||||
if (p_message.type == MSG_TYPE_STD_RICH) {
|
||||
log->append_text(p_message.text);
|
||||
} else {
|
||||
log->add_text(p_message.text);
|
||||
}
|
||||
if (p_message.clear || p_message.type != MSG_TYPE_STD_RICH) {
|
||||
log->pop_all(); // Pop all unclosed tags.
|
||||
}
|
||||
log->add_newline();
|
||||
|
||||
if (p_replace_previous) {
|
||||
// Force sync last line update (skip if number of unprocessed log messages is too large to avoid editor lag).
|
||||
if (log->get_pending_paragraphs() < 100) {
|
||||
while (!log->is_ready()) {
|
||||
::OS::get_singleton()->delay_usec(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (log->get_paragraph_count() > line_limit + 1) {
|
||||
log->remove_paragraph(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLog::_set_filter_active(bool p_active, MessageType p_message_type) {
|
||||
type_filter_map[p_message_type]->set_active(p_active);
|
||||
_start_state_save_timer();
|
||||
_rebuild_log();
|
||||
}
|
||||
|
||||
void EditorLog::_set_search_visible(bool p_visible) {
|
||||
search_box->set_visible(p_visible);
|
||||
if (p_visible) {
|
||||
search_box->grab_focus();
|
||||
}
|
||||
_start_state_save_timer();
|
||||
}
|
||||
|
||||
void EditorLog::_search_changed(const String &p_text) {
|
||||
_rebuild_log();
|
||||
}
|
||||
|
||||
void EditorLog::_reset_message_counts() {
|
||||
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
|
||||
E.value->set_message_count(0);
|
||||
}
|
||||
}
|
||||
|
||||
EditorLog::EditorLog() {
|
||||
save_state_timer = memnew(Timer);
|
||||
save_state_timer->set_wait_time(2);
|
||||
save_state_timer->set_one_shot(true);
|
||||
save_state_timer->connect("timeout", callable_mp(this, &EditorLog::_save_state));
|
||||
add_child(save_state_timer);
|
||||
|
||||
line_limit = int(EDITOR_GET("run/output/max_lines"));
|
||||
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorLog::_editor_settings_changed));
|
||||
|
||||
HBoxContainer *hb = this;
|
||||
|
||||
VBoxContainer *vb_left = memnew(VBoxContainer);
|
||||
vb_left->set_custom_minimum_size(Size2(0, 180) * EDSCALE);
|
||||
vb_left->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
vb_left->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hb->add_child(vb_left);
|
||||
|
||||
// Log - Rich Text Label.
|
||||
log = memnew(RichTextLabel);
|
||||
log->set_threaded(true);
|
||||
log->set_use_bbcode(true);
|
||||
log->set_scroll_follow(true);
|
||||
log->set_selection_enabled(true);
|
||||
log->set_context_menu_enabled(true);
|
||||
log->set_focus_mode(FOCUS_CLICK);
|
||||
log->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
log->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
log->set_deselect_on_focus_loss_enabled(false);
|
||||
log->connect("meta_clicked", callable_mp(this, &EditorLog::_meta_clicked));
|
||||
vb_left->add_child(log);
|
||||
|
||||
// Search box
|
||||
search_box = memnew(LineEdit);
|
||||
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
search_box->set_placeholder(TTR("Filter Messages"));
|
||||
search_box->set_clear_button_enabled(true);
|
||||
search_box->set_visible(true);
|
||||
search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorLog::_search_changed));
|
||||
vb_left->add_child(search_box);
|
||||
|
||||
VBoxContainer *vb_right = memnew(VBoxContainer);
|
||||
hb->add_child(vb_right);
|
||||
|
||||
// Tools grid
|
||||
HBoxContainer *hb_tools = memnew(HBoxContainer);
|
||||
hb_tools->set_h_size_flags(SIZE_SHRINK_CENTER);
|
||||
vb_right->add_child(hb_tools);
|
||||
|
||||
// Clear.
|
||||
clear_button = memnew(Button);
|
||||
clear_button->set_theme_type_variation("FlatButton");
|
||||
clear_button->set_focus_mode(FOCUS_NONE);
|
||||
clear_button->set_shortcut(ED_SHORTCUT("editor/clear_output", TTR("Clear Output"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::K));
|
||||
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorLog::_clear_request));
|
||||
hb_tools->add_child(clear_button);
|
||||
|
||||
// Copy.
|
||||
copy_button = memnew(Button);
|
||||
copy_button->set_theme_type_variation("FlatButton");
|
||||
copy_button->set_focus_mode(FOCUS_NONE);
|
||||
copy_button->set_shortcut(ED_SHORTCUT("editor/copy_output", TTR("Copy Selection"), KeyModifierMask::CMD_OR_CTRL | Key::C));
|
||||
copy_button->set_shortcut_context(this);
|
||||
copy_button->connect(SceneStringName(pressed), callable_mp(this, &EditorLog::_copy_request));
|
||||
hb_tools->add_child(copy_button);
|
||||
|
||||
// Separate toggle buttons from normal buttons.
|
||||
vb_right->add_child(memnew(HSeparator));
|
||||
|
||||
// A second hbox to make a 2x2 grid of buttons.
|
||||
HBoxContainer *hb_tools2 = memnew(HBoxContainer);
|
||||
hb_tools2->set_h_size_flags(SIZE_SHRINK_CENTER);
|
||||
vb_right->add_child(hb_tools2);
|
||||
|
||||
// Collapse.
|
||||
collapse_button = memnew(Button);
|
||||
collapse_button->set_theme_type_variation("FlatButton");
|
||||
collapse_button->set_focus_mode(FOCUS_NONE);
|
||||
collapse_button->set_tooltip_text(TTR("Collapse duplicate messages into one log entry. Shows number of occurrences."));
|
||||
collapse_button->set_toggle_mode(true);
|
||||
collapse_button->set_pressed(false);
|
||||
collapse_button->connect("toggled", callable_mp(this, &EditorLog::_set_collapse));
|
||||
hb_tools2->add_child(collapse_button);
|
||||
|
||||
// Show Search.
|
||||
show_search_button = memnew(Button);
|
||||
show_search_button->set_theme_type_variation("FlatButton");
|
||||
show_search_button->set_focus_mode(FOCUS_NONE);
|
||||
show_search_button->set_toggle_mode(true);
|
||||
show_search_button->set_pressed(true);
|
||||
show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTR("Focus Search/Filter Bar"), KeyModifierMask::CMD_OR_CTRL | Key::F));
|
||||
show_search_button->set_shortcut_context(this);
|
||||
show_search_button->connect("toggled", callable_mp(this, &EditorLog::_set_search_visible));
|
||||
hb_tools2->add_child(show_search_button);
|
||||
|
||||
// Message Type Filters.
|
||||
vb_right->add_child(memnew(HSeparator));
|
||||
|
||||
LogFilter *std_filter = memnew(LogFilter(MSG_TYPE_STD));
|
||||
std_filter->initialize_button(TTR("Toggle visibility of standard output messages."), callable_mp(this, &EditorLog::_set_filter_active));
|
||||
vb_right->add_child(std_filter->toggle_button);
|
||||
type_filter_map.insert(MSG_TYPE_STD, std_filter);
|
||||
type_filter_map.insert(MSG_TYPE_STD_RICH, std_filter);
|
||||
|
||||
LogFilter *error_filter = memnew(LogFilter(MSG_TYPE_ERROR));
|
||||
error_filter->initialize_button(TTR("Toggle visibility of errors."), callable_mp(this, &EditorLog::_set_filter_active));
|
||||
vb_right->add_child(error_filter->toggle_button);
|
||||
type_filter_map.insert(MSG_TYPE_ERROR, error_filter);
|
||||
|
||||
LogFilter *warning_filter = memnew(LogFilter(MSG_TYPE_WARNING));
|
||||
warning_filter->initialize_button(TTR("Toggle visibility of warnings."), callable_mp(this, &EditorLog::_set_filter_active));
|
||||
vb_right->add_child(warning_filter->toggle_button);
|
||||
type_filter_map.insert(MSG_TYPE_WARNING, warning_filter);
|
||||
|
||||
LogFilter *editor_filter = memnew(LogFilter(MSG_TYPE_EDITOR));
|
||||
editor_filter->initialize_button(TTR("Toggle visibility of editor messages."), callable_mp(this, &EditorLog::_set_filter_active));
|
||||
vb_right->add_child(editor_filter->toggle_button);
|
||||
type_filter_map.insert(MSG_TYPE_EDITOR, editor_filter);
|
||||
|
||||
add_message(VERSION_FULL_NAME " (c) 2007-present Juan Linietsky, Ariel Manzur & Godot Contributors.");
|
||||
|
||||
eh.errfunc = _error_handler;
|
||||
eh.userdata = this;
|
||||
add_error_handler(&eh);
|
||||
}
|
||||
|
||||
void EditorLog::deinit() {
|
||||
remove_error_handler(&eh);
|
||||
}
|
||||
|
||||
EditorLog::~EditorLog() {
|
||||
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
|
||||
// MSG_TYPE_STD_RICH is connected to the std_filter button, so we do this
|
||||
// to avoid it from being deleted twice, causing a crash on closing.
|
||||
if (E.key != MSG_TYPE_STD_RICH) {
|
||||
memdelete(E.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
202
engine/editor/editor_log.h
Normal file
202
engine/editor/editor_log.h
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/**************************************************************************/
|
||||
/* editor_log.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_LOG_H
|
||||
#define EDITOR_LOG_H
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/rich_text_label.h"
|
||||
#include "scene/gui/texture_button.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
class UndoRedo;
|
||||
|
||||
class EditorLog : public HBoxContainer {
|
||||
GDCLASS(EditorLog, HBoxContainer);
|
||||
|
||||
public:
|
||||
enum MessageType {
|
||||
MSG_TYPE_STD,
|
||||
MSG_TYPE_ERROR,
|
||||
MSG_TYPE_STD_RICH,
|
||||
MSG_TYPE_WARNING,
|
||||
MSG_TYPE_EDITOR,
|
||||
};
|
||||
|
||||
private:
|
||||
struct LogMessage {
|
||||
String text;
|
||||
MessageType type;
|
||||
int count = 1;
|
||||
bool clear = true;
|
||||
|
||||
LogMessage() {}
|
||||
|
||||
LogMessage(const String &p_text, MessageType p_type, bool p_clear) :
|
||||
text(p_text),
|
||||
type(p_type),
|
||||
clear(p_clear) {
|
||||
}
|
||||
};
|
||||
|
||||
struct {
|
||||
Color error_color;
|
||||
Ref<Texture2D> error_icon;
|
||||
|
||||
Color warning_color;
|
||||
Ref<Texture2D> warning_icon;
|
||||
|
||||
Color message_color;
|
||||
} theme_cache;
|
||||
|
||||
// Encapsulates all data and functionality regarding filters.
|
||||
struct LogFilter {
|
||||
private:
|
||||
// Force usage of set method since it has functionality built-in.
|
||||
int message_count = 0;
|
||||
bool active = true;
|
||||
|
||||
public:
|
||||
MessageType type;
|
||||
Button *toggle_button = nullptr;
|
||||
|
||||
void initialize_button(const String &p_tooltip, Callable p_toggled_callback) {
|
||||
toggle_button = memnew(Button);
|
||||
toggle_button->set_toggle_mode(true);
|
||||
toggle_button->set_pressed(true);
|
||||
toggle_button->set_text(itos(message_count));
|
||||
toggle_button->set_tooltip_text(TTR(p_tooltip));
|
||||
// Don't tint the icon even when in "pressed" state.
|
||||
toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1));
|
||||
toggle_button->set_focus_mode(FOCUS_NONE);
|
||||
// When toggled call the callback and pass the MessageType this button is for.
|
||||
toggle_button->connect("toggled", p_toggled_callback.bind(type));
|
||||
}
|
||||
|
||||
int get_message_count() {
|
||||
return message_count;
|
||||
}
|
||||
|
||||
void set_message_count(int p_count) {
|
||||
message_count = p_count;
|
||||
toggle_button->set_text(itos(message_count));
|
||||
}
|
||||
|
||||
bool is_active() {
|
||||
return active;
|
||||
}
|
||||
|
||||
void set_active(bool p_active) {
|
||||
toggle_button->set_pressed(p_active);
|
||||
active = p_active;
|
||||
}
|
||||
|
||||
LogFilter(MessageType p_type) :
|
||||
type(p_type) {
|
||||
}
|
||||
};
|
||||
|
||||
int line_limit = 10000;
|
||||
|
||||
Vector<LogMessage> messages;
|
||||
// Maps MessageTypes to LogFilters for convenient access and storage (don't need 1 member per filter).
|
||||
HashMap<MessageType, LogFilter *> type_filter_map;
|
||||
|
||||
RichTextLabel *log = nullptr;
|
||||
|
||||
Button *clear_button = nullptr;
|
||||
Button *copy_button = nullptr;
|
||||
|
||||
Button *collapse_button = nullptr;
|
||||
bool collapse = false;
|
||||
|
||||
Button *show_search_button = nullptr;
|
||||
LineEdit *search_box = nullptr;
|
||||
|
||||
// Reference to the "Output" button on the toolbar so we can update it's icon when
|
||||
// Warnings or Errors are encounetered.
|
||||
Button *tool_button = nullptr;
|
||||
|
||||
bool is_loading_state = false; // Used to disable saving requests while loading (some signals from buttons will try trigger a save, which happens during loading).
|
||||
Timer *save_state_timer = nullptr;
|
||||
|
||||
static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
|
||||
ErrorHandlerList eh;
|
||||
|
||||
//void _dragged(const Point2& p_ofs);
|
||||
void _meta_clicked(const String &p_meta);
|
||||
void _clear_request();
|
||||
void _copy_request();
|
||||
static void _undo_redo_cbk(void *p_self, const String &p_name);
|
||||
|
||||
void _rebuild_log();
|
||||
void _add_log_line(LogMessage &p_message, bool p_replace_previous = false);
|
||||
bool _check_display_message(LogMessage &p_message);
|
||||
|
||||
void _set_filter_active(bool p_active, MessageType p_message_type);
|
||||
void _set_search_visible(bool p_visible);
|
||||
void _search_changed(const String &p_text);
|
||||
|
||||
void _process_message(const String &p_msg, MessageType p_type, bool p_clear);
|
||||
void _reset_message_counts();
|
||||
|
||||
void _set_collapse(bool p_collapse);
|
||||
|
||||
void _start_state_save_timer();
|
||||
void _save_state();
|
||||
void _load_state();
|
||||
|
||||
void _update_theme();
|
||||
void _editor_settings_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void add_message(const String &p_msg, MessageType p_type = MSG_TYPE_STD);
|
||||
void set_tool_button(Button *p_tool_button);
|
||||
void register_undo_redo(UndoRedo *p_undo_redo);
|
||||
void deinit();
|
||||
|
||||
void clear();
|
||||
|
||||
EditorLog();
|
||||
~EditorLog();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorLog::MessageType);
|
||||
|
||||
#endif // EDITOR_LOG_H
|
||||
160
engine/editor/editor_native_shader_source_visualizer.cpp
Normal file
160
engine/editor/editor_native_shader_source_visualizer.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/**************************************************************************/
|
||||
/* editor_native_shader_source_visualizer.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_native_shader_source_visualizer.h"
|
||||
|
||||
#include "editor/code_editor.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/text_edit.h"
|
||||
#include "servers/rendering/shader_language.h"
|
||||
|
||||
void EditorNativeShaderSourceVisualizer::_load_theme_settings() {
|
||||
syntax_highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
|
||||
syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
|
||||
syntax_highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/function_color"));
|
||||
syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/member_variable_color"));
|
||||
|
||||
syntax_highlighter->clear_keyword_colors();
|
||||
|
||||
List<String> keywords;
|
||||
ShaderLanguage::get_keyword_list(&keywords);
|
||||
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
|
||||
const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color");
|
||||
|
||||
for (const String &keyword : keywords) {
|
||||
if (ShaderLanguage::is_control_flow_keyword(keyword)) {
|
||||
syntax_highlighter->add_keyword_color(keyword, control_flow_keyword_color);
|
||||
} else {
|
||||
syntax_highlighter->add_keyword_color(keyword, keyword_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Colorize comments.
|
||||
const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
|
||||
syntax_highlighter->clear_color_regions();
|
||||
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
|
||||
syntax_highlighter->add_color_region("//", "", comment_color, true);
|
||||
|
||||
// Colorize preprocessor statements.
|
||||
const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color");
|
||||
syntax_highlighter->add_color_region("#", "", user_type_color, true);
|
||||
|
||||
syntax_highlighter->set_uint_suffix_enabled(true);
|
||||
}
|
||||
|
||||
void EditorNativeShaderSourceVisualizer::_inspect_shader(RID p_shader) {
|
||||
if (versions) {
|
||||
memdelete(versions);
|
||||
versions = nullptr;
|
||||
}
|
||||
|
||||
RS::ShaderNativeSourceCode nsc = RS::get_singleton()->shader_get_native_source_code(p_shader);
|
||||
|
||||
_load_theme_settings();
|
||||
|
||||
versions = memnew(TabContainer);
|
||||
versions->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
|
||||
versions->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
versions->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
for (int i = 0; i < nsc.versions.size(); i++) {
|
||||
TabContainer *vtab = memnew(TabContainer);
|
||||
vtab->set_name("Version " + itos(i));
|
||||
vtab->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
|
||||
vtab->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vtab->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
versions->add_child(vtab);
|
||||
for (int j = 0; j < nsc.versions[i].stages.size(); j++) {
|
||||
CodeEdit *code_edit = memnew(CodeEdit);
|
||||
code_edit->set_editable(false);
|
||||
code_edit->set_syntax_highlighter(syntax_highlighter);
|
||||
code_edit->add_theme_font_override(SceneStringName(font), get_theme_font("source", EditorStringName(EditorFonts)));
|
||||
code_edit->add_theme_font_size_override(SceneStringName(font_size), get_theme_font_size("source_size", EditorStringName(EditorFonts)));
|
||||
code_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6));
|
||||
|
||||
// Appearance: Caret
|
||||
code_edit->set_caret_type((TextEdit::CaretType)EDITOR_GET("text_editor/appearance/caret/type").operator int());
|
||||
code_edit->set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink"));
|
||||
code_edit->set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval"));
|
||||
code_edit->set_highlight_current_line(EDITOR_GET("text_editor/appearance/caret/highlight_current_line"));
|
||||
code_edit->set_highlight_all_occurrences(EDITOR_GET("text_editor/appearance/caret/highlight_all_occurrences"));
|
||||
|
||||
// Appearance: Gutters
|
||||
code_edit->set_draw_line_numbers(EDITOR_GET("text_editor/appearance/gutters/show_line_numbers"));
|
||||
code_edit->set_line_numbers_zero_padded(EDITOR_GET("text_editor/appearance/gutters/line_numbers_zero_padded"));
|
||||
|
||||
// Appearance: Minimap
|
||||
code_edit->set_draw_minimap(EDITOR_GET("text_editor/appearance/minimap/show_minimap"));
|
||||
code_edit->set_minimap_width((int)EDITOR_GET("text_editor/appearance/minimap/minimap_width") * EDSCALE);
|
||||
|
||||
// Appearance: Lines
|
||||
code_edit->set_line_folding_enabled(EDITOR_GET("text_editor/appearance/lines/code_folding"));
|
||||
code_edit->set_draw_fold_gutter(EDITOR_GET("text_editor/appearance/lines/code_folding"));
|
||||
code_edit->set_line_wrapping_mode((TextEdit::LineWrappingMode)EDITOR_GET("text_editor/appearance/lines/word_wrap").operator int());
|
||||
code_edit->set_autowrap_mode((TextServer::AutowrapMode)EDITOR_GET("text_editor/appearance/lines/autowrap_mode").operator int());
|
||||
|
||||
// Appearance: Whitespace
|
||||
code_edit->set_draw_tabs(EDITOR_GET("text_editor/appearance/whitespace/draw_tabs"));
|
||||
code_edit->set_draw_spaces(EDITOR_GET("text_editor/appearance/whitespace/draw_spaces"));
|
||||
code_edit->add_theme_constant_override("line_spacing", EDITOR_GET("text_editor/appearance/whitespace/line_spacing"));
|
||||
|
||||
// Behavior: Navigation
|
||||
code_edit->set_scroll_past_end_of_file_enabled(EDITOR_GET("text_editor/behavior/navigation/scroll_past_end_of_file"));
|
||||
code_edit->set_smooth_scroll_enabled(EDITOR_GET("text_editor/behavior/navigation/smooth_scrolling"));
|
||||
code_edit->set_v_scroll_speed(EDITOR_GET("text_editor/behavior/navigation/v_scroll_speed"));
|
||||
code_edit->set_drag_and_drop_selection_enabled(EDITOR_GET("text_editor/behavior/navigation/drag_and_drop_selection"));
|
||||
|
||||
// Behavior: Indent
|
||||
code_edit->set_indent_size(EDITOR_GET("text_editor/behavior/indent/size"));
|
||||
code_edit->set_auto_indent_enabled(EDITOR_GET("text_editor/behavior/indent/auto_indent"));
|
||||
code_edit->set_indent_wrapped_lines(EDITOR_GET("text_editor/behavior/indent/indent_wrapped_lines"));
|
||||
|
||||
code_edit->set_name(nsc.versions[i].stages[j].name);
|
||||
code_edit->set_text(nsc.versions[i].stages[j].code);
|
||||
code_edit->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
code_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vtab->add_child(code_edit);
|
||||
}
|
||||
}
|
||||
add_child(versions);
|
||||
popup_centered_ratio();
|
||||
}
|
||||
|
||||
void EditorNativeShaderSourceVisualizer::_bind_methods() {
|
||||
ClassDB::bind_method("_inspect_shader", &EditorNativeShaderSourceVisualizer::_inspect_shader);
|
||||
}
|
||||
|
||||
EditorNativeShaderSourceVisualizer::EditorNativeShaderSourceVisualizer() {
|
||||
syntax_highlighter.instantiate();
|
||||
|
||||
add_to_group("_native_shader_source_visualizer");
|
||||
set_title(TTR("Native Shader Source Inspector"));
|
||||
}
|
||||
53
engine/editor/editor_native_shader_source_visualizer.h
Normal file
53
engine/editor/editor_native_shader_source_visualizer.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************/
|
||||
/* editor_native_shader_source_visualizer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_NATIVE_SHADER_SOURCE_VISUALIZER_H
|
||||
#define EDITOR_NATIVE_SHADER_SOURCE_VISUALIZER_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/resources/syntax_highlighter.h"
|
||||
|
||||
class EditorNativeShaderSourceVisualizer : public AcceptDialog {
|
||||
GDCLASS(EditorNativeShaderSourceVisualizer, AcceptDialog)
|
||||
TabContainer *versions = nullptr;
|
||||
Ref<CodeHighlighter> syntax_highlighter;
|
||||
|
||||
void _load_theme_settings();
|
||||
void _inspect_shader(RID p_shader);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EditorNativeShaderSourceVisualizer();
|
||||
};
|
||||
|
||||
#endif // EDITOR_NATIVE_SHADER_SOURCE_VISUALIZER_H
|
||||
7966
engine/editor/editor_node.cpp
Normal file
7966
engine/editor/editor_node.cpp
Normal file
File diff suppressed because it is too large
Load diff
1022
engine/editor/editor_node.h
Normal file
1022
engine/editor/editor_node.h
Normal file
File diff suppressed because it is too large
Load diff
268
engine/editor/editor_paths.cpp
Normal file
268
engine/editor/editor_paths.cpp
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
/**************************************************************************/
|
||||
/* editor_paths.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_paths.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "main/main.h"
|
||||
|
||||
EditorPaths *EditorPaths::singleton = nullptr;
|
||||
|
||||
bool EditorPaths::are_paths_valid() const {
|
||||
return paths_valid;
|
||||
}
|
||||
|
||||
String EditorPaths::get_data_dir() const {
|
||||
return data_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_config_dir() const {
|
||||
return config_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_cache_dir() const {
|
||||
return cache_dir;
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_data_dir() const {
|
||||
return project_data_dir;
|
||||
}
|
||||
|
||||
bool EditorPaths::is_self_contained() const {
|
||||
return self_contained;
|
||||
}
|
||||
|
||||
String EditorPaths::get_self_contained_file() const {
|
||||
return self_contained_file;
|
||||
}
|
||||
|
||||
String EditorPaths::get_export_templates_dir() const {
|
||||
return get_data_dir().path_join(export_templates_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_debug_keystore_path() const {
|
||||
return get_data_dir().path_join("keystores/debug.keystore");
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_settings_dir() const {
|
||||
return get_project_data_dir().path_join("editor");
|
||||
}
|
||||
|
||||
String EditorPaths::get_text_editor_themes_dir() const {
|
||||
return get_config_dir().path_join(text_editor_themes_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_script_templates_dir() const {
|
||||
return get_config_dir().path_join(script_templates_folder);
|
||||
}
|
||||
|
||||
String EditorPaths::get_project_script_templates_dir() const {
|
||||
return GLOBAL_GET("editor/script/templates_search_path");
|
||||
}
|
||||
|
||||
String EditorPaths::get_feature_profiles_dir() const {
|
||||
return get_config_dir().path_join(feature_profiles_folder);
|
||||
}
|
||||
|
||||
void EditorPaths::create() {
|
||||
memnew(EditorPaths);
|
||||
}
|
||||
|
||||
void EditorPaths::free() {
|
||||
ERR_FAIL_NULL(singleton);
|
||||
memdelete(singleton);
|
||||
}
|
||||
|
||||
void EditorPaths::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_data_dir"), &EditorPaths::get_data_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_config_dir"), &EditorPaths::get_config_dir);
|
||||
ClassDB::bind_method(D_METHOD("get_cache_dir"), &EditorPaths::get_cache_dir);
|
||||
ClassDB::bind_method(D_METHOD("is_self_contained"), &EditorPaths::is_self_contained);
|
||||
ClassDB::bind_method(D_METHOD("get_self_contained_file"), &EditorPaths::get_self_contained_file);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_project_settings_dir"), &EditorPaths::get_project_settings_dir);
|
||||
}
|
||||
|
||||
EditorPaths::EditorPaths() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
|
||||
project_data_dir = ProjectSettings::get_singleton()->get_project_data_path();
|
||||
|
||||
// Self-contained mode if a `._sc_` or `_sc_` file is present in executable dir.
|
||||
String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
|
||||
Ref<DirAccess> d = DirAccess::create_for_path(exe_path);
|
||||
if (d->file_exists(exe_path + "/._sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/._sc_";
|
||||
} else if (d->file_exists(exe_path + "/_sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/_sc_";
|
||||
}
|
||||
|
||||
// On macOS, look outside .app bundle, since .app bundle is read-only.
|
||||
// Note: This will not work if Gatekeeper path randomization is active.
|
||||
if (OS::get_singleton()->has_feature("macos") && exe_path.ends_with("MacOS") && exe_path.path_join("..").simplify_path().ends_with("Contents")) {
|
||||
exe_path = exe_path.path_join("../../..").simplify_path();
|
||||
d = DirAccess::create_for_path(exe_path);
|
||||
if (d->file_exists(exe_path + "/._sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/._sc_";
|
||||
} else if (d->file_exists(exe_path + "/_sc_")) {
|
||||
self_contained = true;
|
||||
self_contained_file = exe_path + "/_sc_";
|
||||
}
|
||||
}
|
||||
|
||||
String data_path;
|
||||
String config_path;
|
||||
String cache_path;
|
||||
|
||||
if (self_contained) {
|
||||
// editor is self contained, all in same folder
|
||||
data_path = exe_path;
|
||||
data_dir = data_path.path_join("editor_data");
|
||||
config_path = exe_path;
|
||||
config_dir = data_dir;
|
||||
cache_path = exe_path;
|
||||
cache_dir = data_dir.path_join("cache");
|
||||
} else {
|
||||
// Typically XDG_DATA_HOME or %APPDATA%.
|
||||
data_path = OS::get_singleton()->get_data_path();
|
||||
data_dir = data_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
// Can be different from data_path e.g. on Linux or macOS.
|
||||
config_path = OS::get_singleton()->get_config_path();
|
||||
config_dir = config_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
// Can be different from above paths, otherwise a subfolder of data_dir.
|
||||
cache_path = OS::get_singleton()->get_cache_path();
|
||||
if (cache_path == data_path) {
|
||||
cache_dir = data_dir.path_join("cache");
|
||||
} else {
|
||||
cache_dir = cache_path.path_join(OS::get_singleton()->get_godot_dir_name());
|
||||
}
|
||||
}
|
||||
|
||||
paths_valid = (!data_path.is_empty() && !config_path.is_empty() && !cache_path.is_empty());
|
||||
ERR_FAIL_COND_MSG(!paths_valid, "Editor data, config, or cache paths are invalid.");
|
||||
|
||||
// Validate or create each dir and its relevant subdirectories.
|
||||
|
||||
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
|
||||
// Data dir.
|
||||
{
|
||||
if (dir->change_dir(data_dir) != OK) {
|
||||
dir->make_dir_recursive(data_dir);
|
||||
if (dir->change_dir(data_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor data directory: " + data_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir->dir_exists(export_templates_folder)) {
|
||||
dir->make_dir(export_templates_folder);
|
||||
}
|
||||
}
|
||||
|
||||
// Config dir.
|
||||
{
|
||||
if (dir->change_dir(config_dir) != OK) {
|
||||
dir->make_dir_recursive(config_dir);
|
||||
if (dir->change_dir(config_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor config directory: " + config_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir->dir_exists(text_editor_themes_folder)) {
|
||||
dir->make_dir(text_editor_themes_folder);
|
||||
}
|
||||
if (!dir->dir_exists(script_templates_folder)) {
|
||||
dir->make_dir(script_templates_folder);
|
||||
}
|
||||
if (!dir->dir_exists(feature_profiles_folder)) {
|
||||
dir->make_dir(feature_profiles_folder);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache dir.
|
||||
{
|
||||
if (dir->change_dir(cache_dir) != OK) {
|
||||
dir->make_dir_recursive(cache_dir);
|
||||
if (dir->change_dir(cache_dir) != OK) {
|
||||
ERR_PRINT("Could not create editor cache directory: " + cache_dir);
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate or create project-specific editor data dir,
|
||||
// including shader cache subdir.
|
||||
if (Engine::get_singleton()->is_project_manager_hint() || (Main::is_cmdline_tool() && !ProjectSettings::get_singleton()->is_project_loaded())) {
|
||||
// Nothing to create, use shared editor data dir for shader cache.
|
||||
Engine::get_singleton()->set_shader_cache_path(data_dir);
|
||||
} else {
|
||||
Ref<DirAccess> dir_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
if (dir_res->change_dir(project_data_dir) != OK) {
|
||||
dir_res->make_dir_recursive(project_data_dir);
|
||||
if (dir_res->change_dir(project_data_dir) != OK) {
|
||||
ERR_PRINT("Could not create project data directory (" + project_data_dir + ") in: " + dir_res->get_current_dir());
|
||||
paths_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the project data directory '.gdignore' file exists
|
||||
String project_data_gdignore_file_path = project_data_dir.path_join(".gdignore");
|
||||
if (!FileAccess::exists(project_data_gdignore_file_path)) {
|
||||
// Add an empty .gdignore file to avoid scan.
|
||||
Ref<FileAccess> f = FileAccess::open(project_data_gdignore_file_path, FileAccess::WRITE);
|
||||
if (f.is_valid()) {
|
||||
f->store_line("");
|
||||
} else {
|
||||
ERR_PRINT("Failed to create file " + project_data_gdignore_file_path);
|
||||
}
|
||||
}
|
||||
|
||||
Engine::get_singleton()->set_shader_cache_path(project_data_dir);
|
||||
|
||||
// Editor metadata dir.
|
||||
if (!dir_res->dir_exists("editor")) {
|
||||
dir_res->make_dir("editor");
|
||||
}
|
||||
// Imported assets dir.
|
||||
String imported_files_path = ProjectSettings::get_singleton()->get_imported_files_path();
|
||||
if (!dir_res->dir_exists(imported_files_path)) {
|
||||
dir_res->make_dir(imported_files_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
engine/editor/editor_paths.h
Normal file
86
engine/editor/editor_paths.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**************************************************************************/
|
||||
/* editor_paths.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_PATHS_H
|
||||
#define EDITOR_PATHS_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class EditorPaths : public Object {
|
||||
GDCLASS(EditorPaths, Object)
|
||||
|
||||
bool paths_valid = false; // If any of the paths can't be created, this is false.
|
||||
String data_dir; // Editor data (templates, shader cache, etc.).
|
||||
String config_dir; // Editor config (settings, profiles, themes, etc.).
|
||||
String cache_dir; // Editor cache (thumbnails, tmp generated files).
|
||||
String project_data_dir; // Project-specific data (metadata, shader cache, etc.).
|
||||
bool self_contained = false; // Self-contained means everything goes to `editor_data` dir.
|
||||
String self_contained_file; // Self-contained file with configuration.
|
||||
String export_templates_folder = "export_templates";
|
||||
String text_editor_themes_folder = "text_editor_themes";
|
||||
String script_templates_folder = "script_templates";
|
||||
String feature_profiles_folder = "feature_profiles";
|
||||
|
||||
static EditorPaths *singleton;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
bool are_paths_valid() const;
|
||||
|
||||
String get_data_dir() const;
|
||||
String get_config_dir() const;
|
||||
String get_cache_dir() const;
|
||||
String get_project_data_dir() const;
|
||||
String get_export_templates_dir() const;
|
||||
String get_debug_keystore_path() const;
|
||||
String get_project_settings_dir() const;
|
||||
String get_text_editor_themes_dir() const;
|
||||
String get_script_templates_dir() const;
|
||||
String get_project_script_templates_dir() const;
|
||||
String get_feature_profiles_dir() const;
|
||||
|
||||
bool is_self_contained() const;
|
||||
String get_self_contained_file() const;
|
||||
|
||||
static EditorPaths *get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
static void create();
|
||||
static void free();
|
||||
|
||||
EditorPaths();
|
||||
};
|
||||
|
||||
#endif // EDITOR_PATHS_H
|
||||
3913
engine/editor/editor_properties.cpp
Normal file
3913
engine/editor/editor_properties.cpp
Normal file
File diff suppressed because it is too large
Load diff
749
engine/editor/editor_properties.h
Normal file
749
engine/editor/editor_properties.h
Normal file
|
|
@ -0,0 +1,749 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_PROPERTIES_H
|
||||
#define EDITOR_PROPERTIES_H
|
||||
|
||||
#include "editor/editor_inspector.h"
|
||||
|
||||
class CheckBox;
|
||||
class ColorPickerButton;
|
||||
class CreateDialog;
|
||||
class EditorFileDialog;
|
||||
class EditorLocaleDialog;
|
||||
class EditorResourcePicker;
|
||||
class EditorSpinSlider;
|
||||
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 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;
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
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;
|
||||
EditorFileDialog *dialog = nullptr;
|
||||
LineEdit *path = nullptr;
|
||||
Button *path_edit = nullptr;
|
||||
|
||||
void _path_selected(const String &p_path);
|
||||
void _path_pressed();
|
||||
void _path_focus_exited();
|
||||
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;
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(const Vector<String> &p_extensions, bool p_folder, bool p_global);
|
||||
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:
|
||||
static void _bind_methods();
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertySignal();
|
||||
};
|
||||
|
||||
class EditorPropertyCallable : public EditorProperty {
|
||||
GDCLASS(EditorPropertyCallable, EditorProperty);
|
||||
Button *edit = nullptr;
|
||||
String base_type;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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);
|
||||
static void _bind_methods();
|
||||
|
||||
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 _popup_closed();
|
||||
void _picker_opening();
|
||||
|
||||
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;
|
||||
|
||||
Vector<StringName> valid_types;
|
||||
void _node_selected(const NodePath &p_path);
|
||||
void _node_assign();
|
||||
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;
|
||||
|
||||
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();
|
||||
|
||||
protected:
|
||||
virtual void _set_read_only(bool p_read_only) override;
|
||||
void _notification(int p_what);
|
||||
|
||||
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 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);
|
||||
};
|
||||
|
||||
#endif // EDITOR_PROPERTIES_H
|
||||
1411
engine/editor/editor_properties_array_dict.cpp
Normal file
1411
engine/editor/editor_properties_array_dict.cpp
Normal file
File diff suppressed because it is too large
Load diff
282
engine/editor/editor_properties_array_dict.h
Normal file
282
engine/editor/editor_properties_array_dict.h
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EDITOR_PROPERTIES_ARRAY_DICT_H
|
||||
#define EDITOR_PROPERTIES_ARRAY_DICT_H
|
||||
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/editor_locale_dialog.h"
|
||||
#include "editor/filesystem_dock.h"
|
||||
|
||||
class Button;
|
||||
class EditorSpinSlider;
|
||||
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();
|
||||
|
||||
EditorPropertyArrayObject();
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
EditorPropertyDictionaryObject();
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
PopupMenu *change_type = nullptr;
|
||||
|
||||
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;
|
||||
|
||||
static void _bind_methods();
|
||||
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 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 = "");
|
||||
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;
|
||||
bool as_id = false;
|
||||
EditorProperty *prop = nullptr;
|
||||
String prop_name;
|
||||
|
||||
void set_index(int p_idx) {
|
||||
index = p_idx;
|
||||
prop_name = object->get_property_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 update_prop_or_index() {
|
||||
prop->set_object_and_property(object.ptr(), prop_name);
|
||||
prop->set_label(object->get_label_for_index(index));
|
||||
}
|
||||
};
|
||||
|
||||
PopupMenu *change_type = nullptr;
|
||||
bool updating = 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;
|
||||
PropertyHint property_hint;
|
||||
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);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void setup(PropertyHint p_hint);
|
||||
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);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual void update_property() override;
|
||||
EditorPropertyLocalizableString();
|
||||
};
|
||||
|
||||
#endif // EDITOR_PROPERTIES_ARRAY_DICT_H
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue