initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
editor/settings/SCsub
Normal file
6
editor/settings/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
613
editor/settings/action_map_editor.cpp
Normal file
613
editor/settings/action_map_editor.cpp
Normal 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);
|
||||
}
|
126
editor/settings/action_map_editor.h
Normal file
126
editor/settings/action_map_editor.h
Normal 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();
|
||||
};
|
999
editor/settings/editor_autoload_settings.cpp
Normal file
999
editor/settings/editor_autoload_settings.cpp
Normal 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();
|
||||
}
|
115
editor/settings/editor_autoload_settings.h
Normal file
115
editor/settings/editor_autoload_settings.h
Normal 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();
|
||||
};
|
1371
editor/settings/editor_build_profile.cpp
Normal file
1371
editor/settings/editor_build_profile.cpp
Normal file
File diff suppressed because it is too large
Load Diff
214
editor/settings/editor_build_profile.h
Normal file
214
editor/settings/editor_build_profile.h
Normal 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();
|
||||
};
|
390
editor/settings/editor_command_palette.cpp
Normal file
390
editor/settings/editor_command_palette.cpp
Normal 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;
|
||||
}
|
104
editor/settings/editor_command_palette.h
Normal file
104
editor/settings/editor_command_palette.h
Normal 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 = "");
|
109
editor/settings/editor_event_search_bar.cpp
Normal file
109
editor/settings/editor_event_search_bar.cpp
Normal 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);
|
||||
}
|
64
editor/settings/editor_event_search_bar.h
Normal file
64
editor/settings/editor_event_search_bar.h
Normal 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();
|
||||
};
|
1074
editor/settings/editor_feature_profile.cpp
Normal file
1074
editor/settings/editor_feature_profile.cpp
Normal file
File diff suppressed because it is too large
Load Diff
186
editor/settings/editor_feature_profile.h
Normal file
186
editor/settings/editor_feature_profile.h
Normal 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();
|
||||
};
|
296
editor/settings/editor_folding.cpp
Normal file
296
editor/settings/editor_folding.cpp
Normal 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);
|
||||
}
|
54
editor/settings/editor_folding.h
Normal file
54
editor/settings/editor_folding.h
Normal 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);
|
||||
};
|
141
editor/settings/editor_layouts_dialog.cpp
Normal file
141
editor/settings/editor_layouts_dialog.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
/**************************************************************************/
|
||||
/* editor_layouts_dialog.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "editor_layouts_dialog.h"
|
||||
|
||||
#include "core/io/config_file.h"
|
||||
#include "editor/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);
|
||||
}
|
58
editor/settings/editor_layouts_dialog.h
Normal file
58
editor/settings/editor_layouts_dialog.h
Normal 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);
|
||||
};
|
2161
editor/settings/editor_settings.cpp
Normal file
2161
editor/settings/editor_settings.cpp
Normal file
File diff suppressed because it is too large
Load Diff
221
editor/settings/editor_settings.h
Normal file
221
editor/settings/editor_settings.h
Normal 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);
|
1103
editor/settings/editor_settings_dialog.cpp
Normal file
1103
editor/settings/editor_settings_dialog.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
editor/settings/editor_settings_dialog.h
Normal file
178
editor/settings/editor_settings_dialog.h
Normal 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;
|
||||
};
|
277
editor/settings/event_listener_line_edit.cpp
Normal file
277
editor/settings/event_listener_line_edit.cpp
Normal 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"));
|
||||
}
|
76
editor/settings/event_listener_line_edit.h
Normal file
76
editor/settings/event_listener_line_edit.h
Normal 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();
|
||||
};
|
777
editor/settings/input_event_configuration_dialog.cpp
Normal file
777
editor/settings/input_event_configuration_dialog.cpp
Normal 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);
|
||||
}
|
138
editor/settings/input_event_configuration_dialog.h
Normal file
138
editor/settings/input_event_configuration_dialog.h
Normal 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();
|
||||
};
|
858
editor/settings/project_settings_editor.cpp
Normal file
858
editor/settings/project_settings_editor.cpp
Normal 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.
|
||||
}
|
157
editor/settings/project_settings_editor.h
Normal file
157
editor/settings/project_settings_editor.h
Normal 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);
|
||||
};
|
Reference in New Issue
Block a user