initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

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

6
editor/settings/SCsub Normal file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
env.add_source_files(env.editor_sources, "*.cpp")

View File

@@ -0,0 +1,613 @@
/**************************************************************************/
/* 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 "action_map_editor.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_event_search_bar.h"
#include "editor/settings/editor_settings.h"
#include "editor/settings/event_listener_line_edit.h"
#include "editor/settings/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) {
const 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;
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::_on_search_bar_value_changed() {
if (action_list_search_bar->is_searching()) {
show_builtin_actions_checkbutton->set_pressed_no_signal(true);
show_builtin_actions_checkbutton->set_disabled(true);
show_builtin_actions_checkbutton->set_tooltip_text(TTRC("Built-in actions are always shown when searching."));
} else {
show_builtin_actions_checkbutton->set_pressed_no_signal(show_builtin_actions);
show_builtin_actions_checkbutton->set_disabled(false);
show_builtin_actions_checkbutton->set_tooltip_text(String());
}
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));
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
action_tree->set_drag_preview(label);
get_viewport()->gui_set_drag_description(vformat(RTR("Action %s"), name));
Dictionary drag_data;
if (selected->has_meta("__action")) {
drag_data["input_type"] = "action";
}
if (selected->has_meta("__event")) {
drag_data["input_type"] = "event";
}
drag_data["source"] = selected->get_instance_id();
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 *source = Object::cast_to<TreeItem>(ObjectDB::get_instance(d["source"].operator ObjectID()));
TreeItem *selected = action_tree->get_selected();
TreeItem *item = (p_point == Vector2(Math::INF, Math::INF)) ? selected : action_tree->get_item_at_position(p_point);
if (!selected || !item || item == source) {
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 = (p_point == Vector2(Math::INF, Math::INF)) ? selected : action_tree->get_item_at_position(p_point);
if (!target) {
return;
}
bool drop_above = ((p_point == Vector2(Math::INF, Math::INF)) ? action_tree->get_drop_section_at_position(action_tree->get_item_rect(target).position) : action_tree->get_drop_section_at_position(p_point)) == -1;
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_TRANSLATION_CHANGED: {
if (!actions_cache.is_empty()) {
update_action_list();
}
if (!add_button->get_tooltip_text().is_empty()) {
_add_edit_text_changed(add_edit->get_text());
}
} break;
case NOTIFICATION_THEME_CHANGED: {
add_button->set_button_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")));
}
LineEdit *ActionMapEditor::get_search_box() const {
return action_list_search_bar->get_name_search_box();
}
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_bar->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_bar->get_name().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;
}
HashSet<String> collapsed_actions;
TreeItem *root = action_tree->get_root();
if (root) {
for (TreeItem *child = root->get_first_child(); child; child = child->get_next()) {
if (child->is_collapsed()) {
collapsed_actions.insert(child->get_meta("__name"));
}
}
}
action_tree->clear();
root = action_tree->create_item();
for (const ActionInfo &action_info : actions_cache) {
const Array events = action_info.action["events"];
if (!_should_display_action(action_info.name, events)) {
continue;
}
if (!action_info.editable && !action_list_search_bar->is_searching() && !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);
action_item->set_collapsed(collapsed_actions.has(action_info.name));
// First Column - Action Name
action_item->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
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, get_editor_theme_icon(SNAME("ReloadSmall")), BUTTON_REVERT_ACTION, action_eq, action_eq ? TTRC("Cannot Revert - Action is same as initial") : TTRC("Revert Action"));
}
action_item->add_button(2, get_editor_theme_icon(SNAME("Add")), BUTTON_ADD_EVENT, false, TTRC("Add Event"));
action_item->add_button(2, get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_ACTION, !action_info.editable, action_info.editable ? TTRC("Remove Action") : TTRC("Cannot Remove Action"));
action_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)));
action_item->set_custom_bg_color(1, 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_auto_translate_mode(0, AUTO_TRANSLATE_MODE_DISABLED);
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, get_editor_theme_icon(SNAME("KeyboardLabel")));
} else if (k->get_keycode() != Key::NONE) {
event_item->set_icon(0, get_editor_theme_icon(SNAME("Keyboard")));
} else if (k->get_physical_keycode() != Key::NONE) {
event_item->set_icon(0, get_editor_theme_icon(SNAME("KeyboardPhysical")));
} else {
event_item->set_icon(0, get_editor_theme_icon(SNAME("KeyboardError")));
}
}
Ref<InputEventMouseButton> mb = event;
if (mb.is_valid()) {
event_item->set_icon(0, get_editor_theme_icon(SNAME("Mouse")));
}
Ref<InputEventJoypadButton> jb = event;
if (jb.is_valid()) {
event_item->set_icon(0, get_editor_theme_icon(SNAME("JoyButton")));
}
Ref<InputEventJoypadMotion> jm = event;
if (jm.is_valid()) {
event_item->set_icon(0, get_editor_theme_icon(SNAME("JoyAxis")));
}
// Third Column - Buttons
event_item->add_button(2, get_editor_theme_icon(SNAME("Edit")), BUTTON_EDIT_EVENT, false, TTRC("Edit Event"), TTRC("Edit Event"));
event_item->add_button(2, get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE_EVENT, false, TTRC("Remove Event"), TTRC("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));
}
}
}
void ActionMapEditor::show_message(const String &p_message) {
message->set_text(p_message);
message->popup_centered();
}
ActionMapEditor::ActionMapEditor() {
// Main Vbox Container
VBoxContainer *main_vbox = memnew(VBoxContainer);
main_vbox->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
add_child(main_vbox);
action_list_search_bar = memnew(EditorEventSearchBar);
action_list_search_bar->connect(SceneStringName(value_changed), callable_mp(this, &ActionMapEditor::_on_search_bar_value_changed));
main_vbox->add_child(action_list_search_bar);
// 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(TTRC("Add New Action"));
add_edit->set_accessibility_name(TTRC("Add New Action"));
add_edit->set_clear_button_enabled(true);
add_edit->set_keep_editing_on_text_submit(true);
add_edit->connect(SceneStringName(text_changed), callable_mp(this, &ActionMapEditor::_add_edit_text_changed));
add_edit->connect(SceneStringName(text_submitted), callable_mp(this, &ActionMapEditor::_add_action));
add_hbox->add_child(add_edit);
add_button = memnew(Button);
add_button->set_text(TTRC("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(TTRC("Show Built-in Actions"));
show_builtin_actions_checkbutton->connect(SceneStringName(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_no_signal(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_accessibility_name(TTRC("Action Map"));
action_tree->set_columns(3);
action_tree->set_hide_root(true);
action_tree->set_column_titles_visible(true);
action_tree->set_column_title(0, TTRC("Action"));
action_tree->set_column_clip_content(0, true);
action_tree->set_column_title(1, TTRC("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);
}

View File

@@ -0,0 +1,126 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "scene/gui/control.h"
class AcceptDialog;
class Button;
class CheckButton;
class EditorEventSearchBar;
class EventListenerLineEdit;
class HBoxContainer;
class InputEventConfigurationDialog;
class LineEdit;
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;
EditorEventSearchBar *action_list_search_bar = 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 _on_search_bar_value_changed();
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 _set_show_builtin_actions(bool p_show);
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);
ActionMapEditor();
};

View File

@@ -0,0 +1,999 @@
/**************************************************************************/
/* 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/docks/filesystem_dock.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/settings/project_settings_editor.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_button_icon(get_editor_theme_icon(SNAME("Folder")));
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
if (!error_message->get_text().is_empty()) {
_autoload_text_changed(autoload_add_name->get_text());
}
} break;
case NOTIFICATION_THEME_CHANGED: {
browse_button->set_button_icon(get_editor_theme_icon(SNAME("Folder")));
add_autoload->set_button_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_unicode_identifier()) {
if (r_error) {
*r_error = TTR("Invalid name.") + " " + TTR("Must be a valid Unicode identifier.");
}
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;
}
if (Variant::get_type_by_name(p_name) < Variant::VARIANT_MAX) {
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++) {
for (const String &keyword : ScriptServer::get_language(i)->get_reserved_words()) {
if (keyword == 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_slicec('/', 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);
}
// 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) {
EditorNode::get_singleton()->load_scene_or_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_null(), 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) {
// It's important to add the node without deferring because code in plugins or tool scripts
// could use the autoload node when they are enabled.
get_tree()->get_root()->add_child(info.node);
}
}
}
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_slicec('/', 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);
}
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, TTRC("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();
}
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.is_empty() || 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)));
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
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 = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_selected() : tree->get_item_at_position(p_point);
if (!ti) {
return false;
}
int section = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_drop_section_at_position(tree->get_item_rect(ti).position) : 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 = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_selected() : tree->get_item_at_position(p_point);
if (!ti) {
return;
}
int section = (p_point == Vector2(Math::INF, Math::INF)) ? tree->get_drop_section_at_position(tree->get_item_rect(ti).position) : 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"];
// Store the initial order of the autoloads for comparison.
Vector<int> initial_orders;
initial_orders.resize(autoload_cache.size());
int idx = 0;
for (const AutoloadInfo &F : autoload_cache) {
initial_orders.write[idx++] = F.order;
}
// Perform the drag-and-drop operation.
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;
}
}
idx = 0;
for (const AutoloadInfo &F : autoload_cache) {
orders.write[idx++] = F.order;
}
// If the order didn't change, we shouldn't create undo/redo actions.
if (orders == initial_orders) {
return;
}
orders.sort();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Rearrange Autoloads"));
idx = 0;
for (const AutoloadInfo &F : autoload_cache) {
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + F.name, orders[idx++]);
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_slicec('/', 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);
}
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->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
error_message->set_focus_mode(FOCUS_ACCESSIBILITY);
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(TTRC("Path:"));
hbc->add_child(l);
autoload_add_path = memnew(LineEdit);
hbc->add_child(autoload_add_path);
autoload_add_path->set_accessibility_name(TTRC("Autoload 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(TTRC("Set path or press \"Add\" to create a script."));
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->set_accessibility_name(TTRC("Select Autoload Path"));
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(TTRC("Node Name:"));
hbc->add_child(l);
autoload_add_name = memnew(LineEdit);
autoload_add_name->set_accessibility_name(TTRC("Node Name:"));
autoload_add_name->set_h_size_flags(SIZE_EXPAND_FILL);
autoload_add_name->connect(SceneStringName(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(TTRC("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_accessibility_name(TTRC("Autoloads"));
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, TTRC("Name"));
tree->set_column_title_alignment(0, HORIZONTAL_ALIGNMENT_LEFT);
tree->set_column_expand(0, true);
tree->set_column_expand_ratio(0, 1);
tree->set_column_title(1, TTRC("Path"));
tree->set_column_title_alignment(1, HORIZONTAL_ALIGNMENT_LEFT);
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, TTRC("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(SceneStringName(text_submitted), p_text);
}
void EditorAutoloadSettings::_browse_autoload_add_path() {
file_dialog->popup_file_dialog();
}

View File

@@ -0,0 +1,115 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
#include "editor/doc/editor_help.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
class EditorBuildProfile : public RefCounted {
GDCLASS(EditorBuildProfile, RefCounted);
public:
enum BuildOption {
BUILD_OPTION_3D,
BUILD_OPTION_NAVIGATION_2D,
BUILD_OPTION_NAVIGATION_3D,
BUILD_OPTION_XR,
BUILD_OPTION_OPENXR,
BUILD_OPTION_WAYLAND,
BUILD_OPTION_X11,
BUILD_OPTION_RENDERING_DEVICE,
BUILD_OPTION_FORWARD_RENDERER,
BUILD_OPTION_MOBILE_RENDERER,
BUILD_OPTION_VULKAN,
BUILD_OPTION_D3D12,
BUILD_OPTION_METAL,
BUILD_OPTION_OPENGL,
BUILD_OPTION_PHYSICS_2D,
BUILD_OPTION_PHYSICS_GODOT_2D,
BUILD_OPTION_PHYSICS_3D,
BUILD_OPTION_PHYSICS_GODOT_3D,
BUILD_OPTION_PHYSICS_JOLT,
BUILD_OPTION_TEXT_SERVER_FALLBACK,
BUILD_OPTION_TEXT_SERVER_ADVANCED,
BUILD_OPTION_DYNAMIC_FONTS,
BUILD_OPTION_WOFF2_FONTS,
BUILD_OPTION_GRAPHITE_FONTS,
BUILD_OPTION_MSDFGEN,
BUILD_OPTION_MAX,
};
enum BuildOptionCategory {
BUILD_OPTION_CATEGORY_GENERAL,
BUILD_OPTION_CATEGORY_GRAPHICS,
BUILD_OPTION_CATEGORY_PHYSICS,
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 bool build_option_explicit_use[BUILD_OPTION_MAX];
static const BuildOptionCategory build_option_category[BUILD_OPTION_MAX];
static const HashMap<BuildOption, LocalVector<BuildOption>> build_option_dependencies;
static HashMap<BuildOption, HashMap<String, LocalVector<Variant>>> build_option_settings;
static const HashMap<BuildOption, LocalVector<String>> build_option_classes;
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 reset_build_options();
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 String get_build_option_identifier(BuildOption p_build_option);
static bool get_build_option_disable_value(BuildOption p_build_option);
static bool get_build_option_explicit_use(BuildOption p_build_option);
static BuildOptionCategory get_build_option_category(BuildOption p_build_option);
static LocalVector<BuildOption> get_build_option_dependencies(BuildOption p_build_option);
static HashMap<String, LocalVector<Variant>> get_build_option_settings(BuildOption p_build_option);
static LocalVector<String> get_build_option_classes(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);
bool project_scan_canceled = false;
void _detect_from_project();
void _force_detect_classes_changed(const String &p_text);
struct DetectedFile {
uint32_t timestamp = 0;
String md5;
Vector<String> classes;
Vector<String> build_deps;
};
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();
};

View File

@@ -0,0 +1,390 @@
/**************************************************************************/
/* 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_command_palette.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_toaster.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/control.h"
#include "scene/gui/line_edit.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_slicec('/', 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_event) {
// Redirect navigational key events to the tree.
Ref<InputEventKey> key = p_event;
if (key.is_valid()) {
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
search_options->gui_input(key);
command_search_box->accept_event();
}
}
}
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 {
_update_command_search(String());
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());
for (const KeyValue<Variant, Variant> &history_kv : command_history) {
const String &history_key = history_kv.key;
if (commands.has(history_key)) {
commands[history_key].last_used = history_kv.value;
}
}
}
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->set_accessibility_name(TTRC("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;
}

View File

@@ -0,0 +1,104 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/input/shortcut.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_event);
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 = "");

View File

@@ -0,0 +1,109 @@
/**************************************************************************/
/* editor_event_search_bar.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_event_search_bar.h"
#include "editor/settings/event_listener_line_edit.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
void EditorEventSearchBar::_on_event_changed(const Ref<InputEvent> &p_event) {
if (p_event.is_valid() && (!p_event->is_pressed() || p_event->is_echo())) {
return;
}
_value_changed();
}
void EditorEventSearchBar::_on_clear_all() {
search_by_name->set_block_signals(true);
search_by_name->clear();
search_by_name->set_block_signals(false);
search_by_event->set_block_signals(true);
search_by_event->clear_event();
search_by_event->set_block_signals(false);
_value_changed();
}
void EditorEventSearchBar::_value_changed() {
clear_all->set_disabled(!is_searching());
emit_signal(SceneStringName(value_changed));
}
bool EditorEventSearchBar::is_searching() const {
return !get_name().is_empty() || get_event().is_valid();
}
String EditorEventSearchBar::get_name() const {
return search_by_name->get_text().strip_edges();
}
Ref<InputEvent> EditorEventSearchBar::get_event() const {
return search_by_event->get_event();
}
void EditorEventSearchBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
search_by_name->set_right_icon(get_editor_theme_icon(SNAME("Search")));
} break;
}
}
void EditorEventSearchBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("value_changed"));
}
EditorEventSearchBar::EditorEventSearchBar() {
set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_by_name = memnew(LineEdit);
search_by_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_by_name->set_placeholder(TTRC("Filter by Name"));
search_by_name->set_accessibility_name(TTRC("Filter by Name"));
search_by_name->set_clear_button_enabled(true);
search_by_name->connect(SceneStringName(text_changed), callable_mp(this, &EditorEventSearchBar::_value_changed).unbind(1));
add_child(search_by_name);
search_by_event = memnew(EventListenerLineEdit);
search_by_event->set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_by_event->set_stretch_ratio(0.75);
search_by_event->set_accessibility_name(TTRC("Action Event"));
search_by_event->connect("event_changed", callable_mp(this, &EditorEventSearchBar::_on_event_changed));
add_child(search_by_event);
clear_all = memnew(Button(TTRC("Clear All")));
clear_all->set_tooltip_text(TTRC("Clear all search filters."));
clear_all->connect(SceneStringName(pressed), callable_mp(this, &EditorEventSearchBar::_on_clear_all));
clear_all->set_disabled(true);
add_child(clear_all);
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************/
/* editor_event_search_bar.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
class Button;
class EventListenerLineEdit;
class LineEdit;
class EditorEventSearchBar : public HBoxContainer {
GDCLASS(EditorEventSearchBar, HBoxContainer);
LineEdit *search_by_name = nullptr;
EventListenerLineEdit *search_by_event = nullptr;
Button *clear_all = nullptr;
void _on_event_changed(const Ref<InputEvent> &p_event);
void _on_clear_all();
void _value_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
LineEdit *get_name_search_box() const { return search_by_name; }
bool is_searching() const;
String get_name() const;
Ref<InputEvent> get_event() const;
EditorEventSearchBar();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#include "core/object/ref_counted.h"
#include "editor/doc/editor_help.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/option_button.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_GAME,
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();
};

View File

@@ -0,0 +1,296 @@
/**************************************************************************/
/* 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/file_system/editor_paths.h"
#include "editor/inspector/editor_inspector.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, 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->get_owner())) {
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_char('/');
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);
}

View File

@@ -0,0 +1,54 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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);
};

View 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/settings/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;
}
Vector<String> layouts = config->get_sections();
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_accessibility_name(TTRC("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);
}

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#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);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
/**************************************************************************/
/* editor_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. */
/**************************************************************************/
#pragma once
#include "core/input/shortcut.h"
#include "core/io/config_file.h"
#include "core/io/resource.h"
#include "core/os/thread_safe.h"
class EditorPlugin;
class EditorSettings : public Resource {
GDCLASS(EditorSettings, Resource);
_THREAD_SAFE_CLASS_
public:
struct Plugin {
EditorPlugin *instance = nullptr;
String path;
String name;
String author;
String version;
String description;
bool installs = false;
String script;
Vector<String> install_files;
};
enum NetworkMode {
NETWORK_OFFLINE,
NETWORK_ONLINE,
};
enum InitialScreen {
INITIAL_SCREEN_AUTO = -5, // Remembers last screen position.
INITIAL_SCREEN_WITH_MOUSE_FOCUS = -4,
INITIAL_SCREEN_WITH_KEYBOARD_FOCUS = -3,
INITIAL_SCREEN_PRIMARY = -2,
};
private:
struct VariantContainer {
int order = 0;
Variant variant;
Variant initial;
bool basic = false;
bool has_default_value = false;
bool hide_from_editor = false;
bool save = false;
bool restart_if_changed = false;
VariantContainer() {}
VariantContainer(const Variant &p_variant, int p_order) :
order(p_order),
variant(p_variant) {
}
};
static Ref<EditorSettings> singleton;
HashSet<String> changed_settings;
mutable Ref<ConfigFile> project_metadata;
HashMap<String, PropertyInfo> hints;
HashMap<String, VariantContainer> props;
int last_order;
Ref<Resource> clipboard;
mutable HashMap<String, Ref<Shortcut>> shortcuts;
HashMap<String, List<Ref<InputEvent>>> builtin_action_overrides;
Vector<String> favorites;
HashMap<String, PackedStringArray> favorite_properties;
Vector<String> recent_dirs;
bool save_changed_setting = true;
bool optimize_save = true; //do not save stuff that came from config but was not set from engine
bool initialized = false;
bool _set(const StringName &p_name, const Variant &p_value);
bool _set_only(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _initial_set(const StringName &p_name, const Variant &p_value, bool p_basic = false);
void _get_property_list(List<PropertyInfo> *p_list) const;
void _add_property_info_bind(const Dictionary &p_info);
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
void _set_initialized();
void _load_defaults(Ref<ConfigFile> p_extra_config = Ref<ConfigFile>());
void _load_default_visual_shader_editor_theme();
static String _guess_exec_args_for_extenal_editor(const String &p_value);
const String _get_project_metadata_path() const;
#ifndef DISABLE_DEPRECATED
void _remove_deprecated_settings();
#endif
protected:
static void _bind_methods();
public:
enum {
NOTIFICATION_EDITOR_SETTINGS_CHANGED = 10000
};
static EditorSettings *get_singleton();
static String get_existing_settings_path();
static String get_newest_settings_path();
static void create();
void setup_language();
void setup_network();
static void save();
static void destroy();
void set_optimize_save(bool p_optimize);
bool has_default_value(const String &p_setting) const;
void set_setting(const String &p_setting, const Variant &p_value);
Variant get_setting(const String &p_setting) const;
bool has_setting(const String &p_setting) const;
void erase(const String &p_setting);
void raise_order(const String &p_setting);
void set_initial_value(const StringName &p_setting, const Variant &p_value, bool p_update_current = false);
void set_restart_if_changed(const StringName &p_setting, bool p_restart);
void set_basic(const StringName &p_setting, bool p_basic);
void set_manually(const StringName &p_setting, const Variant &p_value, bool p_emit_signal = false) {
if (p_emit_signal) {
_set(p_setting, p_value);
} else {
_set_only(p_setting, p_value);
}
}
void add_property_hint(const PropertyInfo &p_hint);
PackedStringArray get_changed_settings() const;
bool check_changed_settings_in_group(const String &p_setting_prefix) const;
void mark_setting_changed(const String &p_setting);
void set_resource_clipboard(const Ref<Resource> &p_resource) { clipboard = p_resource; }
Ref<Resource> get_resource_clipboard() const { return clipboard; }
void set_project_metadata(const String &p_section, const String &p_key, const Variant &p_data);
Variant get_project_metadata(const String &p_section, const String &p_key, const Variant &p_default) const;
void set_favorites(const Vector<String> &p_favorites);
Vector<String> get_favorites() const;
void set_favorite_properties(const HashMap<String, PackedStringArray> &p_favorite_properties);
HashMap<String, PackedStringArray> get_favorite_properties() const;
void set_recent_dirs(const Vector<String> &p_recent_dirs);
Vector<String> get_recent_dirs() const;
void load_favorites_and_recent_dirs();
static HashMap<StringName, Color> get_godot2_text_editor_theme();
static bool is_default_text_editor_theme(const String &p_theme_name);
void update_text_editor_themes_list();
Vector<String> get_script_templates(const String &p_extension, const String &p_custom_path = String());
String get_editor_layouts_config() const;
static float get_auto_display_scale();
void _add_shortcut_default(const String &p_name, const Ref<Shortcut> &p_shortcut);
void add_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut);
bool is_shortcut(const String &p_name, const Ref<InputEvent> &p_event) const;
Ref<Shortcut> get_shortcut(const String &p_name) const;
void get_shortcut_list(List<String> *r_shortcuts);
void set_builtin_action_override(const String &p_name, const TypedArray<InputEvent> &p_events);
const Array get_builtin_action_overrides(const String &p_name) const;
void notify_changes();
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
EditorSettings();
};
//not a macro any longer
#define EDITOR_DEF(m_var, m_val) _EDITOR_DEF(m_var, Variant(m_val))
#define EDITOR_DEF_RST(m_var, m_val) _EDITOR_DEF(m_var, Variant(m_val), true)
#define EDITOR_DEF_BASIC(m_var, m_val) _EDITOR_DEF(m_var, Variant(m_val), false, true)
Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default, bool p_restart_if_changed = false, bool p_basic = false);
#define EDITOR_GET(m_var) _EDITOR_GET(m_var)
Variant _EDITOR_GET(const String &p_setting);
#define ED_IS_SHORTCUT(p_name, p_ev) (EditorSettings::get_singleton()->is_shortcut(p_name, p_ev))
Ref<Shortcut> ED_SHORTCUT(const String &p_path, const String &p_name, Key p_keycode = Key::NONE, bool p_physical = false);
Ref<Shortcut> ED_SHORTCUT_ARRAY(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, bool p_physical = false);
void ED_SHORTCUT_OVERRIDE(const String &p_path, const String &p_feature, Key p_keycode = Key::NONE, bool p_physical = false);
void ED_SHORTCUT_OVERRIDE_ARRAY(const String &p_path, const String &p_feature, const PackedInt32Array &p_keycodes, bool p_physical = false);
Ref<Shortcut> ED_GET_SHORTCUT(const String &p_path);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,178 @@
/**************************************************************************/
/* editor_settings_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "editor/inspector/editor_inspector.h"
#include "scene/gui/dialogs.h"
class CheckButton;
class EditorEventSearchBar;
class EventListenerLineEdit;
class InputEventConfigurationDialog;
class PanelContainer;
class SectionedInspector;
class TabContainer;
class TextureRect;
class Tree;
class TreeItem;
class EditorSettingsDialog : public AcceptDialog {
GDCLASS(EditorSettingsDialog, AcceptDialog);
bool updating = false;
TabContainer *tabs = nullptr;
Control *tab_general = nullptr;
Control *tab_shortcuts = nullptr;
LineEdit *search_box = nullptr;
CheckButton *advanced_switch = nullptr;
SectionedInspector *inspector = nullptr;
EditorEventSearchBar *shortcut_search_bar = nullptr;
// Shortcuts
enum ShortcutButton {
SHORTCUT_ADD,
SHORTCUT_EDIT,
SHORTCUT_ERASE,
SHORTCUT_REVERT
};
Tree *shortcuts = nullptr;
InputEventConfigurationDialog *shortcut_editor = nullptr;
bool is_editing_action = false;
String current_edited_identifier;
Array current_events;
int current_event_index = -1;
Timer *timer = nullptr;
virtual void cancel_pressed() override;
virtual void ok_pressed() override;
void _settings_changed();
void _settings_property_edited(const String &p_name);
void _settings_save();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
void _update_icons();
void _event_config_confirmed();
TreeItem *_create_shortcut_treeitem(TreeItem *p_parent, const String &p_shortcut_identifier, const String &p_display, Array &p_events, bool p_allow_revert, bool p_is_common, bool p_is_collapsed);
Array _event_list_to_array_helper(const List<Ref<InputEvent>> &p_events);
void _update_builtin_action(const String &p_name, const Array &p_events);
void _update_shortcut_events(const String &p_path, const Array &p_events);
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 _tabs_tab_changed(int p_tab);
void _focus_current_search_box();
void _advanced_toggled(bool p_button_pressed);
void _update_dynamic_property_hints();
PropertyInfo _create_mouse_shortcut_property_info(const String &p_property_name, const String &p_shortcut_1_name, const String &p_shortcut_2_name);
String _get_shortcut_button_string(const String &p_shortcut_name);
bool _should_display_shortcut(const String &p_name, const Array &p_events, bool p_match_localized_name) const;
void _update_shortcuts();
void _shortcut_button_pressed(Object *p_item, int p_column, int p_idx, MouseButton p_button = MouseButton::LEFT);
void _shortcut_cell_double_clicked();
static void _set_shortcut_input(const String &p_name, Ref<InputEventKey> &p_event);
static void _undo_redo_callback(void *p_self, const String &p_name);
Label *restart_label = nullptr;
TextureRect *restart_icon = nullptr;
PanelContainer *restart_container = nullptr;
Button *restart_close_button = nullptr;
void _editor_restart_request();
void _editor_restart();
void _editor_restart_close();
protected:
static void _bind_methods();
public:
void popup_edit_settings();
static void update_navigation_preset();
EditorSettingsDialog();
};
class EditorSettingsPropertyWrapper : public EditorProperty {
GDCLASS(EditorSettingsPropertyWrapper, EditorProperty);
String property;
EditorProperty *editor_property = nullptr;
BoxContainer *container = nullptr;
HBoxContainer *override_info = nullptr;
Label *override_label = nullptr;
Button *goto_button = nullptr;
Button *remove_button = nullptr;
bool requires_restart = false;
void _update_override();
void _create_override();
void _remove_override();
protected:
void _notification(int p_what);
public:
static inline Callable restart_request_callback;
virtual void update_property() override;
void setup(const String &p_property, EditorProperty *p_editor_property, bool p_requires_restart);
};
class EditorSettingsInspectorPlugin : public EditorInspectorPlugin {
GDCLASS(EditorSettingsInspectorPlugin, EditorInspectorPlugin);
Object *current_object = nullptr;
public:
SectionedInspector *inspector = nullptr;
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;
};

View File

@@ -0,0 +1,277 @@
/**************************************************************************/
/* event_listener_line_edit.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "event_listener_line_edit.h"
#include "core/input/input_map.h"
#include "scene/gui/dialogs.h"
// Maps to 2*axis if value is neg, or 2*axis+1 if value is pos.
static const char *_joy_axis_descriptions[(size_t)JoyAxis::MAX * 2] = {
TTRC("Left Stick Left, Joystick 0 Left"),
TTRC("Left Stick Right, Joystick 0 Right"),
TTRC("Left Stick Up, Joystick 0 Up"),
TTRC("Left Stick Down, Joystick 0 Down"),
TTRC("Right Stick Left, Joystick 1 Left"),
TTRC("Right Stick Right, Joystick 1 Right"),
TTRC("Right Stick Up, Joystick 1 Up"),
TTRC("Right Stick Down, Joystick 1 Down"),
TTRC("Joystick 2 Left"),
TTRC("Left Trigger, Sony L2, Xbox LT, Joystick 2 Right"),
TTRC("Joystick 2 Up"),
TTRC("Right Trigger, Sony R2, Xbox RT, Joystick 2 Down"),
TTRC("Joystick 3 Left"),
TTRC("Joystick 3 Right"),
TTRC("Joystick 3 Up"),
TTRC("Joystick 3 Down"),
TTRC("Joystick 4 Left"),
TTRC("Joystick 4 Right"),
TTRC("Joystick 4 Up"),
TTRC("Joystick 4 Down"),
};
String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, bool p_include_device) {
ERR_FAIL_COND_V_MSG(p_event.is_null(), String(), "Provided event is not a valid instance of InputEvent");
String text;
Ref<InputEventKey> key = p_event;
if (key.is_valid()) {
String mods_text = key->InputEventWithModifiers::as_text();
mods_text = mods_text.is_empty() ? mods_text : mods_text + "+";
if (key->is_command_or_control_autoremap()) {
if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
mods_text = mods_text.replace("Command", "Command/Ctrl");
} else {
mods_text = mods_text.replace("Ctrl", "Command/Ctrl");
}
}
if (key->get_keycode() != Key::NONE) {
text += mods_text + keycode_get_string(key->get_keycode());
}
if (key->get_physical_keycode() != Key::NONE) {
if (!text.is_empty()) {
text += " " + TTR("or") + " ";
}
text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical");
if (key->get_location() != KeyLocation::UNSPECIFIED) {
text += " " + key->as_text_location();
}
text += ")";
}
if (key->get_key_label() != Key::NONE) {
if (!text.is_empty()) {
text += " " + TTR("or") + " ";
}
text += mods_text + keycode_get_string(key->get_key_label()) + " (" + TTR("Unicode") + ")";
}
if (text.is_empty()) {
text = "(" + TTR("Unset") + ")";
}
} else {
text = p_event->as_text();
}
Ref<InputEventMouse> mouse = p_event;
Ref<InputEventJoypadMotion> jp_motion = p_event;
Ref<InputEventJoypadButton> jp_button = p_event;
if (jp_motion.is_valid()) {
// Joypad motion events will display slightly differently than what the event->as_text() provides. See #43660.
String desc = TTR("Unknown Joypad Axis");
if (jp_motion->get_axis() < JoyAxis::MAX) {
desc = TTR(_joy_axis_descriptions[2 * (size_t)jp_motion->get_axis() + (jp_motion->get_axis_value() < 0 ? 0 : 1)]);
}
// TRANSLATORS: %d is the axis number, the first %s is either "-" or "+", and the second %s is the description of the axis.
text = vformat(TTR("Joypad Axis %d %s (%s)"), (int64_t)jp_motion->get_axis(), jp_motion->get_axis_value() < 0 ? "-" : "+", desc);
}
if (p_include_device && (mouse.is_valid() || jp_button.is_valid() || jp_motion.is_valid())) {
String device_string = get_device_string(p_event->get_device());
text += vformat(" - %s", device_string);
}
return text;
}
String EventListenerLineEdit::get_device_string(int p_device) {
if (p_device == InputMap::ALL_DEVICES) {
return TTR("All Devices");
}
return TTR("Device") + " " + itos(p_device);
}
bool EventListenerLineEdit::_is_event_allowed(const Ref<InputEvent> &p_event) const {
const Ref<InputEventMouseButton> mb = p_event;
const Ref<InputEventKey> k = p_event;
const Ref<InputEventJoypadButton> jb = p_event;
const Ref<InputEventJoypadMotion> jm = p_event;
return (mb.is_valid() && (allowed_input_types & INPUT_MOUSE_BUTTON)) ||
(k.is_valid() && (allowed_input_types & INPUT_KEY)) ||
(jb.is_valid() && (allowed_input_types & INPUT_JOY_BUTTON)) ||
(jm.is_valid() && (allowed_input_types & INPUT_JOY_MOTION));
}
void EventListenerLineEdit::gui_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
LineEdit::gui_input(p_event);
return;
}
// Allow mouse button click on the clear button without being treated as an event.
const Ref<InputEventMouseButton> b = p_event;
if (b.is_valid() && _is_over_clear_button(b->get_position())) {
LineEdit::gui_input(p_event);
return;
}
// First event will be an event which is used to focus this control - i.e. a mouse click, or a tab press.
// Ignore the first one so that clicking into the LineEdit does not override the current event.
// Ignore is reset to true when the control is unfocused.
// This class also specially handles grab_focus() calls.
if (ignore_next_event) {
ignore_next_event = false;
return;
}
Ref<InputEvent> event_to_check = p_event;
// Allow releasing focus by holding "ui_cancel" action.
const uint64_t hold_to_unfocus_timeout = 3000;
if (p_event->is_action_pressed(SNAME("ui_cancel"), true, true)) {
if ((OS::get_singleton()->get_ticks_msec() - hold_next) < hold_to_unfocus_timeout) {
hold_next = 0;
Control *next = find_next_valid_focus();
next->grab_focus();
} else {
hold_next = OS::get_singleton()->get_ticks_msec();
hold_event = p_event;
}
accept_event();
return;
}
if (p_event->is_action_released(SNAME("ui_cancel"), true)) {
event_to_check = hold_event;
hold_next = 0;
hold_event = Ref<InputEvent>();
}
accept_event();
if (!event_to_check->is_pressed() || event_to_check->is_echo() || event_to_check->is_match(event) || !_is_event_allowed(event_to_check)) {
return;
}
event = event_to_check;
set_text(get_event_text(event, false));
emit_signal("event_changed", event);
}
void EventListenerLineEdit::_on_text_changed(const String &p_text) {
if (p_text.is_empty()) {
clear_event();
}
}
Ref<InputEvent> EventListenerLineEdit::get_event() const {
return event;
}
void EventListenerLineEdit::clear_event() {
if (event.is_valid()) {
event = Ref<InputEvent>();
set_text("");
emit_signal("event_changed", event);
}
}
void EventListenerLineEdit::set_allowed_input_types(int p_type_masks) {
allowed_input_types = p_type_masks;
}
int EventListenerLineEdit::get_allowed_input_types() const {
return allowed_input_types;
}
void EventListenerLineEdit::grab_focus() {
// If we grab focus through code, we don't need to ignore the first event!
ignore_next_event = false;
Control::grab_focus();
}
void EventListenerLineEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_extra_info(ae, vformat(TTR("Listening for Input. Hold %s to release focus."), InputMap::get_singleton()->get_action_description("ui_cancel")));
} break;
case NOTIFICATION_THEME_CHANGED: {
set_right_icon(get_editor_theme_icon(SNAME("Keyboard")));
} break;
case NOTIFICATION_ENTER_TREE: {
connect(SceneStringName(text_changed), callable_mp(this, &EventListenerLineEdit::_on_text_changed));
set_clear_button_enabled(true);
} break;
case NOTIFICATION_FOCUS_ENTER: {
AcceptDialog *dialog = Object::cast_to<AcceptDialog>(get_window());
if (dialog) {
dialog->set_close_on_escape(false);
}
set_placeholder(TTRC("Listening for Input"));
} break;
case NOTIFICATION_FOCUS_EXIT: {
AcceptDialog *dialog = Object::cast_to<AcceptDialog>(get_window());
if (dialog) {
dialog->set_close_on_escape(true);
}
ignore_next_event = true;
set_placeholder(TTRC("Filter by Event"));
} break;
}
}
void EventListenerLineEdit::_bind_methods() {
// `event` is either null or a valid InputEvent that is pressed and non-echo.
ADD_SIGNAL(MethodInfo("event_changed", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
}
EventListenerLineEdit::EventListenerLineEdit() {
set_caret_blink_enabled(false);
set_placeholder(TTRC("Filter by Event"));
}

View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* event_listener_line_edit.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/line_edit.h"
enum InputType {
INPUT_KEY = 1,
INPUT_MOUSE_BUTTON = 2,
INPUT_JOY_BUTTON = 4,
INPUT_JOY_MOTION = 8
};
class EventListenerLineEdit : public LineEdit {
GDCLASS(EventListenerLineEdit, LineEdit)
uint64_t hold_next = 0;
Ref<InputEvent> hold_event;
int allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION;
bool ignore_next_event = true;
bool share_keycodes = false;
Ref<InputEvent> event;
bool _is_event_allowed(const Ref<InputEvent> &p_event) const;
void gui_input(const Ref<InputEvent> &p_event) override;
void _on_text_changed(const String &p_text);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
static String get_event_text(const Ref<InputEvent> &p_event, bool p_include_device);
static String get_device_string(int p_device);
Ref<InputEvent> get_event() const;
void clear_event();
void set_allowed_input_types(int p_type_masks);
int get_allowed_input_types() const;
void grab_focus();
public:
EventListenerLineEdit();
};

View File

@@ -0,0 +1,777 @@
/**************************************************************************/
/* input_event_configuration_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 "input_event_configuration_dialog.h"
#include "core/input/input_map.h"
#include "editor/editor_string_names.h"
#include "editor/settings/event_listener_line_edit.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/check_box.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection) {
if (p_event.is_valid()) {
event = p_event;
original_event = p_original_event;
// If the event is changed to something which is not the same as the listener,
// clear out the event from the listener text box to avoid confusion.
const Ref<InputEvent> listener_event = event_listener->get_event();
if (listener_event.is_valid() && !listener_event->is_match(p_event)) {
event_listener->clear_event();
}
// Update Label
event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true));
Ref<InputEventKey> k = p_event;
Ref<InputEventMouseButton> mb = p_event;
Ref<InputEventJoypadButton> joyb = p_event;
Ref<InputEventJoypadMotion> joym = p_event;
Ref<InputEventWithModifiers> mod = p_event;
// Update option values and visibility
bool show_mods = false;
bool show_device = false;
bool show_key = false;
bool show_location = false;
if (mod.is_valid()) {
show_mods = true;
mod_checkboxes[MOD_ALT]->set_pressed(mod->is_alt_pressed());
mod_checkboxes[MOD_SHIFT]->set_pressed(mod->is_shift_pressed());
mod_checkboxes[MOD_CTRL]->set_pressed(mod->is_ctrl_pressed());
mod_checkboxes[MOD_META]->set_pressed(mod->is_meta_pressed());
autoremap_command_or_control_checkbox->set_pressed(mod->is_command_or_control_autoremap());
}
if (k.is_valid()) {
show_key = true;
Key phys_key = k->get_physical_keycode();
if (k->get_keycode() == Key::NONE && phys_key == Key::NONE && k->get_key_label() != Key::NONE) {
key_mode->select(KEYMODE_UNICODE);
} else if (k->get_keycode() != Key::NONE) {
key_mode->select(KEYMODE_KEYCODE);
} else if (phys_key != Key::NONE) {
key_mode->select(KEYMODE_PHY_KEYCODE);
if (phys_key == Key::SHIFT || phys_key == Key::CTRL || phys_key == Key::ALT || phys_key == Key::META) {
key_location->select((int)k->get_location());
show_location = true;
}
} else {
// Invalid key.
event = Ref<InputEvent>();
original_event = Ref<InputEvent>();
event_listener->clear_event();
event_as_text->set_text(TTRC("No Event Configured"));
additional_options_container->hide();
input_list_tree->deselect_all();
_update_input_list();
return;
}
} else if (joyb.is_valid() || joym.is_valid() || mb.is_valid()) {
show_device = true;
_set_current_device(event->get_device());
}
mod_container->set_visible(show_mods);
device_container->set_visible(show_device);
key_mode->set_visible(show_key);
location_container->set_visible(show_location);
additional_options_container->show();
// Update mode selector based on original key event.
Ref<InputEventKey> ko = p_original_event;
if (ko.is_valid()) {
if (ko->get_keycode() == Key::NONE) {
if (ko->get_physical_keycode() != Key::NONE) {
ko->set_keycode(ko->get_physical_keycode());
}
if (ko->get_key_label() != Key::NONE) {
ko->set_keycode(fix_keycode((char32_t)ko->get_key_label(), Key::NONE));
}
}
if (ko->get_physical_keycode() == Key::NONE) {
if (ko->get_keycode() != Key::NONE) {
ko->set_physical_keycode(ko->get_keycode());
}
if (ko->get_key_label() != Key::NONE) {
ko->set_physical_keycode(fix_keycode((char32_t)ko->get_key_label(), Key::NONE));
}
}
if (ko->get_key_label() == Key::NONE) {
if (ko->get_keycode() != Key::NONE) {
ko->set_key_label(fix_key_label((char32_t)ko->get_keycode(), Key::NONE));
}
if (ko->get_physical_keycode() != Key::NONE) {
ko->set_key_label(fix_key_label((char32_t)ko->get_physical_keycode(), Key::NONE));
}
}
key_mode->set_item_disabled(KEYMODE_KEYCODE, ko->get_keycode() == Key::NONE);
key_mode->set_item_disabled(KEYMODE_PHY_KEYCODE, ko->get_physical_keycode() == Key::NONE);
key_mode->set_item_disabled(KEYMODE_UNICODE, ko->get_key_label() == Key::NONE);
}
// Update selected item in input list.
if (p_update_input_list_selection && (k.is_valid() || joyb.is_valid() || joym.is_valid() || mb.is_valid())) {
in_tree_update = true;
TreeItem *category = input_list_tree->get_root()->get_first_child();
while (category) {
TreeItem *input_item = category->get_first_child();
if (input_item != nullptr) {
// input_type should always be > 0, unless the tree structure has been misconfigured.
int input_type = input_item->get_parent()->get_meta("__type", 0);
if (input_type == 0) {
in_tree_update = false;
return;
}
// If event type matches input types of this category.
if ((k.is_valid() && input_type == INPUT_KEY) || (joyb.is_valid() && input_type == INPUT_JOY_BUTTON) || (joym.is_valid() && input_type == INPUT_JOY_MOTION) || (mb.is_valid() && input_type == INPUT_MOUSE_BUTTON)) {
// Loop through all items of this category until one matches.
while (input_item) {
bool key_match = k.is_valid() && (Variant(k->get_keycode()) == input_item->get_meta("__keycode") || Variant(k->get_physical_keycode()) == input_item->get_meta("__keycode"));
bool joyb_match = joyb.is_valid() && Variant(joyb->get_button_index()) == input_item->get_meta("__index");
bool joym_match = joym.is_valid() && Variant(joym->get_axis()) == input_item->get_meta("__axis") && joym->get_axis_value() == (float)input_item->get_meta("__value");
bool mb_match = mb.is_valid() && Variant(mb->get_button_index()) == input_item->get_meta("__index");
if (key_match || joyb_match || joym_match || mb_match) {
category->set_collapsed(false);
input_item->select(0);
input_list_tree->ensure_cursor_is_visible();
in_tree_update = false;
return;
}
input_item = input_item->get_next();
}
}
}
category->set_collapsed(true); // Event not in this category, so collapse;
category = category->get_next();
}
in_tree_update = false;
}
} else {
// Event is not valid, reset dialog
event = Ref<InputEvent>();
original_event = Ref<InputEvent>();
event_listener->clear_event();
event_as_text->set_text(TTRC("No Event Configured"));
additional_options_container->hide();
input_list_tree->deselect_all();
_update_input_list();
}
}
void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEvent> &p_event) {
// Ignore if invalid, echo or not pressed
if (p_event.is_null() || p_event->is_echo() || !p_event->is_pressed()) {
return;
}
// Create an editable reference and a copy of full event.
Ref<InputEvent> received_event = p_event;
Ref<InputEvent> received_original_event = received_event->duplicate();
// Check what the type is and if it is allowed.
Ref<InputEventKey> k = received_event;
Ref<InputEventJoypadButton> joyb = received_event;
Ref<InputEventJoypadMotion> joym = received_event;
Ref<InputEventMouseButton> mb = received_event;
int type = 0;
if (k.is_valid()) {
type = INPUT_KEY;
} else if (joyb.is_valid()) {
type = INPUT_JOY_BUTTON;
} else if (joym.is_valid()) {
type = INPUT_JOY_MOTION;
} else if (mb.is_valid()) {
type = INPUT_MOUSE_BUTTON;
}
if (!(allowed_input_types & type)) {
return;
}
if (joym.is_valid()) {
joym->set_axis_value(SIGN(joym->get_axis_value()));
}
if (k.is_valid()) {
k->set_pressed(false); // To avoid serialization of 'pressed' property - doesn't matter for actions anyway.
if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
k->set_physical_keycode(Key::NONE);
k->set_key_label(Key::NONE);
} else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
k->set_keycode(Key::NONE);
k->set_key_label(Key::NONE);
} else if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
k->set_physical_keycode(Key::NONE);
k->set_keycode(Key::NONE);
}
if (key_location->get_selected_id() == (int)KeyLocation::UNSPECIFIED) {
k->set_location(KeyLocation::UNSPECIFIED);
}
}
Ref<InputEventWithModifiers> mod = received_event;
if (mod.is_valid()) {
mod->set_window_id(0);
}
// Maintain device selection.
received_event->set_device(_get_current_device());
_set_event(received_event, received_original_event);
}
void InputEventConfigurationDialog::_search_term_updated(const String &) {
_update_input_list();
}
void InputEventConfigurationDialog::_update_input_list() {
input_list_tree->clear();
TreeItem *root = input_list_tree->create_item();
String search_term = input_list_search->get_text();
bool collapse = input_list_search->get_text().is_empty();
if (allowed_input_types & INPUT_KEY) {
TreeItem *kb_root = input_list_tree->create_item(root);
kb_root->set_text(0, TTR("Keyboard Keys"));
kb_root->set_icon(0, icon_cache.keyboard);
kb_root->set_collapsed(collapse);
kb_root->set_meta("__type", INPUT_KEY);
for (int i = 0; i < keycode_get_count(); i++) {
String name = keycode_get_name_by_index(i);
if (!search_term.is_empty() && !name.containsn(search_term)) {
continue;
}
TreeItem *item = input_list_tree->create_item(kb_root);
item->set_text(0, name);
item->set_meta("__keycode", keycode_get_value_by_index(i));
}
}
if (allowed_input_types & INPUT_MOUSE_BUTTON) {
TreeItem *mouse_root = input_list_tree->create_item(root);
mouse_root->set_text(0, TTR("Mouse Buttons"));
mouse_root->set_icon(0, icon_cache.mouse);
mouse_root->set_collapsed(collapse);
mouse_root->set_meta("__type", INPUT_MOUSE_BUTTON);
MouseButton mouse_buttons[9] = { MouseButton::LEFT, MouseButton::RIGHT, MouseButton::MIDDLE, MouseButton::WHEEL_UP, MouseButton::WHEEL_DOWN, MouseButton::WHEEL_LEFT, MouseButton::WHEEL_RIGHT, MouseButton::MB_XBUTTON1, MouseButton::MB_XBUTTON2 };
for (int i = 0; i < 9; i++) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_button_index(mouse_buttons[i]);
String desc = EventListenerLineEdit::get_event_text(mb, false);
if (!search_term.is_empty() && !desc.containsn(search_term)) {
continue;
}
TreeItem *item = input_list_tree->create_item(mouse_root);
item->set_text(0, desc);
item->set_meta("__index", mouse_buttons[i]);
}
}
if (allowed_input_types & INPUT_JOY_BUTTON) {
TreeItem *joyb_root = input_list_tree->create_item(root);
joyb_root->set_text(0, TTR("Joypad Buttons"));
joyb_root->set_icon(0, icon_cache.joypad_button);
joyb_root->set_collapsed(collapse);
joyb_root->set_meta("__type", INPUT_JOY_BUTTON);
for (int i = 0; i < (int)JoyButton::MAX; i++) {
Ref<InputEventJoypadButton> joyb;
joyb.instantiate();
joyb->set_button_index((JoyButton)i);
String desc = EventListenerLineEdit::get_event_text(joyb, false);
if (!search_term.is_empty() && !desc.containsn(search_term)) {
continue;
}
TreeItem *item = input_list_tree->create_item(joyb_root);
item->set_text(0, desc);
item->set_meta("__index", i);
}
}
if (allowed_input_types & INPUT_JOY_MOTION) {
TreeItem *joya_root = input_list_tree->create_item(root);
joya_root->set_text(0, TTR("Joypad Axes"));
joya_root->set_icon(0, icon_cache.joypad_axis);
joya_root->set_collapsed(collapse);
joya_root->set_meta("__type", INPUT_JOY_MOTION);
for (int i = 0; i < (int)JoyAxis::MAX * 2; i++) {
int axis = i / 2;
int direction = (i & 1) ? 1 : -1;
Ref<InputEventJoypadMotion> joym;
joym.instantiate();
joym->set_axis((JoyAxis)axis);
joym->set_axis_value(direction);
String desc = EventListenerLineEdit::get_event_text(joym, false);
if (!search_term.is_empty() && !desc.containsn(search_term)) {
continue;
}
TreeItem *item = input_list_tree->create_item(joya_root);
item->set_text(0, desc);
item->set_meta("__axis", i >> 1);
item->set_meta("__value", (i & 1) ? 1 : -1);
}
}
}
void InputEventConfigurationDialog::_mod_toggled(bool p_checked, int p_index) {
Ref<InputEventWithModifiers> ie = event;
// Not event with modifiers
if (ie.is_null()) {
return;
}
if (p_index == 0) {
ie->set_alt_pressed(p_checked);
} else if (p_index == 1) {
ie->set_shift_pressed(p_checked);
} else if (p_index == 2) {
if (!autoremap_command_or_control_checkbox->is_pressed()) {
ie->set_ctrl_pressed(p_checked);
}
} else if (p_index == 3) {
if (!autoremap_command_or_control_checkbox->is_pressed()) {
ie->set_meta_pressed(p_checked);
}
}
_set_event(ie, original_event);
}
void InputEventConfigurationDialog::_autoremap_command_or_control_toggled(bool p_checked) {
Ref<InputEventWithModifiers> ie = event;
if (ie.is_valid()) {
ie->set_command_or_control_autoremap(p_checked);
_set_event(ie, original_event);
}
if (p_checked) {
mod_checkboxes[MOD_META]->hide();
mod_checkboxes[MOD_CTRL]->hide();
} else {
mod_checkboxes[MOD_META]->show();
mod_checkboxes[MOD_CTRL]->show();
}
}
void InputEventConfigurationDialog::_key_mode_selected(int p_mode) {
Ref<InputEventKey> k = event;
Ref<InputEventKey> ko = original_event;
if (k.is_null() || ko.is_null()) {
return;
}
if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
k->set_keycode(ko->get_keycode());
k->set_physical_keycode(Key::NONE);
k->set_key_label(Key::NONE);
} else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
k->set_keycode(Key::NONE);
k->set_physical_keycode(ko->get_physical_keycode());
k->set_key_label(Key::NONE);
} else if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
k->set_physical_keycode(Key::NONE);
k->set_keycode(Key::NONE);
k->set_key_label(ko->get_key_label());
}
_set_event(k, original_event);
}
void InputEventConfigurationDialog::_key_location_selected(int p_location) {
Ref<InputEventKey> k = event;
if (k.is_null()) {
return;
}
k->set_location((KeyLocation)p_location);
_set_event(k, original_event);
}
void InputEventConfigurationDialog::_input_list_item_activated() {
TreeItem *selected = input_list_tree->get_selected();
selected->set_collapsed(!selected->is_collapsed());
}
void InputEventConfigurationDialog::_input_list_item_selected() {
TreeItem *selected = input_list_tree->get_selected();
// Called form _set_event, do not update for a second time.
if (in_tree_update) {
return;
}
// Invalid tree selection - type only exists on the "category" items, which are not a valid selection.
if (selected->has_meta("__type")) {
return;
}
InputType input_type = (InputType)(int)selected->get_parent()->get_meta("__type");
switch (input_type) {
case INPUT_KEY: {
Key keycode = (Key)(int)selected->get_meta("__keycode");
Ref<InputEventKey> k;
k.instantiate();
k->set_physical_keycode(keycode);
k->set_keycode(keycode);
k->set_key_label(keycode);
// Maintain modifier state from checkboxes.
k->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed());
k->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed());
if (autoremap_command_or_control_checkbox->is_pressed()) {
k->set_command_or_control_autoremap(true);
} else {
k->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed());
k->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed());
}
Ref<InputEventKey> ko = k->duplicate();
if (key_mode->get_selected_id() == KEYMODE_UNICODE) {
key_mode->select(KEYMODE_PHY_KEYCODE);
}
if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
k->set_physical_keycode(Key::NONE);
k->set_keycode(keycode);
k->set_key_label(Key::NONE);
} else if (key_mode->get_selected_id() == KEYMODE_PHY_KEYCODE) {
k->set_physical_keycode(keycode);
k->set_keycode(Key::NONE);
k->set_key_label(Key::NONE);
}
_set_event(k, ko, false);
} break;
case INPUT_MOUSE_BUTTON: {
MouseButton idx = (MouseButton)(int)selected->get_meta("__index");
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_button_index(idx);
// Maintain modifier state from checkboxes
mb->set_alt_pressed(mod_checkboxes[MOD_ALT]->is_pressed());
mb->set_shift_pressed(mod_checkboxes[MOD_SHIFT]->is_pressed());
if (autoremap_command_or_control_checkbox->is_pressed()) {
mb->set_command_or_control_autoremap(true);
} else {
mb->set_ctrl_pressed(mod_checkboxes[MOD_CTRL]->is_pressed());
mb->set_meta_pressed(mod_checkboxes[MOD_META]->is_pressed());
}
// Maintain selected device
mb->set_device(_get_current_device());
_set_event(mb, mb, false);
} break;
case INPUT_JOY_BUTTON: {
JoyButton idx = (JoyButton)(int)selected->get_meta("__index");
Ref<InputEventJoypadButton> jb = InputEventJoypadButton::create_reference(idx);
// Maintain selected device
jb->set_device(_get_current_device());
_set_event(jb, jb, false);
} break;
case INPUT_JOY_MOTION: {
JoyAxis axis = (JoyAxis)(int)selected->get_meta("__axis");
int value = selected->get_meta("__value");
Ref<InputEventJoypadMotion> jm;
jm.instantiate();
jm->set_axis(axis);
jm->set_axis_value(value);
// Maintain selected device
jm->set_device(_get_current_device());
_set_event(jm, jm, false);
} break;
}
}
void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) {
// Subtract 1 as option index 0 corresponds to "All Devices" (value of -1)
// and option index 1 corresponds to device 0, etc...
event->set_device(p_option_button_index - 1);
event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true));
}
void InputEventConfigurationDialog::_set_current_device(int p_device) {
device_id_option->select(p_device + 1);
}
int InputEventConfigurationDialog::_get_current_device() const {
return device_id_option->get_selected() - 1;
}
void InputEventConfigurationDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
event_listener->grab_focus();
} break;
case NOTIFICATION_THEME_CHANGED: {
input_list_search->set_right_icon(get_editor_theme_icon(SNAME("Search")));
key_mode->set_item_icon(KEYMODE_KEYCODE, get_editor_theme_icon(SNAME("Keyboard")));
key_mode->set_item_icon(KEYMODE_PHY_KEYCODE, get_editor_theme_icon(SNAME("KeyboardPhysical")));
key_mode->set_item_icon(KEYMODE_UNICODE, get_editor_theme_icon(SNAME("KeyboardLabel")));
icon_cache.keyboard = get_editor_theme_icon(SNAME("Keyboard"));
icon_cache.mouse = get_editor_theme_icon(SNAME("Mouse"));
icon_cache.joypad_button = get_editor_theme_icon(SNAME("JoyButton"));
icon_cache.joypad_axis = get_editor_theme_icon(SNAME("JoyAxis"));
event_as_text->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
_update_input_list();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
key_location->set_item_text(key_location->get_item_index((int)KeyLocation::UNSPECIFIED), TTR("Unspecified", "Key Location"));
key_location->set_item_text(key_location->get_item_index((int)KeyLocation::LEFT), TTR("Left", "Key Location"));
key_location->set_item_text(key_location->get_item_index((int)KeyLocation::RIGHT), TTR("Right", "Key Location"));
} break;
}
}
void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event, const String &p_current_action_name) {
if (p_event.is_valid()) {
_set_event(p_event->duplicate(), p_event->duplicate());
} else {
// Clear Event
_set_event(Ref<InputEvent>(), Ref<InputEvent>());
// Clear Checkbox Values
for (int i = 0; i < MOD_MAX; i++) {
mod_checkboxes[i]->set_pressed(false);
}
// Enable the Physical Key by default to encourage its use.
// Physical Key should be used for most game inputs as it allows keys to work
// on non-QWERTY layouts out of the box.
// This is especially important for WASD movement layouts.
key_mode->select(KEYMODE_PHY_KEYCODE);
autoremap_command_or_control_checkbox->set_pressed(false);
// Select "All Devices" by default.
device_id_option->select(0);
// Also "all locations".
key_location->select(0);
}
if (!p_current_action_name.is_empty()) {
set_title(vformat(TTR("Event Configuration for \"%s\""), p_current_action_name));
} else {
set_title(TTR("Event Configuration"));
}
popup_centered(Size2(0, 400) * EDSCALE);
}
Ref<InputEvent> InputEventConfigurationDialog::get_event() const {
return event;
}
void InputEventConfigurationDialog::set_allowed_input_types(int p_type_masks) {
allowed_input_types = p_type_masks;
event_listener->set_allowed_input_types(p_type_masks);
}
InputEventConfigurationDialog::InputEventConfigurationDialog() {
allowed_input_types = INPUT_KEY | INPUT_MOUSE_BUTTON | INPUT_JOY_BUTTON | INPUT_JOY_MOTION;
set_min_size(Size2i(800, 0) * EDSCALE);
VBoxContainer *main_vbox = memnew(VBoxContainer);
add_child(main_vbox);
event_as_text = memnew(Label);
event_as_text->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
event_as_text->set_custom_minimum_size(Size2(500, 0) * EDSCALE);
event_as_text->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
event_as_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
event_as_text->add_theme_font_size_override(SceneStringName(font_size), 18 * EDSCALE);
main_vbox->add_child(event_as_text);
event_listener = memnew(EventListenerLineEdit);
event_listener->set_h_size_flags(Control::SIZE_EXPAND_FILL);
event_listener->set_stretch_ratio(0.75);
event_listener->connect("event_changed", callable_mp(this, &InputEventConfigurationDialog::_on_listen_input_changed));
main_vbox->add_child(event_listener);
main_vbox->add_child(memnew(HSeparator));
// List of all input options to manually select from.
VBoxContainer *manual_vbox = memnew(VBoxContainer);
manual_vbox->set_name("Manual Selection");
manual_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
main_vbox->add_child(manual_vbox);
input_list_search = memnew(LineEdit);
input_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL);
input_list_search->set_placeholder(TTRC("Filter Inputs"));
input_list_search->set_accessibility_name(TTRC("Filter Inputs"));
input_list_search->set_clear_button_enabled(true);
input_list_search->connect(SceneStringName(text_changed), callable_mp(this, &InputEventConfigurationDialog::_search_term_updated));
manual_vbox->add_child(input_list_search);
input_list_tree = memnew(Tree);
input_list_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
input_list_tree->connect("item_activated", callable_mp(this, &InputEventConfigurationDialog::_input_list_item_activated));
input_list_tree->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_input_list_item_selected));
input_list_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
manual_vbox->add_child(input_list_tree);
input_list_tree->set_hide_root(true);
input_list_tree->set_columns(1);
_update_input_list();
// Additional Options
additional_options_container = memnew(VBoxContainer);
additional_options_container->hide();
Label *opts_label = memnew(Label(TTRC("Additional Options")));
opts_label->set_theme_type_variation("HeaderSmall");
additional_options_container->add_child(opts_label);
// Device Selection
device_container = memnew(HBoxContainer);
device_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
Label *device_label = memnew(Label(TTRC("Device:")));
device_label->set_theme_type_variation("HeaderSmall");
device_container->add_child(device_label);
device_id_option = memnew(OptionButton);
device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);
for (int i = -1; i < 8; i++) {
device_id_option->add_item(EventListenerLineEdit::get_device_string(i));
}
device_id_option->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed));
device_id_option->set_accessibility_name(TTRC("Device:"));
_set_current_device(InputMap::ALL_DEVICES);
device_container->add_child(device_id_option);
device_container->hide();
additional_options_container->add_child(device_container);
// Modifier Selection
mod_container = memnew(HBoxContainer);
for (int i = 0; i < MOD_MAX; i++) {
String name = mods[i];
mod_checkboxes[i] = memnew(CheckBox(name));
mod_checkboxes[i]->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i));
mod_checkboxes[i]->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
mod_checkboxes[i]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_ALWAYS);
mod_checkboxes[i]->set_tooltip_text(mods_tip[i]);
mod_container->add_child(mod_checkboxes[i]);
}
mod_container->add_child(memnew(VSeparator));
autoremap_command_or_control_checkbox = memnew(CheckBox(TTRC("Command / Control (auto)")));
autoremap_command_or_control_checkbox->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled));
autoremap_command_or_control_checkbox->set_pressed(false);
autoremap_command_or_control_checkbox->set_tooltip_text(TTRC("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform."));
mod_container->add_child(autoremap_command_or_control_checkbox);
mod_container->hide();
additional_options_container->add_child(mod_container);
// Key Mode Selection
key_mode = memnew(OptionButton);
key_mode->add_item(TTRC("Keycode (Latin Equivalent)"), KEYMODE_KEYCODE);
key_mode->add_item(TTRC("Physical Keycode (Position on US QWERTY Keyboard)"), KEYMODE_PHY_KEYCODE);
key_mode->add_item(TTRC("Key Label (Unicode, Case-Insensitive)"), KEYMODE_UNICODE);
key_mode->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_key_mode_selected));
key_mode->hide();
additional_options_container->add_child(key_mode);
// Key Location Selection
location_container = memnew(HBoxContainer);
location_container->hide();
location_container->add_child(memnew(Label(TTRC("Physical location"))));
key_location = memnew(OptionButton);
key_location->set_h_size_flags(Control::SIZE_EXPAND_FILL);
key_location->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
// Item texts will be set in `NOTIFICATION_TRANSLATION_CHANGED`.
key_location->add_item(String(), (int)KeyLocation::UNSPECIFIED);
key_location->add_item(String(), (int)KeyLocation::LEFT);
key_location->add_item(String(), (int)KeyLocation::RIGHT);
key_location->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_key_location_selected));
key_location->set_accessibility_name(TTRC("Physical location"));
location_container->add_child(key_location);
additional_options_container->add_child(location_container);
main_vbox->add_child(additional_options_container);
}

View File

@@ -0,0 +1,138 @@
/**************************************************************************/
/* input_event_configuration_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/dialogs.h"
class OptionButton;
class Tree;
class EventListenerLineEdit;
class CheckBox;
// Confirmation Dialog used when configuring an input event.
// Separate from ActionMapEditor for code cleanliness and separation of responsibilities.
class InputEventConfigurationDialog : public ConfirmationDialog {
GDCLASS(InputEventConfigurationDialog, ConfirmationDialog)
private:
struct IconCache {
Ref<Texture2D> keyboard;
Ref<Texture2D> mouse;
Ref<Texture2D> joypad_button;
Ref<Texture2D> joypad_axis;
} icon_cache;
Ref<InputEvent> event;
Ref<InputEvent> original_event;
bool in_tree_update = false;
// Listening for input
EventListenerLineEdit *event_listener = nullptr;
Label *event_as_text = nullptr;
// List of All Key/Mouse/Joypad input options.
int allowed_input_types;
Tree *input_list_tree = nullptr;
LineEdit *input_list_search = nullptr;
// Additional Options, shown depending on event selected
VBoxContainer *additional_options_container = nullptr;
HBoxContainer *device_container = nullptr;
OptionButton *device_id_option = nullptr;
HBoxContainer *mod_container = nullptr; // Contains the subcontainer and the store command checkbox.
enum ModCheckbox {
MOD_ALT,
MOD_SHIFT,
MOD_CTRL,
MOD_META,
MOD_MAX
};
#if defined(MACOS_ENABLED)
String mods[MOD_MAX] = { "Option", "Shift", "Ctrl", "Command" };
#elif defined(WINDOWS_ENABLED)
String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Windows" };
#else
String mods[MOD_MAX] = { "Alt", "Shift", "Ctrl", "Meta" };
#endif
String mods_tip[MOD_MAX] = {
TTRC("Alt or Option key"),
TTRC("Shift key"),
TTRC("Control key"),
TTRC("Meta/Windows or Command key"),
};
CheckBox *mod_checkboxes[MOD_MAX];
CheckBox *autoremap_command_or_control_checkbox = nullptr;
enum KeyMode {
KEYMODE_KEYCODE,
KEYMODE_PHY_KEYCODE,
KEYMODE_UNICODE,
};
OptionButton *key_mode = nullptr;
HBoxContainer *location_container = nullptr;
OptionButton *key_location = nullptr;
void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true);
void _on_listen_input_changed(const Ref<InputEvent> &p_event);
void _search_term_updated(const String &p_term);
void _update_input_list();
void _input_list_item_activated();
void _input_list_item_selected();
void _mod_toggled(bool p_checked, int p_index);
void _autoremap_command_or_control_toggled(bool p_checked);
void _key_mode_selected(int p_mode);
void _key_location_selected(int p_location);
void _device_selection_changed(int p_option_button_index);
void _set_current_device(int p_device);
int _get_current_device() const;
protected:
void _notification(int p_what);
public:
// Pass an existing event to configure it. Alternatively, pass no event to start with a blank configuration.
// An action name can be passed for descriptive purposes.
void popup_and_configure(const Ref<InputEvent> &p_event = Ref<InputEvent>(), const String &p_current_action_name = "");
Ref<InputEvent> get_event() const;
void set_allowed_input_types(int p_type_masks);
InputEventConfigurationDialog();
};

View File

@@ -0,0 +1,858 @@
/**************************************************************************/
/* project_settings_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 "project_settings_editor.h"
#include "core/config/project_settings.h"
#include "core/input/input_map.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export.h"
#include "editor/gui/editor_variant_type_selectors.h"
#include "editor/inspector/editor_inspector.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/check_button.h"
#include "servers/movie_writer/movie_writer.h"
void ProjectSettingsEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) {
localization_editor->connect_filesystem_dock_signals(p_fs_dock);
group_settings->connect_filesystem_dock_signals(p_fs_dock);
}
void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) {
// Restore valid window bounds or pop up at default size.
Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "project_settings", Rect2());
if (saved_size != Rect2()) {
popup(saved_size);
} else {
popup_centered_clamped(Size2(1200, 700) * EDSCALE, 0.8);
}
_add_feature_overrides();
general_settings_inspector->update_category_list();
set_process_shortcut_input(true);
localization_editor->update_translations();
autoload_settings->update_autoload();
group_settings->update_groups();
plugin_settings->update_plugins();
import_defaults_editor->clear();
if (p_clear_filter) {
search_box->clear();
}
_focus_current_search_box();
}
void ProjectSettingsEditor::popup_for_override(const String &p_override) {
popup_project_settings();
tab_container->set_current_tab(0);
general_settings_inspector->set_current_section(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX + p_override.get_slicec('/', 0));
}
void ProjectSettingsEditor::set_filter(const String &p_filter) {
search_box->set_text(p_filter);
}
void ProjectSettingsEditor::queue_save() {
settings_changed = true;
timer->start();
}
void ProjectSettingsEditor::_save() {
settings_changed = false;
if (ps) {
ps->save();
}
if (pending_override_notify) {
pending_override_notify = false;
EditorNode::get_singleton()->notify_settings_overrides_changed();
}
}
void ProjectSettingsEditor::set_plugins_page() {
tab_container->set_current_tab(tab_container->get_tab_idx_from_control(plugin_settings));
}
void ProjectSettingsEditor::set_general_page(const String &p_category) {
tab_container->set_current_tab(tab_container->get_tab_idx_from_control(general_editor));
general_settings_inspector->set_current_section(p_category);
}
void ProjectSettingsEditor::update_plugins() {
plugin_settings->update_plugins();
}
void ProjectSettingsEditor::init_autoloads() {
autoload_settings->init_autoloads();
}
void ProjectSettingsEditor::_setting_edited(const String &p_name) {
const String full_name = general_settings_inspector->get_full_item_path(p_name);
if (full_name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX)) {
EditorSettings::get_singleton()->mark_setting_changed(full_name.trim_prefix(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
pending_override_notify = true;
}
queue_save();
}
void ProjectSettingsEditor::_update_advanced(bool p_is_advanced) {
custom_properties->set_visible(p_is_advanced);
}
void ProjectSettingsEditor::_on_category_changed(const String &p_new_category) {
general_settings_inspector->get_inspector()->set_use_deletable_properties(p_new_category.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
}
void ProjectSettingsEditor::_on_editor_override_deleted(const String &p_setting) {
const String full_name = general_settings_inspector->get_full_item_path(p_setting);
ERR_FAIL_COND(!full_name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
ProjectSettings::get_singleton()->set_setting(full_name, Variant());
EditorSettings::get_singleton()->mark_setting_changed(full_name.trim_prefix(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
pending_override_notify = true;
_save();
general_settings_inspector->update_category_list();
}
void ProjectSettingsEditor::_advanced_toggled(bool p_button_pressed) {
EditorSettings::get_singleton()->set("_project_settings_advanced_mode", p_button_pressed);
EditorSettings::get_singleton()->save();
_update_advanced(p_button_pressed);
}
void ProjectSettingsEditor::_setting_selected(const String &p_path) {
if (p_path.is_empty()) {
return;
}
property_box->set_text(general_settings_inspector->get_current_section() + "/" + p_path);
_update_property_box(); // set_text doesn't trigger text_changed
}
void ProjectSettingsEditor::_add_setting() {
String setting = _get_setting_name();
// Initialize the property with the default value for the given type.
Callable::CallError ce;
Variant value;
Variant::construct(type_box->get_selected_type(), value, nullptr, 0, ce);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Project Setting"));
undo_redo->add_do_property(ps, setting, value);
undo_redo->add_undo_property(ps, setting, ps->has_setting(setting) ? ps->get(setting) : Variant());
undo_redo->add_do_method(general_settings_inspector, "update_category_list");
undo_redo->add_undo_method(general_settings_inspector, "update_category_list");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
general_settings_inspector->set_current_section(setting.get_slicec('/', 1));
add_button->release_focus();
}
void ProjectSettingsEditor::_delete_setting() {
String setting = _get_setting_name();
Variant value = ps->get(setting);
int order = ps->get_order(setting);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Delete Item"));
undo_redo->add_do_method(ps, "clear", setting);
undo_redo->add_undo_method(ps, "set", setting, value);
undo_redo->add_undo_method(ps, "set_order", setting, order);
undo_redo->add_do_method(general_settings_inspector, "update_category_list");
undo_redo->add_undo_method(general_settings_inspector, "update_category_list");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
property_box->clear();
del_button->release_focus();
}
void ProjectSettingsEditor::_property_box_changed(const String &p_text) {
_update_property_box();
}
void ProjectSettingsEditor::_feature_selected(int p_index) {
const String property = property_box->get_text().strip_edges().get_slicec('.', 0);
if (p_index == FEATURE_ALL) {
property_box->set_text(property);
} else if (p_index == FEATURE_CUSTOM) {
property_box->set_text(property + ".custom");
const int len = property.length() + 1;
property_box->select(len);
property_box->set_caret_column(len);
property_box->grab_focus();
} else {
property_box->set_text(property + "." + feature_box->get_item_text(p_index));
};
_update_property_box();
}
void ProjectSettingsEditor::_update_property_box() {
const String setting = _get_setting_name();
int slices = setting.get_slice_count(".");
const String name = setting.get_slicec('.', 0);
const String feature = setting.get_slicec('.', 1);
bool feature_invalid = slices > 2 || (slices == 2 && feature.is_empty());
add_button->set_disabled(true);
del_button->set_disabled(true);
if (feature.is_empty() || feature_invalid) {
feature_box->select(FEATURE_ALL);
} else {
bool is_custom = true;
for (int i = FEATURE_FIRST; i < feature_box->get_item_count(); i++) {
if (feature == feature_box->get_item_text(i)) {
is_custom = false;
feature_box->select(i);
break;
}
}
if (is_custom) {
feature_box->select(FEATURE_CUSTOM);
}
}
if (property_box->get_text().is_empty()) {
return;
}
if (ps->has_setting(setting)) {
del_button->set_disabled(ps->is_builtin_setting(setting) || setting.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
_select_type(ps->get_setting(setting).get_type());
} else {
if (ps->has_setting(name)) {
_select_type(ps->get_setting(name).get_type());
} else {
type_box->select(0);
}
if (feature_invalid || name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX)) {
return;
}
const Vector<String> names = name.split("/");
for (int i = 0; i < names.size(); i++) {
if (!names[i].is_valid_ascii_identifier()) {
return;
}
}
add_button->set_disabled(false);
}
}
void ProjectSettingsEditor::_select_type(Variant::Type p_type) {
type_box->select(type_box->get_item_index(p_type));
}
void ProjectSettingsEditor::shortcut_input(const Ref<InputEvent> &p_event) {
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
bool handled = false;
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
EditorNode::get_singleton()->undo();
handled = true;
}
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
EditorNode::get_singleton()->redo();
handled = true;
}
if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
_focus_current_search_box();
handled = true;
}
if (ED_IS_SHORTCUT("file_dialog/focus_path", p_event)) {
_focus_current_path_box();
handled = true;
}
if (handled) {
set_input_as_handled();
}
}
}
String ProjectSettingsEditor::_get_setting_name() const {
String name = property_box->get_text().strip_edges();
if (!name.begins_with("_") && !name.contains_char('/')) {
name = "global/" + name;
}
return name;
}
void ProjectSettingsEditor::_add_feature_overrides() {
HashSet<String> presets;
presets.insert("bptc");
presets.insert("s3tc");
presets.insert("etc2");
presets.insert("editor");
presets.insert("editor_hint");
presets.insert("editor_runtime");
presets.insert("template_debug");
presets.insert("template_release");
presets.insert("debug");
presets.insert("release");
presets.insert("template");
presets.insert("double");
presets.insert("single");
presets.insert("32");
presets.insert("64");
presets.insert("movie");
EditorExport *ee = EditorExport::get_singleton();
for (int i = 0; i < ee->get_export_platform_count(); i++) {
List<String> p;
ee->get_export_platform(i)->get_platform_features(&p);
for (const String &E : p) {
presets.insert(E);
}
}
for (int i = 0; i < ee->get_export_preset_count(); i++) {
List<String> p;
ee->get_export_preset(i)->get_platform()->get_preset_features(ee->get_export_preset(i), &p);
for (const String &E : p) {
presets.insert(E);
}
String custom = ee->get_export_preset(i)->get_custom_features();
Vector<String> custom_list = custom.split(",");
for (int j = 0; j < custom_list.size(); j++) {
String f = custom_list[j].strip_edges();
if (!f.is_empty()) {
presets.insert(f);
}
}
}
feature_box->clear();
feature_box->add_item(TTRC("All"), FEATURE_ALL); // So it is always on top.
feature_box->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
feature_box->add_item(TTRC("Custom"), FEATURE_CUSTOM);
feature_box->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
feature_box->add_separator();
int id = FEATURE_FIRST;
for (const String &E : presets) {
feature_box->add_item(E, id++);
}
}
void ProjectSettingsEditor::_tabs_tab_changed(int p_tab) {
_focus_current_search_box();
}
void ProjectSettingsEditor::_focus_current_search_box() {
Control *tab = tab_container->get_current_tab_control();
LineEdit *current_search_box = nullptr;
if (tab == general_editor) {
current_search_box = search_box;
} else if (tab == action_map_editor) {
current_search_box = action_map_editor->get_search_box();
}
if (current_search_box) {
current_search_box->grab_focus();
current_search_box->select_all();
}
}
void ProjectSettingsEditor::_focus_current_path_box() {
Control *tab = tab_container->get_current_tab_control();
LineEdit *current_path_box = nullptr;
if (tab == general_editor) {
current_path_box = property_box;
} else if (tab == action_map_editor) {
current_path_box = action_map_editor->get_path_box();
} else if (tab == autoload_settings) {
current_path_box = autoload_settings->get_path_box();
} else if (tab == shaders_global_shader_uniforms_editor) {
current_path_box = shaders_global_shader_uniforms_editor->get_name_box();
} else if (tab == group_settings) {
current_path_box = group_settings->get_name_box();
}
if (current_path_box) {
current_path_box->grab_focus();
current_path_box->select_all();
}
}
void ProjectSettingsEditor::_editor_restart() {
ProjectSettings::get_singleton()->save();
EditorNode::get_singleton()->save_all_scenes();
EditorNode::get_singleton()->restart_editor();
}
void ProjectSettingsEditor::_editor_restart_request() {
restart_container->show();
}
void ProjectSettingsEditor::_editor_restart_close() {
restart_container->hide();
}
void ProjectSettingsEditor::_action_added(const String &p_name) {
String name = "input/" + p_name;
ERR_FAIL_COND_MSG(ProjectSettings::get_singleton()->has_setting(name),
"An action with this name already exists.");
Dictionary action;
action["events"] = Array();
action["deadzone"] = InputMap::DEFAULT_DEADZONE;
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Input Action"));
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", name, action);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name);
undo_redo->add_do_method(this, "_update_action_map_editor");
undo_redo->add_undo_method(this, "_update_action_map_editor");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
}
void ProjectSettingsEditor::_action_edited(const String &p_name, const Dictionary &p_action) {
const String property_name = "input/" + p_name;
Dictionary old_val = GLOBAL_GET(property_name);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (old_val["deadzone"] != p_action["deadzone"]) {
// Deadzone Changed
undo_redo->create_action(TTR("Change Action deadzone"));
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", property_name, p_action);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
} else {
// Events changed
undo_redo->create_action(TTR("Change Input Action Event(s)"));
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", property_name, p_action);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
}
undo_redo->add_do_method(this, "_update_action_map_editor");
undo_redo->add_undo_method(this, "_update_action_map_editor");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
}
void ProjectSettingsEditor::_action_removed(const String &p_name) {
const String property_name = "input/" + p_name;
Dictionary old_val = GLOBAL_GET(property_name);
int order = ProjectSettings::get_singleton()->get_order(property_name);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Erase Input Action"));
undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", property_name);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property_name, old_val);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", property_name, order);
undo_redo->add_do_method(this, "_update_action_map_editor");
undo_redo->add_undo_method(this, "_update_action_map_editor");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
}
void ProjectSettingsEditor::_action_renamed(const String &p_old_name, const String &p_new_name) {
const String old_property_name = "input/" + p_old_name;
const String new_property_name = "input/" + p_new_name;
ERR_FAIL_COND_MSG(ProjectSettings::get_singleton()->has_setting(new_property_name),
"An action with this name already exists.");
int order = ProjectSettings::get_singleton()->get_order(old_property_name);
Dictionary action = GLOBAL_GET(old_property_name);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Rename Input Action"));
// Do: clear old, set new
undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", old_property_name);
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", new_property_name, action);
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", new_property_name, order);
// Undo: clear new, set old
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", new_property_name);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", old_property_name, action);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", old_property_name, order);
undo_redo->add_do_method(this, "_update_action_map_editor");
undo_redo->add_undo_method(this, "_update_action_map_editor");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
}
void ProjectSettingsEditor::_action_reordered(const String &p_action_name, const String &p_relative_to, bool p_before) {
const String action_name = "input/" + p_action_name;
const String target_name = "input/" + p_relative_to;
// It is much easier to rebuild the custom "input" properties rather than messing around with the "order" values of them.
Variant action_value = ps->get(action_name);
Variant target_value = ps->get(target_name);
List<PropertyInfo> props;
HashMap<String, Variant> action_values;
ProjectSettings::get_singleton()->get_property_list(&props);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Update Input Action Order"));
for (const PropertyInfo &prop : props) {
// Skip builtins and non-inputs
// Order matters here, checking for "input/" filters out properties that aren't settings and produce errors in is_builtin_setting().
if (!prop.name.begins_with("input/") || ProjectSettings::get_singleton()->is_builtin_setting(prop.name)) {
continue;
}
action_values.insert(prop.name, ps->get(prop.name));
undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", prop.name);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", prop.name);
}
for (const KeyValue<String, Variant> &E : action_values) {
String name = E.key;
const Variant &value = E.value;
if (name == target_name) {
if (p_before) {
// Insert before target
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
} else {
// Insert after target
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", action_name, action_value);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", target_name, target_value);
}
} else if (name != action_name) {
undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", name, value);
undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", name, value);
}
}
undo_redo->add_do_method(this, "_update_action_map_editor");
undo_redo->add_undo_method(this, "_update_action_map_editor");
undo_redo->add_do_method(this, "queue_save");
undo_redo->add_undo_method(this, "queue_save");
undo_redo->commit_action();
}
void ProjectSettingsEditor::_update_action_map_editor() {
Vector<ActionMapEditor::ActionInfo> actions;
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
const Ref<Texture2D> builtin_icon = get_editor_theme_icon(SNAME("PinPressed"));
for (const PropertyInfo &E : props) {
const String property_name = E.name;
if (!property_name.begins_with("input/")) {
continue;
}
// Strip the "input/" from the left.
String display_name = property_name.substr(String("input/").size() - 1);
Dictionary action = GLOBAL_GET(property_name);
ActionMapEditor::ActionInfo action_info;
action_info.action = action;
action_info.editable = true;
action_info.name = display_name;
const bool is_builtin_input = ProjectSettings::get_singleton()->get_input_presets().find(property_name) != nullptr;
if (is_builtin_input) {
action_info.editable = false;
action_info.icon = builtin_icon;
action_info.has_initial = true;
action_info.action_initial = ProjectSettings::get_singleton()->property_get_revert(property_name);
}
actions.push_back(action_info);
}
action_map_editor->update_action_list(actions);
}
void ProjectSettingsEditor::_update_theme() {
add_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
del_button->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
restart_close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
restart_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
restart_icon->set_texture(get_editor_theme_icon(SNAME("StatusWarning")));
restart_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
}
void ProjectSettingsEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
HashMap<String, PropertyInfo> editor_settings_info;
List<PropertyInfo> infos;
EditorSettings::get_singleton()->get_property_list(&infos);
for (const PropertyInfo &pi : infos) {
editor_settings_info[pi.name] = pi;
}
ProjectSettings::get_singleton()->editor_settings_info = editor_settings_info;
} else {
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", Rect2(get_position(), get_size()));
if (settings_changed) {
timer->stop();
_save();
}
}
} break;
case NOTIFICATION_ENTER_TREE: {
general_settings_inspector->edit(ps);
_update_action_map_editor();
_update_theme();
} break;
case NOTIFICATION_THEME_CHANGED: {
_update_theme();
} break;
}
}
void ProjectSettingsEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("queue_save"), &ProjectSettingsEditor::queue_save);
ClassDB::bind_method(D_METHOD("_update_action_map_editor"), &ProjectSettingsEditor::_update_action_map_editor);
}
ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
singleton = this;
set_title(TTRC("Project Settings (project.godot)"));
set_clamp_to_embedder(true);
ps = ProjectSettings::get_singleton();
data = p_data;
tab_container = memnew(TabContainer);
tab_container->set_use_hidden_tabs_for_min_size(true);
tab_container->set_theme_type_variation("TabContainerOdd");
tab_container->connect("tab_changed", callable_mp(this, &ProjectSettingsEditor::_tabs_tab_changed));
add_child(tab_container);
general_editor = memnew(VBoxContainer);
general_editor->set_name(TTRC("General"));
general_editor->set_alignment(BoxContainer::ALIGNMENT_BEGIN);
general_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tab_container->add_child(general_editor);
HBoxContainer *search_bar = memnew(HBoxContainer);
general_editor->add_child(search_bar);
search_box = memnew(LineEdit);
search_box->set_placeholder(TTRC("Filter Settings"));
search_box->set_accessibility_name(TTRC("Filter Settings"));
search_box->set_clear_button_enabled(true);
search_box->set_virtual_keyboard_show_on_focus(false);
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_bar->add_child(search_box);
advanced = memnew(CheckButton);
advanced->set_text(TTRC("Advanced Settings"));
search_bar->add_child(advanced);
custom_properties = memnew(HBoxContainer);
general_editor->add_child(custom_properties);
property_box = memnew(LineEdit);
property_box->set_placeholder(TTRC("Select a Setting or Type its Name"));
property_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
property_box->connect(SceneStringName(text_changed), callable_mp(this, &ProjectSettingsEditor::_property_box_changed));
custom_properties->add_child(property_box);
feature_box = memnew(OptionButton);
feature_box->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
feature_box->set_accessibility_name(TTRC("Feature"));
feature_box->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
feature_box->connect(SceneStringName(item_selected), callable_mp(this, &ProjectSettingsEditor::_feature_selected));
custom_properties->add_child(feature_box);
type_box = memnew(EditorVariantTypeOptionButton);
type_box->populate({ Variant::NIL, Variant::OBJECT });
type_box->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
type_box->set_accessibility_name(TTRC("Type"));
custom_properties->add_child(type_box);
add_button = memnew(Button);
add_button->set_text(TTRC("Add"));
add_button->set_disabled(true);
add_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectSettingsEditor::_add_setting));
custom_properties->add_child(add_button);
del_button = memnew(Button);
del_button->set_text(TTRC("Delete"));
del_button->set_disabled(true);
del_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectSettingsEditor::_delete_setting));
custom_properties->add_child(del_button);
general_settings_inspector = memnew(SectionedInspector);
general_settings_inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL);
general_settings_inspector->register_search_box(search_box);
general_settings_inspector->register_advanced_toggle(advanced);
general_settings_inspector->connect("category_changed", callable_mp(this, &ProjectSettingsEditor::_on_category_changed));
general_settings_inspector->get_inspector()->set_use_filter(true);
general_settings_inspector->get_inspector()->set_mark_unsaved(false);
general_settings_inspector->get_inspector()->connect("property_selected", callable_mp(this, &ProjectSettingsEditor::_setting_selected));
general_settings_inspector->get_inspector()->connect("property_edited", callable_mp(this, &ProjectSettingsEditor::_setting_edited));
general_settings_inspector->get_inspector()->connect("property_deleted", callable_mp(this, &ProjectSettingsEditor::_on_editor_override_deleted));
general_settings_inspector->get_inspector()->connect("restart_requested", callable_mp(this, &ProjectSettingsEditor::_editor_restart_request));
general_editor->add_child(general_settings_inspector);
if (EDITOR_GET("interface/touchscreen/enable_touch_optimizations")) {
general_settings_inspector->set_touch_dragger_enabled(true);
}
restart_container = memnew(PanelContainer);
general_editor->add_child(restart_container);
HBoxContainer *restart_hb = memnew(HBoxContainer);
restart_container->hide();
restart_container->add_child(restart_hb);
restart_icon = memnew(TextureRect);
restart_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
restart_hb->add_child(restart_icon);
restart_label = memnew(Label);
restart_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
restart_label->set_text(TTRC("Changed settings will be applied to the editor after restarting."));
restart_hb->add_child(restart_label);
restart_hb->add_spacer();
Button *restart_button = memnew(Button);
restart_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectSettingsEditor::_editor_restart));
restart_hb->add_child(restart_button);
restart_button->set_text(TTRC("Save & Restart"));
restart_close_button = memnew(Button);
restart_close_button->set_flat(true);
restart_close_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectSettingsEditor::_editor_restart_close));
restart_close_button->set_accessibility_name(TTRC("Close"));
restart_hb->add_child(restart_close_button);
action_map_editor = memnew(ActionMapEditor);
action_map_editor->set_name(TTRC("Input Map"));
action_map_editor->connect("action_added", callable_mp(this, &ProjectSettingsEditor::_action_added));
action_map_editor->connect("action_edited", callable_mp(this, &ProjectSettingsEditor::_action_edited));
action_map_editor->connect("action_removed", callable_mp(this, &ProjectSettingsEditor::_action_removed));
action_map_editor->connect("action_renamed", callable_mp(this, &ProjectSettingsEditor::_action_renamed));
action_map_editor->connect("action_reordered", callable_mp(this, &ProjectSettingsEditor::_action_reordered));
tab_container->add_child(action_map_editor);
localization_editor = memnew(LocalizationEditor);
localization_editor->set_name(TTRC("Localization"));
localization_editor->connect("localization_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
tab_container->add_child(localization_editor);
TabContainer *globals_container = memnew(TabContainer);
globals_container->set_name(TTRC("Globals"));
tab_container->add_child(globals_container);
autoload_settings = memnew(EditorAutoloadSettings);
autoload_settings->set_name(TTRC("Autoload"));
autoload_settings->connect("autoload_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
globals_container->add_child(autoload_settings);
shaders_global_shader_uniforms_editor = memnew(ShaderGlobalsEditor);
shaders_global_shader_uniforms_editor->set_name(TTRC("Shader Globals"));
shaders_global_shader_uniforms_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
globals_container->add_child(shaders_global_shader_uniforms_editor);
group_settings = memnew(GroupSettingsEditor);
group_settings->set_name(TTRC("Groups"));
group_settings->connect("group_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
globals_container->add_child(group_settings);
plugin_settings = memnew(EditorPluginSettings);
plugin_settings->set_name(TTRC("Plugins"));
tab_container->add_child(plugin_settings);
timer = memnew(Timer);
timer->set_wait_time(1.5);
timer->connect("timeout", callable_mp(this, &ProjectSettingsEditor::_save));
timer->set_one_shot(true);
add_child(timer);
set_ok_button_text(TTRC("Close"));
set_hide_on_ok(true);
bool use_advanced = EDITOR_DEF("_project_settings_advanced_mode", false);
if (use_advanced) {
advanced->set_pressed(true);
}
advanced->connect(SceneStringName(toggled), callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
_update_advanced(use_advanced);
import_defaults_editor = memnew(ImportDefaultsEditor);
import_defaults_editor->set_name(TTRC("Import Defaults"));
tab_container->add_child(import_defaults_editor);
MovieWriter::set_extensions_hint(); // ensure extensions are properly displayed.
}

View File

@@ -0,0 +1,157 @@
/**************************************************************************/
/* project_settings_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. */
/**************************************************************************/
#pragma once
#include "core/config/project_settings.h"
#include "editor/editor_data.h"
#include "editor/import/import_defaults_editor.h"
#include "editor/inspector/editor_sectioned_inspector.h"
#include "editor/plugins/editor_plugin_settings.h"
#include "editor/scene/group_settings_editor.h"
#include "editor/settings/action_map_editor.h"
#include "editor/settings/editor_autoload_settings.h"
#include "editor/shader/shader_globals_editor.h"
#include "editor/translations/localization_editor.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/texture_rect.h"
class EditorVariantTypeOptionButton;
class FileSystemDock;
class ProjectSettingsEditor : public AcceptDialog {
GDCLASS(ProjectSettingsEditor, AcceptDialog);
inline static ProjectSettingsEditor *singleton = nullptr;
enum {
FEATURE_ALL,
FEATURE_CUSTOM,
FEATURE_FIRST,
};
ProjectSettings *ps = nullptr;
Timer *timer = nullptr;
TabContainer *tab_container = nullptr;
VBoxContainer *general_editor = nullptr;
SectionedInspector *general_settings_inspector = nullptr;
ActionMapEditor *action_map_editor = nullptr;
LocalizationEditor *localization_editor = nullptr;
EditorAutoloadSettings *autoload_settings = nullptr;
ShaderGlobalsEditor *shaders_global_shader_uniforms_editor = nullptr;
GroupSettingsEditor *group_settings = nullptr;
EditorPluginSettings *plugin_settings = nullptr;
LineEdit *search_box = nullptr;
CheckButton *advanced = nullptr;
HBoxContainer *custom_properties = nullptr;
LineEdit *property_box = nullptr;
OptionButton *feature_box = nullptr;
EditorVariantTypeOptionButton *type_box = nullptr;
Button *add_button = nullptr;
Button *del_button = nullptr;
Label *restart_label = nullptr;
TextureRect *restart_icon = nullptr;
PanelContainer *restart_container = nullptr;
Button *restart_close_button = nullptr;
ImportDefaultsEditor *import_defaults_editor = nullptr;
EditorData *data = nullptr;
bool settings_changed = false;
bool pending_override_notify = false;
void _on_category_changed(const String &p_new_category);
void _on_editor_override_deleted(const String &p_setting);
void _advanced_toggled(bool p_button_pressed);
void _update_advanced(bool p_is_advanced);
void _property_box_changed(const String &p_text);
void _update_property_box();
void _feature_selected(int p_index);
void _select_type(Variant::Type p_type);
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
String _get_setting_name() const;
void _setting_edited(const String &p_name);
void _setting_selected(const String &p_path);
void _add_setting();
void _delete_setting();
void _tabs_tab_changed(int p_tab);
void _focus_current_search_box();
void _focus_current_path_box();
void _editor_restart_request();
void _editor_restart();
void _editor_restart_close();
void _add_feature_overrides();
void _action_added(const String &p_name);
void _action_edited(const String &p_name, const Dictionary &p_action);
void _action_removed(const String &p_name);
void _action_renamed(const String &p_old_name, const String &p_new_name);
void _action_reordered(const String &p_action_name, const String &p_relative_to, bool p_before);
void _update_action_map_editor();
void _update_theme();
void _save();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
static ProjectSettingsEditor *get_singleton() { return singleton; }
void popup_project_settings(bool p_clear_filter = false);
void popup_for_override(const String &p_override);
void set_plugins_page();
void set_general_page(const String &p_category);
void update_plugins();
void init_autoloads();
void set_filter(const String &p_filter);
EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
GroupSettingsEditor *get_group_settings() { return group_settings; }
TabContainer *get_tabs() { return tab_container; }
void queue_save();
void connect_filesystem_dock_signals(FileSystemDock *p_fs_dock);
ProjectSettingsEditor(EditorData *p_data);
};