Editor: Restructure editor code

Moving various editor files into sub folders to reduce clutter
This commit is contained in:
A Thousand Ships 2025-06-10 16:47:26 +02:00
parent 3954b2459d
commit f11aff3841
No known key found for this signature in database
GPG key ID: DEFC5A5B1306947D
601 changed files with 1195 additions and 1019 deletions

2015
editor/gui/code_editor.cpp Normal file

File diff suppressed because it is too large Load diff

305
editor/gui/code_editor.h Normal file
View file

@ -0,0 +1,305 @@
/**************************************************************************/
/* code_editor.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/code_edit.h"
#include "scene/gui/dialogs.h"
class Button;
class CheckBox;
class CodeTextEditor;
class Label;
class LineEdit;
class MenuButton;
class RichTextLabel;
class Timer;
class GotoLinePopup : public PopupPanel {
GDCLASS(GotoLinePopup, PopupPanel);
Variant original_state;
LineEdit *line_input = nullptr;
CodeTextEditor *text_editor = nullptr;
void _goto_line();
void _submit();
protected:
void _notification(int p_what);
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
public:
void popup_find_line(CodeTextEditor *p_text_editor);
GotoLinePopup();
};
class FindReplaceBar : public HBoxContainer {
GDCLASS(FindReplaceBar, HBoxContainer);
enum SearchMode {
SEARCH_CURRENT,
SEARCH_NEXT,
SEARCH_PREV,
};
Button *toggle_replace_button = nullptr;
LineEdit *search_text = nullptr;
Label *matches_label = nullptr;
Button *find_prev = nullptr;
Button *find_next = nullptr;
CheckBox *case_sensitive = nullptr;
CheckBox *whole_words = nullptr;
Button *hide_button = nullptr;
LineEdit *replace_text = nullptr;
Button *replace = nullptr;
Button *replace_all = nullptr;
CheckBox *selection_only = nullptr;
HBoxContainer *hbc_button_replace = nullptr;
HBoxContainer *hbc_option_replace = nullptr;
CodeTextEditor *base_text_editor = nullptr;
CodeEdit *text_editor = nullptr;
uint32_t flags = 0;
int result_line = 0;
int result_col = 0;
int results_count = -1;
int results_count_to_current = -1;
bool replace_all_mode = false;
bool preserve_cursor = false;
virtual void input(const Ref<InputEvent> &p_event) override;
void _get_search_from(int &r_line, int &r_col, SearchMode p_search_mode);
void _update_results_count();
void _update_matches_display();
void _show_search(bool p_with_replace, bool p_show_only);
void _hide_bar();
void _update_toggle_replace_button(bool p_replace_visible);
void _editor_text_changed();
void _search_options_changed(bool p_pressed);
void _search_text_changed(const String &p_text);
void _search_text_submitted(const String &p_text);
void _replace_text_submitted(const String &p_text);
void _toggle_replace_pressed();
protected:
void _notification(int p_what);
void _update_flags(bool p_direction_backwards);
bool _search(uint32_t p_flags, int p_from_line, int p_from_col);
void _replace();
void _replace_all();
static void _bind_methods();
public:
String get_search_text() const;
String get_replace_text() const;
bool is_case_sensitive() const;
bool is_whole_words() const;
bool is_selection_only() const;
void set_text_edit(CodeTextEditor *p_text_editor);
void popup_search(bool p_show_only = false);
void popup_replace();
bool search_current();
bool search_prev();
bool search_next();
bool needs_to_count_results = true;
bool line_col_changed_for_result = false;
FindReplaceBar();
};
typedef void (*CodeTextEditorCodeCompleteFunc)(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced);
class CodeTextEditor : public VBoxContainer {
GDCLASS(CodeTextEditor, VBoxContainer);
CodeEdit *text_editor = nullptr;
FindReplaceBar *find_replace_bar = nullptr;
HBoxContainer *status_bar = nullptr;
Button *toggle_files_button = nullptr;
Control *toggle_files_list = nullptr;
Button *error_button = nullptr;
Button *warning_button = nullptr;
MenuButton *zoom_button = nullptr;
Label *line_and_col_txt = nullptr;
Label *indentation_txt = nullptr;
Label *info = nullptr;
Timer *idle = nullptr;
float idle_time = 0.0f;
float idle_time_with_errors = 0.0f;
bool code_complete_enabled = true;
Timer *code_complete_timer = nullptr;
int code_complete_timer_line = 0;
float zoom_factor = 1.0f;
RichTextLabel *error = nullptr;
int error_line;
int error_column;
bool preview_navigation_change = false;
Dictionary previous_state;
void _update_text_editor_theme();
void _update_font_ligatures();
void _complete_request();
Ref<Texture2D> _get_completion_icon(const ScriptLanguage::CodeCompletionOption &p_option);
virtual void input(const Ref<InputEvent> &event) override;
void _text_editor_gui_input(const Ref<InputEvent> &p_event);
Color completion_font_color;
Color completion_string_color;
Color completion_string_name_color;
Color completion_node_path_color;
Color completion_comment_color;
Color completion_doc_comment_color;
CodeTextEditorCodeCompleteFunc code_complete_func;
void *code_complete_ud = nullptr;
void _zoom_in();
void _zoom_out();
void _zoom_to(float p_zoom_factor);
void _update_error_content_height();
void _error_button_pressed();
void _warning_button_pressed();
void _set_show_errors_panel(bool p_show);
void _set_show_warnings_panel(bool p_show);
void _error_pressed(const Ref<InputEvent> &p_event);
void _zoom_popup_id_pressed(int p_idx);
void _toggle_files_pressed();
protected:
virtual void _load_theme_settings() {}
virtual void _validate_script() {}
virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) {}
void _text_changed_idle_timeout();
void _code_complete_timer_timeout();
void _text_changed();
void _line_col_changed();
void _notification(int);
static void _bind_methods();
bool is_warnings_panel_opened = false;
bool is_errors_panel_opened = false;
public:
void trim_trailing_whitespace();
void trim_final_newlines();
void insert_final_newline();
enum CaseStyle {
UPPER,
LOWER,
CAPITALIZE,
};
void convert_case(CaseStyle p_case);
void set_indent_using_spaces(bool p_use_spaces);
/// Toggle inline comment on currently selected lines, or on current line if nothing is selected,
/// by adding or removing comment delimiter
void toggle_inline_comment(const String &delimiter);
void goto_line(int p_line, int p_column = 0);
void goto_line_selection(int p_line, int p_begin, int p_end);
void goto_line_centered(int p_line, int p_column = 0);
void set_executing_line(int p_line);
void clear_executing_line();
Variant get_edit_state();
void set_edit_state(const Variant &p_state);
Variant get_navigation_state();
Variant get_previous_state();
void store_previous_state();
bool is_previewing_navigation_change() const;
void set_preview_navigation_change(bool p_preview);
void set_error_count(int p_error_count);
void set_warning_count(int p_warning_count);
void update_editor_settings();
void set_error(const String &p_error);
void set_error_pos(int p_line, int p_column);
Point2i get_error_pos() const;
void update_line_and_column() { _line_col_changed(); }
CodeEdit *get_text_editor() { return text_editor; }
FindReplaceBar *get_find_replace_bar() { return find_replace_bar; }
void set_find_replace_bar(FindReplaceBar *p_bar);
void remove_find_replace_bar();
virtual void apply_code() {}
virtual void goto_error();
void toggle_bookmark();
void goto_next_bookmark();
void goto_prev_bookmark();
void remove_all_bookmarks();
void set_zoom_factor(float p_zoom_factor);
float get_zoom_factor();
void set_code_complete_func(CodeTextEditorCodeCompleteFunc p_code_complete_func, void *p_ud);
void validate_script();
void set_toggle_list_control(Control *p_control);
void show_toggle_files_button();
void update_toggle_files_button();
CodeTextEditor();
};

View file

@ -0,0 +1,932 @@
/**************************************************************************/
/* create_dialog.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "create_dialog.h"
#include "core/object/class_db.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/file_system/editor_paths.h"
#include "editor/settings/editor_feature_profile.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const String &p_current_type, const String &p_current_name) {
_fill_type_list();
icon_fallback = search_options->has_theme_icon(base_type, EditorStringName(EditorIcons)) ? base_type : "Object";
if (p_dont_clear) {
search_box->select_all();
} else {
search_box->clear();
}
if (p_replace_mode) {
search_box->set_text(p_current_type);
}
search_box->grab_focus();
_update_search();
if (p_replace_mode) {
set_title(vformat(TTR("Change Type of \"%s\""), p_current_name));
set_ok_button_text(TTR("Change"));
} else {
set_title(vformat(TTR("Create New %s"), base_type));
set_ok_button_text(TTR("Create"));
}
_load_favorites_and_history();
_save_and_update_favorite_list();
// Restore valid window bounds or pop up at default size.
Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "create_new_node", Rect2());
if (saved_size != Rect2()) {
popup(saved_size);
} else {
popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
}
}
void CreateDialog::_fill_type_list() {
List<StringName> complete_type_list;
ClassDB::get_class_list(&complete_type_list);
ScriptServer::get_global_class_list(&complete_type_list);
EditorData &ed = EditorNode::get_editor_data();
HashMap<String, DocData::ClassDoc> &class_docs_list = EditorHelp::get_doc_data()->class_list;
for (const StringName &type : complete_type_list) {
if (!_should_hide_type(type)) {
TypeInfo type_info;
type_info.type_name = type;
const DocData::ClassDoc *class_docs = class_docs_list.getptr(type);
if (class_docs) {
type_info.search_keywords = class_docs->keywords.split(",");
for (int i = 0; i < type_info.search_keywords.size(); i++) {
type_info.search_keywords.set(i, type_info.search_keywords[i].strip_edges());
}
}
type_info_list.push_back(type_info);
if (!ed.get_custom_types().has(type)) {
continue;
}
const Vector<EditorData::CustomType> &ct = ed.get_custom_types()[type];
for (int i = 0; i < ct.size(); i++) {
custom_type_parents[ct[i].name] = type;
custom_type_indices[ct[i].name] = i;
TypeInfo custom_type_info;
custom_type_info.type_name = ct[i].name;
type_info_list.push_back(custom_type_info);
}
}
}
struct TypeInfoCompare {
StringName::AlphCompare compare;
_FORCE_INLINE_ bool operator()(const TypeInfo &l, const TypeInfo &r) const {
return compare(l.type_name, r.type_name);
}
};
type_info_list.sort_custom<TypeInfoCompare>();
}
bool CreateDialog::_is_type_preferred(const String &p_type) const {
if (ClassDB::class_exists(p_type)) {
return ClassDB::is_parent_class(p_type, preferred_search_result_type);
}
return EditorNode::get_editor_data().script_class_is_parent(p_type, preferred_search_result_type);
}
void CreateDialog::_script_button_clicked(TreeItem *p_item, int p_column, int p_button_id, MouseButton p_mouse_button_index) {
if (p_mouse_button_index != MouseButton::LEFT) {
return;
}
// The id of opening-script button is 1.
if (p_button_id != 1) {
return;
}
String scr_path = ScriptServer::get_global_class_path(p_item->get_text(0));
Ref<Script> scr = ResourceLoader::load(scr_path, "Script");
ERR_FAIL_COND_MSG(scr.is_null(), vformat("Could not load the script from resource path: %s", scr_path));
EditorNode::get_singleton()->push_item_no_inspector(scr.ptr());
hide();
_cleanup();
}
bool CreateDialog::_is_class_disabled_by_feature_profile(const StringName &p_class) const {
Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
return profile.is_valid() && profile->is_class_disabled(p_class);
}
bool CreateDialog::_should_hide_type(const StringName &p_type) const {
if (_is_class_disabled_by_feature_profile(p_type)) {
return true;
}
if (is_base_type_node && p_type.operator String().begins_with("Editor")) {
return true; // Do not show editor nodes.
}
if (ClassDB::class_exists(p_type)) {
if (!ClassDB::can_instantiate(p_type) || ClassDB::is_virtual(p_type)) {
return true; // Can't create abstract or virtual class.
}
if (!ClassDB::is_parent_class(p_type, base_type)) {
return true; // Wrong inheritance.
}
if (!ClassDB::is_class_exposed(p_type)) {
return true; // Unexposed types.
}
for (const StringName &E : type_blacklist) {
if (ClassDB::is_parent_class(p_type, E)) {
return true; // Parent type is blacklisted.
}
}
for (const StringName &F : custom_type_blocklist) {
if (ClassDB::is_parent_class(p_type, F)) {
return true; // Parent type is excluded in custom type blocklist.
}
}
} else {
if (!ScriptServer::is_global_class(p_type)) {
return true;
}
if (!EditorNode::get_editor_data().script_class_is_parent(p_type, base_type)) {
return true; // Wrong inheritance.
}
StringName native_type = ScriptServer::get_global_class_native_base(p_type);
if (ClassDB::class_exists(native_type)) {
if (!ClassDB::can_instantiate(native_type)) {
return true;
} else if (custom_type_blocklist.has(p_type) || custom_type_blocklist.has(native_type)) {
return true;
}
}
String script_path = ScriptServer::get_global_class_path(p_type);
if (script_path.begins_with("res://addons/")) {
int i = script_path.find_char('/', 13); // 13 is length of "res://addons/".
while (i > -1) {
const String plugin_path = script_path.substr(0, i).path_join("plugin.cfg");
if (FileAccess::exists(plugin_path)) {
return !EditorNode::get_singleton()->is_addon_plugin_enabled(plugin_path);
}
i = script_path.find_char('/', i + 1);
}
}
// Abstract scripts cannot be instantiated.
String path = ScriptServer::get_global_class_path(p_type);
Ref<Script> scr = ResourceLoader::load(path, "Script");
return scr.is_null() || scr->is_abstract();
}
return false;
}
void CreateDialog::_update_search() {
search_options->clear();
search_options_types.clear();
TreeItem *root = search_options->create_item();
root->set_text(0, base_type);
root->set_icon(0, search_options->get_editor_theme_icon(icon_fallback));
search_options_types[base_type] = root;
_configure_search_option_item(root, base_type, ClassDB::class_exists(base_type) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE, "");
const String search_text = search_box->get_text();
float highest_score = 0.0f;
StringName best_match;
for (const TypeInfo &candidate : type_info_list) {
String match_keyword;
// First check if the name matches. If it does not, try the search keywords.
float score = _score_type(candidate.type_name, search_text);
if (score < 0.0f) {
for (const String &keyword : candidate.search_keywords) {
score = _score_type(keyword, search_text);
// Reduce the score of keywords, since they are an indirect match.
score *= 0.1f;
if (score >= 0.0f) {
match_keyword = keyword;
break;
}
}
}
// Search did not match.
if (score < 0.0f) {
continue;
}
_add_type(candidate.type_name, ClassDB::class_exists(candidate.type_name) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE, match_keyword);
if (score > highest_score) {
highest_score = score;
best_match = candidate.type_name;
}
}
// Select the best result.
if (search_text.is_empty()) {
select_type(base_type);
} else if (best_match != StringName()) {
select_type(best_match);
} else {
favorite->set_disabled(true);
help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]")));
get_ok_button()->set_disabled(true);
search_options->deselect_all();
}
}
void CreateDialog::_add_type(const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword) {
if (search_options_types.has(p_type)) {
return;
}
TypeCategory inherited_type = TypeCategory::OTHER_TYPE;
StringName inherits;
if (p_type_category == TypeCategory::CPP_TYPE) {
inherits = ClassDB::get_parent_class(p_type);
inherited_type = TypeCategory::CPP_TYPE;
} else {
if (p_type_category == TypeCategory::PATH_TYPE) {
ERR_FAIL_COND(!ResourceLoader::exists(p_type, "Script"));
Ref<Script> scr = ResourceLoader::load(p_type, "Script");
ERR_FAIL_COND(scr.is_null());
Ref<Script> base = scr->get_base_script();
if (base.is_null()) {
// Must be a native base type.
StringName extends = scr->get_instance_base_type();
if (extends == StringName()) {
// Not a valid script (has compile errors), we therefore ignore it as it can not be instantiated anyway (when selected).
return;
}
inherits = extends;
inherited_type = TypeCategory::CPP_TYPE;
} else {
inherits = base->get_global_name();
if (inherits == StringName()) {
inherits = base->get_path();
inherited_type = TypeCategory::PATH_TYPE;
}
}
} else if (ScriptServer::is_global_class(p_type)) {
inherits = ScriptServer::get_global_class_base(p_type);
bool is_native_class = ClassDB::class_exists(inherits);
inherited_type = is_native_class ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE;
} else {
inherits = custom_type_parents[p_type];
if (ClassDB::class_exists(inherits)) {
inherited_type = TypeCategory::CPP_TYPE;
}
}
}
// Should never happen, but just in case...
ERR_FAIL_COND(inherits == StringName());
_add_type(inherits, inherited_type, "");
TreeItem *item = search_options->create_item(search_options_types[inherits]);
search_options_types[p_type] = item;
_configure_search_option_item(item, p_type, p_type_category, p_match_keyword);
}
void CreateDialog::_configure_search_option_item(TreeItem *r_item, const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword) {
bool script_type = ScriptServer::is_global_class(p_type);
bool is_abstract = false;
bool is_custom_type = false;
String type_name;
String text;
if (p_type_category == TypeCategory::CPP_TYPE) {
type_name = p_type;
text = p_type;
} else if (p_type_category == TypeCategory::PATH_TYPE) {
type_name = "\"" + p_type + "\"";
text = "\"" + p_type + "\"";
} else if (script_type) {
is_custom_type = true;
type_name = p_type;
text = p_type;
is_abstract = ScriptServer::is_global_class_abstract(p_type);
String tooltip = TTR("Script path: %s");
bool is_tool = ScriptServer::is_global_class_tool(p_type);
if (is_tool) {
tooltip = TTR("The script will run in the editor.") + "\n" + tooltip;
}
r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, ScriptServer::get_global_class_path(p_type)));
if (is_tool) {
int button_index = r_item->get_button_count(0) - 1;
r_item->set_button_color(0, button_index, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
}
} else {
is_custom_type = true;
type_name = custom_type_parents[p_type];
text = p_type;
}
if (!p_match_keyword.is_empty()) {
text += " - " + TTR(vformat("Matches the \"%s\" keyword.", p_match_keyword));
}
r_item->set_text(0, text);
Array meta;
meta.append(is_custom_type);
meta.append(type_name);
r_item->set_metadata(0, meta);
bool can_instantiate = (p_type_category == TypeCategory::CPP_TYPE && ClassDB::can_instantiate(p_type)) ||
(p_type_category == TypeCategory::OTHER_TYPE && !is_abstract);
bool instantiable = can_instantiate && !(ClassDB::class_exists(p_type) && ClassDB::is_virtual(p_type));
r_item->set_meta(SNAME("__instantiable"), instantiable);
r_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_type));
if (!instantiable) {
r_item->set_custom_color(0, search_options->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
}
HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(p_type);
bool is_deprecated = (class_doc && class_doc->value.is_deprecated);
bool is_experimental = (class_doc && class_doc->value.is_experimental);
if (is_deprecated) {
r_item->add_button(0, get_editor_theme_icon("StatusError"), 0, false, TTR("This class is marked as deprecated."));
} else if (is_experimental) {
r_item->add_button(0, get_editor_theme_icon("NodeWarning"), 0, false, TTR("This class is marked as experimental."));
}
if (!search_box->get_text().is_empty()) {
r_item->set_collapsed(false);
} else {
// Don't collapse the root node or an abstract node on the first tree level.
bool should_collapse = p_type != base_type && (r_item->get_parent()->get_text(0) != base_type || can_instantiate);
if (should_collapse && bool(EDITOR_GET("docks/scene_tree/start_create_dialog_fully_expanded"))) {
should_collapse = false; // Collapse all nodes anyway.
}
r_item->set_collapsed(should_collapse);
}
const String &description = DTR(class_doc ? class_doc->value.brief_description : "");
r_item->set_tooltip_text(0, description);
if (p_type_category == TypeCategory::OTHER_TYPE && !script_type) {
Ref<Texture2D> icon = EditorNode::get_editor_data().get_custom_types()[custom_type_parents[p_type]][custom_type_indices[p_type]].icon;
if (icon.is_valid()) {
r_item->set_icon(0, icon);
}
}
}
float CreateDialog::_score_type(const String &p_type, const String &p_search) const {
if (p_search.is_empty()) {
return 0.0f;
}
// Determine the best match for a non-empty search.
if (!p_search.is_subsequence_ofn(p_type)) {
return -1.0f;
}
if (p_type == p_search) {
// Always favor an exact match (case-sensitive), since clicking a favorite will set the search text to the type.
return 1.0f;
}
const String &type_name = p_type.get_slicec(' ', 0);
float inverse_length = 1.f / float(type_name.length());
// Favor types where search term is a substring close to the start of the type.
float w = 0.5f;
int pos = type_name.findn(p_search);
float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
// Favor shorter items: they resemble the search term more.
w = 0.9f;
score *= (1 - w) + w * MIN(1.0f, p_search.length() * inverse_length);
score *= _is_type_preferred(type_name) ? 1.0f : 0.9f;
// Add score for being a favorite type.
score *= favorite_list.has(type_name) ? 1.0f : 0.8f;
// Look through at most 5 recent items
bool in_recent = false;
constexpr int RECENT_COMPLETION_SIZE = 5;
for (int i = 0; i < MIN(RECENT_COMPLETION_SIZE - 1, recent->get_item_count()); i++) {
if (recent->get_item_text(i) == type_name) {
in_recent = true;
break;
}
}
score *= in_recent ? 1.0f : 0.9f;
return score;
}
void CreateDialog::_cleanup() {
type_info_list.clear();
favorite_list.clear();
favorites->clear();
recent->clear();
custom_type_parents.clear();
custom_type_indices.clear();
}
void CreateDialog::_confirmed() {
String selected_item = get_selected_type();
if (selected_item.is_empty()) {
return;
}
TreeItem *selected = search_options->get_selected();
if (!selected->get_meta("__instantiable", true)) {
return;
}
{
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("create_recent." + base_type), FileAccess::WRITE);
if (f.is_valid()) {
f->store_line(selected_item);
constexpr int RECENT_HISTORY_SIZE = 15;
for (int i = 0; i < MIN(RECENT_HISTORY_SIZE - 1, recent->get_item_count()); i++) {
if (recent->get_item_text(i) != selected_item) {
f->store_line(recent->get_item_text(i));
}
}
}
}
// To prevent, emitting an error from the transient window (shader dialog for example) hide this dialog before emitting the "create" signal.
hide();
emit_signal(SNAME("create"));
_cleanup();
}
void CreateDialog::_text_changed(const String &p_newtext) {
_update_search();
}
void CreateDialog::_sbox_input(const Ref<InputEvent> &p_event) {
// Redirect navigational key events to the tree.
Ref<InputEventKey> key = p_event;
if (key.is_valid()) {
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
search_options->gui_input(key);
search_box->accept_event();
} else if (key->is_action_pressed("ui_select", true)) {
TreeItem *ti = search_options->get_selected();
if (ti) {
ti->set_collapsed(!ti->is_collapsed());
}
search_box->accept_event();
}
}
}
void CreateDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
connect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
} break;
case NOTIFICATION_EXIT_TREE: {
disconnect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible.
search_box->select_all();
} else {
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));
}
} break;
case NOTIFICATION_THEME_CHANGED: {
const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
search_options->add_theme_constant_override("icon_max_width", icon_width);
favorites->add_theme_constant_override("icon_max_width", icon_width);
recent->set_fixed_icon_size(Size2(icon_width, icon_width));
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
favorite->set_button_icon(get_editor_theme_icon(SNAME("Favorites")));
} break;
}
}
void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
if (!search_options_types.has(p_type)) {
return;
}
TreeItem *to_select = search_options_types[p_type];
to_select->select(0);
search_options->scroll_to_item(to_select, p_center_on_item);
help_bit->parse_symbol("class|" + p_type + "|");
favorite->set_disabled(false);
favorite->set_pressed(favorite_list.has(p_type));
if (to_select->get_meta("__instantiable", true)) {
get_ok_button()->set_disabled(false);
get_ok_button()->set_tooltip_text(String());
} else {
get_ok_button()->set_disabled(true);
get_ok_button()->set_tooltip_text(TTR("The selected class can't be instantiated."));
}
}
void CreateDialog::select_base() {
if (search_options_types.is_empty()) {
_update_search();
}
select_type(base_type, false);
}
String CreateDialog::get_selected_type() {
TreeItem *selected = search_options->get_selected();
if (!selected) {
return String();
}
return selected->get_text(0);
}
void CreateDialog::set_base_type(const String &p_base) {
base_type = p_base;
is_base_type_node = ClassDB::is_parent_class(p_base, "Node");
}
Variant CreateDialog::instantiate_selected() {
TreeItem *selected = search_options->get_selected();
if (!selected) {
return Variant();
}
Array meta = selected->get_metadata(0).operator Array();
ERR_FAIL_COND_V(meta.size() != 2, Variant());
bool is_custom_type = meta[0].operator bool();
String type_name = meta[1].operator String();
Variant obj;
if (is_custom_type) {
if (ScriptServer::is_global_class(type_name)) {
obj = EditorNode::get_editor_data().script_class_instance(type_name);
Node *n = Object::cast_to<Node>(obj);
if (n) {
n->set_name(type_name);
}
} else {
obj = EditorNode::get_editor_data().instantiate_custom_type(selected->get_text(0), type_name);
}
} else {
obj = ClassDB::instantiate(type_name);
}
EditorNode::get_editor_data().instantiate_object_properties(obj);
return obj;
}
void CreateDialog::_item_selected() {
String name = get_selected_type();
select_type(name, false);
}
void CreateDialog::_hide_requested() {
_cancel_pressed(); // From AcceptDialog.
}
void CreateDialog::cancel_pressed() {
_cleanup();
}
void CreateDialog::_favorite_toggled() {
TreeItem *item = search_options->get_selected();
if (!item) {
return;
}
String name = item->get_text(0);
if (favorite_list.has(name)) {
favorite_list.erase(name);
favorite->set_pressed(false);
} else {
favorite_list.push_back(name);
favorite->set_pressed(true);
}
_save_and_update_favorite_list();
}
void CreateDialog::_history_selected(int p_idx) {
search_box->set_text(recent->get_item_text(p_idx).get_slicec(' ', 0));
favorites->deselect_all();
_update_search();
}
void CreateDialog::_favorite_selected() {
TreeItem *item = favorites->get_selected();
if (!item) {
return;
}
search_box->set_text(item->get_text(0).get_slicec(' ', 0));
recent->deselect_all();
_update_search();
}
void CreateDialog::_history_activated(int p_idx) {
_history_selected(p_idx);
_confirmed();
}
void CreateDialog::_favorite_activated() {
_favorite_selected();
_confirmed();
}
Variant CreateDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
TreeItem *ti = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_selected() : favorites->get_item_at_position(p_point);
if (ti) {
Dictionary d;
d["type"] = "create_favorite_drag";
d["class"] = ti->get_text(0);
Button *tb = memnew(Button);
tb->set_flat(true);
tb->set_button_icon(ti->get_icon(0));
tb->set_text(ti->get_text(0));
tb->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
favorites->set_drag_preview(tb);
return d;
}
return Variant();
}
bool CreateDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
Dictionary d = p_data;
if (d.has("type") && String(d["type"]) == "create_favorite_drag") {
favorites->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
return true;
}
return false;
}
void CreateDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
Dictionary d = p_data;
TreeItem *ti = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_selected() : favorites->get_item_at_position(p_point);
if (!ti) {
return;
}
String drop_at = ti->get_text(0);
int ds = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_drop_section_at_position(favorites->get_item_rect(ti).position) : favorites->get_drop_section_at_position(p_point);
int drop_idx = favorite_list.find(drop_at);
if (drop_idx < 0) {
return;
}
String type = d["class"];
int from_idx = favorite_list.find(type);
if (from_idx < 0) {
return;
}
if (drop_idx == from_idx) {
ds = -1; //cause it will be gone
} else if (drop_idx > from_idx) {
drop_idx--;
}
favorite_list.remove_at(from_idx);
if (ds < 0) {
favorite_list.insert(drop_idx, type);
} else {
if (drop_idx >= favorite_list.size() - 1) {
favorite_list.push_back(type);
} else {
favorite_list.insert(drop_idx + 1, type);
}
}
_save_and_update_favorite_list();
}
void CreateDialog::_save_and_update_favorite_list() {
favorites->clear();
TreeItem *root = favorites->create_item();
{
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites." + base_type), FileAccess::WRITE);
if (f.is_valid()) {
for (int i = 0; i < favorite_list.size(); i++) {
String l = favorite_list[i];
String name = l.get_slicec(' ', 0);
if (!EditorNode::get_editor_data().is_type_recognized(name)) {
continue;
}
f->store_line(l);
if (_is_class_disabled_by_feature_profile(name)) {
continue;
}
TreeItem *ti = favorites->create_item(root);
ti->set_text(0, l);
ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(name));
}
}
}
emit_signal(SNAME("favorites_updated"));
}
void CreateDialog::_load_favorites_and_history() {
String dir = EditorPaths::get_singleton()->get_project_settings_dir();
Ref<FileAccess> f = FileAccess::open(dir.path_join("create_recent." + base_type), FileAccess::READ);
if (f.is_valid()) {
while (!f->eof_reached()) {
String l = f->get_line().strip_edges();
String name = l.get_slicec(' ', 0);
if (EditorNode::get_editor_data().is_type_recognized(name) && !_is_class_disabled_by_feature_profile(name)) {
recent->add_item(l, EditorNode::get_singleton()->get_class_icon(name));
}
}
}
f = FileAccess::open(dir.path_join("favorites." + base_type), FileAccess::READ);
if (f.is_valid()) {
while (!f->eof_reached()) {
String l = f->get_line().strip_edges();
if (!l.is_empty()) {
favorite_list.push_back(l);
}
}
}
}
void CreateDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("create"));
ADD_SIGNAL(MethodInfo("favorites_updated"));
}
CreateDialog::CreateDialog() {
base_type = "Object";
preferred_search_result_type = "";
type_blacklist.insert("PluginScript"); // PluginScript must be initialized before use, which is not possible here.
type_blacklist.insert("ScriptCreateDialog"); // This is an exposed editor Node that doesn't have an Editor prefix.
HSplitContainer *hsc = memnew(HSplitContainer);
add_child(hsc);
VSplitContainer *vsc = memnew(VSplitContainer);
hsc->add_child(vsc);
VBoxContainer *fav_vb = memnew(VBoxContainer);
fav_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
fav_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vsc->add_child(fav_vb);
favorites = memnew(Tree);
favorites->set_accessibility_name(TTRC("Favorites:"));
favorites->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
favorites->set_hide_root(true);
favorites->set_hide_folding(true);
favorites->set_allow_reselect(true);
favorites->connect("cell_selected", callable_mp(this, &CreateDialog::_favorite_selected));
favorites->connect("item_activated", callable_mp(this, &CreateDialog::_favorite_activated));
favorites->add_theme_constant_override("draw_guides", 1);
favorites->set_theme_type_variation("TreeSecondary");
SET_DRAG_FORWARDING_GCD(favorites, CreateDialog);
fav_vb->add_margin_child(TTR("Favorites:"), favorites, true);
VBoxContainer *rec_vb = memnew(VBoxContainer);
vsc->add_child(rec_vb);
rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
recent = memnew(ItemList);
recent->set_accessibility_name(TTRC("Recent:"));
recent->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
rec_vb->add_margin_child(TTR("Recent:"), recent, true);
recent->set_allow_reselect(true);
recent->connect(SceneStringName(item_selected), callable_mp(this, &CreateDialog::_history_selected));
recent->connect("item_activated", callable_mp(this, &CreateDialog::_history_activated));
recent->add_theme_constant_override("draw_guides", 1);
recent->set_theme_type_variation("ItemListSecondary");
VBoxContainer *vbc = memnew(VBoxContainer);
vbc->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hsc->add_child(vbc);
search_box = memnew(LineEdit);
search_box->set_accessibility_name(TTRC("Search"));
search_box->set_clear_button_enabled(true);
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_box->connect(SceneStringName(text_changed), callable_mp(this, &CreateDialog::_text_changed));
search_box->connect(SceneStringName(gui_input), callable_mp(this, &CreateDialog::_sbox_input));
HBoxContainer *search_hb = memnew(HBoxContainer);
search_hb->add_child(search_box);
favorite = memnew(Button);
favorite->set_toggle_mode(true);
favorite->set_tooltip_text(TTR("(Un)favorite selected item."));
favorite->connect(SceneStringName(pressed), callable_mp(this, &CreateDialog::_favorite_toggled));
search_hb->add_child(favorite);
vbc->add_margin_child(TTR("Search:"), search_hb);
search_options = memnew(Tree);
search_options->set_accessibility_name(TTRC("Matches:"));
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
search_options->connect("item_activated", callable_mp(this, &CreateDialog::_confirmed));
search_options->connect("cell_selected", callable_mp(this, &CreateDialog::_item_selected));
search_options->connect("button_clicked", callable_mp(this, &CreateDialog::_script_button_clicked));
vbc->add_margin_child(TTR("Matches:"), search_options, true);
help_bit = memnew(EditorHelpBit);
help_bit->set_accessibility_name(TTRC("Description:"));
help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested));
vbc->add_margin_child(TTR("Description:"), help_bit);
register_text_enter(search_box);
set_hide_on_ok(false);
set_clamp_to_embedder(true);
}

130
editor/gui/create_dialog.h Normal file
View file

@ -0,0 +1,130 @@
/**************************************************************************/
/* create_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "editor/doc/editor_help.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/tree.h"
class CreateDialog : public ConfirmationDialog {
GDCLASS(CreateDialog, ConfirmationDialog);
enum TypeCategory {
CPP_TYPE,
PATH_TYPE,
OTHER_TYPE
};
struct TypeInfo {
StringName type_name;
PackedStringArray search_keywords;
};
LineEdit *search_box = nullptr;
Tree *search_options = nullptr;
String base_type;
bool is_base_type_node = false;
String icon_fallback;
String preferred_search_result_type;
Button *favorite = nullptr;
Vector<String> favorite_list;
Tree *favorites = nullptr;
ItemList *recent = nullptr;
EditorHelpBit *help_bit = nullptr;
HashMap<String, TreeItem *> search_options_types;
HashMap<String, String> custom_type_parents;
HashMap<String, int> custom_type_indices;
List<TypeInfo> type_info_list;
HashSet<StringName> type_blacklist;
HashSet<StringName> custom_type_blocklist;
void _update_search();
bool _should_hide_type(const StringName &p_type) const;
void _add_type(const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword);
void _configure_search_option_item(TreeItem *r_item, const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword);
float _score_type(const String &p_type, const String &p_search) const;
bool _is_type_preferred(const String &p_type) const;
void _script_button_clicked(TreeItem *p_item, int p_column, int p_button_id, MouseButton p_mouse_button_index);
void _fill_type_list();
void _cleanup();
void _sbox_input(const Ref<InputEvent> &p_event);
void _text_changed(const String &p_newtext);
void select_type(const String &p_type, bool p_center_on_item = true);
void _item_selected();
void _hide_requested();
void _confirmed();
virtual void cancel_pressed() override;
void _favorite_toggled();
void _history_selected(int p_idx);
void _favorite_selected();
void _history_activated(int p_idx);
void _favorite_activated();
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
bool _is_class_disabled_by_feature_profile(const StringName &p_class) const;
void _load_favorites_and_history();
protected:
void _notification(int p_what);
static void _bind_methods();
void _save_and_update_favorite_list();
public:
Variant instantiate_selected();
String get_selected_type();
void set_base_type(const String &p_base);
String get_base_type() const { return base_type; }
void select_base();
void set_type_blocklist(const HashSet<StringName> &p_blocklist) { custom_type_blocklist = p_blocklist; }
void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; }
void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_current_type = "", const String &p_current_name = "");
CreateDialog();
};

247
editor/gui/credits_roll.cpp Normal file
View file

@ -0,0 +1,247 @@
/**************************************************************************/
/* credits_roll.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 "credits_roll.h"
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/input/input.h"
#include "core/license.gen.h"
#include "core/string/string_builder.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
Label *CreditsRoll::_create_label(const String &p_with_text, LabelSize p_size) {
Label *label = memnew(Label);
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
label->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
label->set_text(p_with_text);
switch (p_size) {
case LabelSize::NORMAL: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal);
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
} break;
case LabelSize::HEADER: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_header);
label->add_theme_font_override(SceneStringName(font), bold_font);
} break;
case LabelSize::BIG_HEADER: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_big_header);
label->add_theme_font_override(SceneStringName(font), bold_font);
} break;
}
content->add_child(label);
return label;
}
void CreditsRoll::_create_nothing(int p_size) {
if (p_size == -1) {
p_size = 30 * EDSCALE;
}
Control *c = memnew(Control);
c->set_custom_minimum_size(Vector2(0, p_size));
content->add_child(c);
}
String CreditsRoll::_build_string(const char *const *p_from) const {
StringBuilder sb;
while (*p_from) {
sb.append(String::utf8(*p_from));
sb.append("\n");
p_from++;
}
return sb.as_string();
}
void CreditsRoll::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
set_process_internal(false);
mouse_enabled = false;
}
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
if (project_manager) {
project_manager->set_text(TTR("Project Manager", "Job Title"));
}
} break;
case NOTIFICATION_WM_GO_BACK_REQUEST: {
hide();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
const Vector2 pos = content->get_position();
if (pos.y < -content->get_size().y - 30) {
hide();
break;
}
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
hide();
break;
}
bool lmb = Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT);
if (!mouse_enabled && !lmb) {
// Makes sure that the initial double click does not speed up text.
mouse_enabled = true;
}
if ((mouse_enabled && lmb) || Input::get_singleton()->is_action_pressed(SNAME("ui_accept"))) {
content->set_position(Vector2(pos.x, pos.y - 2000 * get_process_delta_time()));
} else {
content->set_position(Vector2(pos.x, pos.y - 100 * get_process_delta_time()));
}
} break;
}
}
void CreditsRoll::roll_credits() {
if (!project_manager) {
font_size_normal = get_theme_font_size("main_size", EditorStringName(EditorFonts)) * 2;
font_size_header = font_size_normal + 10 * EDSCALE;
font_size_big_header = font_size_header + 20 * EDSCALE;
bold_font = get_theme_font("bold", EditorStringName(EditorFonts));
{
const Ref<Texture2D> logo_texture = get_editor_theme_icon("Logo");
TextureRect *logo = memnew(TextureRect);
logo->set_custom_minimum_size(Vector2(0, logo_texture->get_height() * 3));
logo->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
logo->set_texture(logo_texture);
content->add_child(logo);
}
_create_label(TTRC("Credits"), LabelSize::BIG_HEADER);
_create_nothing();
_create_label(TTRC("Project Founders"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_FOUNDERS));
_create_nothing();
_create_label(TTRC("Lead Developer"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_LEAD_DEVELOPERS));
_create_nothing();
project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER);
project_manager->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_create_label(_build_string(AUTHORS_PROJECT_MANAGERS));
_create_nothing();
_create_label(TTRC("Developers"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_DEVELOPERS));
_create_nothing();
_create_label(TTRC("Patrons"), LabelSize::HEADER);
_create_label(_build_string(DONORS_PATRONS));
_create_nothing();
_create_label(TTRC("Platinum Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_PLATINUM));
_create_nothing();
_create_label(TTRC("Gold Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_GOLD));
_create_nothing();
_create_label(TTRC("Silver Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_SILVER));
_create_nothing();
_create_label(TTRC("Diamond Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_DIAMOND));
_create_nothing();
_create_label(TTRC("Titanium Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_TITANIUM));
_create_nothing();
_create_label(TTRC("Platinum Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_PLATINUM));
_create_nothing();
_create_label(TTRC("Gold Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_GOLD));
_create_nothing();
_create_label(String::utf8(GODOT_LICENSE_TEXT));
_create_nothing(400 * EDSCALE);
_create_label(TTRC("Thank you for choosing Godot Engine!"), LabelSize::BIG_HEADER);
}
Window *root = get_tree()->get_root();
content->set_position(Vector2(content->get_position().x, root->get_size().y + 30));
set_position(root->get_position());
set_size(root->get_size());
popup();
set_process_internal(true);
}
CreditsRoll::CreditsRoll() {
set_wrap_controls(false);
{
ColorRect *background = memnew(ColorRect);
background->set_color(Color(0, 0, 0, 1));
background->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
add_child(background);
}
content = memnew(VBoxContainer);
content->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
add_child(content);
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_event_search_bar.h */
/* credits_roll.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,35 +30,39 @@
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/popup.h"
class Button;
class EventListenerLineEdit;
class LineEdit;
class Label;
class VBoxContainer;
class Font;
class EditorEventSearchBar : public HBoxContainer {
GDCLASS(EditorEventSearchBar, HBoxContainer);
class CreditsRoll : public Popup {
GDCLASS(CreditsRoll, Popup);
LineEdit *search_by_name = nullptr;
EventListenerLineEdit *search_by_event = nullptr;
Button *clear_all = nullptr;
enum class LabelSize {
NORMAL,
HEADER,
BIG_HEADER,
};
void _on_event_changed(const Ref<InputEvent> &p_event);
void _on_clear_all();
int font_size_normal = 0;
int font_size_header = 0;
int font_size_big_header = 0;
Ref<Font> bold_font;
void _value_changed();
bool mouse_enabled = false;
VBoxContainer *content = nullptr;
Label *project_manager = nullptr;
Label *_create_label(const String &p_with_text, LabelSize p_size = LabelSize::NORMAL);
void _create_nothing(int p_size = -1);
String _build_string(const char *const *p_from) const;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
LineEdit *get_name_search_box() const { return search_by_name; }
void roll_credits();
bool is_searching() const;
String get_name() const;
Ref<InputEvent> get_event() const;
EditorEventSearchBar();
CreditsRoll();
};

View file

@ -0,0 +1,185 @@
/**************************************************************************/
/* directory_create_dialog.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "directory_create_dialog.h"
#include "core/io/dir_access.h"
#include "editor/editor_node.h"
#include "editor/gui/editor_validation_panel.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
String DirectoryCreateDialog::_sanitize_input(const String &p_path) const {
String path = p_path.strip_edges();
if (mode == MODE_DIRECTORY) {
path = path.trim_suffix("/");
}
return path;
}
String DirectoryCreateDialog::_validate_path(const String &p_path) const {
if (p_path.is_empty()) {
return TTR("Name cannot be empty.");
}
if (mode == MODE_FILE && p_path.ends_with("/")) {
return TTR("File name can't end with /.");
}
const PackedStringArray splits = p_path.split("/");
for (int i = 0; i < splits.size(); i++) {
const String &part = splits[i];
bool is_file = mode == MODE_FILE && i == splits.size() - 1;
if (part.is_empty()) {
if (is_file) {
return TTR("File name cannot be empty.");
} else {
return TTR("Folder name cannot be empty.");
}
}
if (part.contains_char('\\') || part.contains_char(':') || part.contains_char('*') ||
part.contains_char('|') || part.contains_char('>') || part.ends_with(".") || part.ends_with(" ")) {
if (is_file) {
return TTR("File name contains invalid characters.");
} else {
return TTR("Folder name contains invalid characters.");
}
}
if (part[0] == '.') {
if (is_file) {
return TTR("File name begins with a dot.");
} else {
return TTR("Folder name begins with a dot.");
}
}
}
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
da->change_dir(base_dir);
if (da->file_exists(p_path)) {
return TTR("File with that name already exists.");
}
if (da->dir_exists(p_path)) {
return TTR("Folder with that name already exists.");
}
return String();
}
void DirectoryCreateDialog::_on_dir_path_changed() {
const String path = _sanitize_input(dir_path->get_text());
const String error = _validate_path(path);
if (error.is_empty()) {
if (path.contains_char('/')) {
if (mode == MODE_DIRECTORY) {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
} else {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in path will create the file in subfolder, creating new subfolders if necessary."), EditorValidationPanel::MSG_OK);
}
} else if (mode == MODE_FILE) {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("File name is valid."), EditorValidationPanel::MSG_OK);
}
} else {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR);
}
}
void DirectoryCreateDialog::ok_pressed() {
const String path = _sanitize_input(dir_path->get_text());
// The OK button should be disabled if the path is invalid, but just in case.
const String error = _validate_path(path);
ERR_FAIL_COND_MSG(!error.is_empty(), error);
accept_callback.call(base_dir.path_join(path));
hide();
}
void DirectoryCreateDialog::_post_popup() {
ConfirmationDialog::_post_popup();
dir_path->grab_focus();
}
void DirectoryCreateDialog::config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name) {
set_title(p_title);
base_dir = p_base_dir;
base_path_label->set_text(vformat(TTR("Base path: %s"), base_dir));
accept_callback = p_accept_callback;
mode = p_mode;
dir_path->set_text(p_default_name);
validation_panel->update();
if (p_mode == MODE_FILE) {
int extension_pos = p_default_name.rfind_char('.');
if (extension_pos > -1) {
dir_path->select(0, extension_pos);
return;
}
}
dir_path->select_all();
}
DirectoryCreateDialog::DirectoryCreateDialog() {
set_min_size(Size2i(480, 0) * EDSCALE);
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
base_path_label = memnew(Label);
base_path_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
base_path_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS);
vb->add_child(base_path_label);
Label *name_label = memnew(Label);
name_label->set_text(TTR("Name:"));
name_label->set_theme_type_variation("HeaderSmall");
vb->add_child(name_label);
dir_path = memnew(LineEdit);
dir_path->set_accessibility_name(TTRC("Name:"));
vb->add_child(dir_path);
register_text_enter(dir_path);
Control *spacing = memnew(Control);
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
vb->add_child(spacing);
validation_panel = memnew(EditorValidationPanel);
vb->add_child(validation_panel);
validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Folder name is valid."));
validation_panel->set_update_callback(callable_mp(this, &DirectoryCreateDialog::_on_dir_path_changed));
validation_panel->set_accept_button(get_ok_button());
dir_path->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_translation_preview_button.h */
/* directory_create_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,18 +30,40 @@
#pragma once
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
class EditorTranslationPreviewButton : public Button {
GDCLASS(EditorTranslationPreviewButton, Button);
class EditorValidationPanel;
class Label;
class LineEdit;
void _update();
protected:
virtual void pressed() override;
void _notification(int p_what);
class DirectoryCreateDialog : public ConfirmationDialog {
GDCLASS(DirectoryCreateDialog, ConfirmationDialog);
public:
EditorTranslationPreviewButton();
enum Mode {
MODE_FILE,
MODE_DIRECTORY,
};
private:
String base_dir;
Callable accept_callback;
int mode = MODE_FILE;
Label *base_path_label = nullptr;
LineEdit *dir_path = nullptr;
EditorValidationPanel *validation_panel = nullptr;
String _sanitize_input(const String &p_input) const;
String _validate_path(const String &p_path) const;
void _on_dir_path_changed();
protected:
virtual void ok_pressed() override;
virtual void _post_popup() override;
public:
void config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name = "");
DirectoryCreateDialog();
};

365
editor/gui/editor_about.cpp Normal file
View file

@ -0,0 +1,365 @@
/**************************************************************************/
/* editor_about.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_about.h"
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/license.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/credits_roll.h"
#include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/item_list.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/split_container.h"
#include "scene/gui/tab_container.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
#include "scene/resources/style_box.h"
void EditorAbout::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
_about_text_label->set_text(
String(U"© 2014-present ") + TTR("Godot Engine contributors") + ".\n" +
String(U"© 2007-2014 Juan Linietsky, Ariel Manzur.\n"));
_project_manager_label->set_text(TTR("Project Manager", "Job Title"));
for (ItemList *il : name_lists) {
for (int i = 0; i < il->get_item_count(); i++) {
const Variant val = il->get_item_metadata(i);
if (val.get_type() == Variant::STRING) {
il->set_item_tooltip(i, val.operator String() + "\n\n" + TTR("Double-click to open in browser."));
}
}
}
} break;
case NOTIFICATION_THEME_CHANGED: {
const Ref<Font> font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts));
const int font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts));
_tpl_text->begin_bulk_theme_override();
_tpl_text->add_theme_font_override("normal_font", font);
_tpl_text->add_theme_font_size_override("normal_font_size", font_size);
_tpl_text->add_theme_constant_override(SceneStringName(line_separation), 4 * EDSCALE);
_tpl_text->end_bulk_theme_override();
license_text_label->begin_bulk_theme_override();
license_text_label->add_theme_font_override("normal_font", font);
license_text_label->add_theme_font_size_override("normal_font_size", font_size);
license_text_label->add_theme_constant_override(SceneStringName(line_separation), 4 * EDSCALE);
license_text_label->end_bulk_theme_override();
_logo->set_texture(get_editor_theme_icon(SNAME("Logo")));
for (ItemList *il : name_lists) {
for (int i = 0; i < il->get_item_count(); i++) {
if (il->get_item_metadata(i)) {
il->set_item_icon(i, get_theme_icon(SNAME("ExternalLink"), EditorStringName(EditorIcons)));
il->set_item_icon_modulate(i, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
}
}
}
} break;
}
}
void EditorAbout::_license_tree_selected() {
TreeItem *selected = _tpl_tree->get_selected();
_tpl_text->scroll_to_line(0);
_tpl_text->set_text(selected->get_metadata(0));
}
void EditorAbout::_item_activated(int p_idx, ItemList *p_il) {
const Variant val = p_il->get_item_metadata(p_idx);
if (val.get_type() == Variant::STRING) {
OS::get_singleton()->shell_open(val);
} else {
// Easter egg :D
if (!EditorNode::get_singleton()) {
// Don't allow in Project Manager.
return;
}
if (!credits_roll) {
credits_roll = memnew(CreditsRoll);
add_child(credits_roll);
}
credits_roll->roll_credits();
}
}
void EditorAbout::_item_list_resized(ItemList *p_il) {
p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?.
}
Label *EditorAbout::_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField<SectionFlags> p_flags) {
Label *lbl = memnew(Label(p_name));
lbl->set_theme_type_variation("HeaderSmall");
lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
p_parent->add_child(lbl);
ItemList *il = memnew(ItemList);
il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
il->set_h_size_flags(Control::SIZE_EXPAND_FILL);
il->set_same_column_width(true);
il->set_auto_height(true);
il->set_max_columns(p_flags.has_flag(FLAG_SINGLE_COLUMN) ? 1 : 16);
il->add_theme_constant_override("h_separation", 16 * EDSCALE);
if (p_flags.has_flag(FLAG_ALLOW_WEBSITE) || (p_flags.has_flag(FLAG_EASTER_EGG) && EditorNode::get_singleton())) {
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
il->add_theme_style_override("focus", empty_stylebox);
il->add_theme_style_override("selected", empty_stylebox);
il->connect("item_activated", callable_mp(this, &EditorAbout::_item_activated).bind(il));
} else {
il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
il->set_focus_mode(Control::FOCUS_NONE);
}
const char *const *names_ptr = p_src;
if (p_flags.has_flag(FLAG_ALLOW_WEBSITE)) {
il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il));
il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all));
while (*names_ptr) {
const String name = String::utf8(*names_ptr++);
const String identifier = name.get_slicec('<', 0);
const String website = name.get_slice_count("<") == 1 ? "" : name.get_slicec('<', 1).trim_suffix(">");
il->add_item(identifier, nullptr, !website.is_empty());
if (website.is_empty()) {
il->set_item_tooltip_enabled(-1, false);
} else {
il->set_item_metadata(-1, website);
}
if (!*names_ptr && name.contains(" anonymous ")) {
il->set_item_disabled(-1, true);
}
}
} else {
while (*names_ptr) {
il->add_item(String::utf8(*names_ptr++), nullptr, false);
il->set_item_tooltip_enabled(-1, false);
}
}
name_lists.append(il);
p_parent->add_child(il);
HSeparator *hs = memnew(HSeparator);
hs->set_modulate(Color(0, 0, 0, 0));
p_parent->add_child(hs);
return lbl;
}
EditorAbout::EditorAbout() {
set_title(TTRC("Thanks from the Godot community!"));
set_hide_on_ok(true);
VBoxContainer *vbc = memnew(VBoxContainer);
add_child(vbc);
HBoxContainer *hbc = memnew(HBoxContainer);
hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hbc->set_alignment(BoxContainer::ALIGNMENT_CENTER);
hbc->add_theme_constant_override("separation", 30 * EDSCALE);
vbc->add_child(hbc);
_logo = memnew(TextureRect);
_logo->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
hbc->add_child(_logo);
VBoxContainer *version_info_vbc = memnew(VBoxContainer);
// Add a dummy control node for spacing.
Control *v_spacer = memnew(Control);
version_info_vbc->add_child(v_spacer);
version_info_vbc->add_child(memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD)));
_about_text_label = memnew(Label);
_about_text_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
_about_text_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
_about_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
version_info_vbc->add_child(_about_text_label);
hbc->add_child(version_info_vbc);
TabContainer *tc = memnew(TabContainer);
tc->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
tc->set_custom_minimum_size(Size2(400, 200) * EDSCALE);
tc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tc->set_theme_type_variation("TabContainerOdd");
vbc->add_child(tc);
{
ScrollContainer *sc = memnew(ScrollContainer);
sc->set_name(TTRC("Authors"));
sc->set_v_size_flags(Control::SIZE_EXPAND);
tc->add_child(sc);
VBoxContainer *vb = memnew(VBoxContainer);
vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
sc->add_child(vb);
_create_section(vb, TTRC("Project Founders"), AUTHORS_FOUNDERS, FLAG_SINGLE_COLUMN);
_create_section(vb, TTRC("Lead Developer"), AUTHORS_LEAD_DEVELOPERS);
// The section title will be updated in NOTIFICATION_TRANSLATION_CHANGED.
_project_manager_label = _create_section(vb, "", AUTHORS_PROJECT_MANAGERS, FLAG_EASTER_EGG);
_project_manager_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_create_section(vb, TTRC("Developers"), AUTHORS_DEVELOPERS);
}
{
ScrollContainer *sc = memnew(ScrollContainer);
sc->set_name(TTRC("Donors"));
sc->set_v_size_flags(Control::SIZE_EXPAND);
tc->add_child(sc);
VBoxContainer *vb = memnew(VBoxContainer);
vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
sc->add_child(vb);
_create_section(vb, TTRC("Patrons"), DONORS_PATRONS, FLAG_ALLOW_WEBSITE | FLAG_SINGLE_COLUMN);
_create_section(vb, TTRC("Platinum Sponsors"), DONORS_SPONSORS_PLATINUM, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Gold Sponsors"), DONORS_SPONSORS_GOLD, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Silver Sponsors"), DONORS_SPONSORS_SILVER, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Diamond Members"), DONORS_MEMBERS_DIAMOND, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Titanium Members"), DONORS_MEMBERS_TITANIUM, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Platinum Members"), DONORS_MEMBERS_PLATINUM, FLAG_ALLOW_WEBSITE);
_create_section(vb, TTRC("Gold Members"), DONORS_MEMBERS_GOLD, FLAG_ALLOW_WEBSITE);
}
// License.
license_text_label = memnew(RichTextLabel);
license_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
license_text_label->set_threaded(true);
license_text_label->set_name(TTRC("License"));
license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT));
tc->add_child(license_text_label);
// Thirdparty License.
VBoxContainer *license_thirdparty = memnew(VBoxContainer);
license_thirdparty->set_name(TTRC("Third-party Licenses"));
license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tc->add_child(license_thirdparty);
Label *tpl_label = memnew(Label(TTRC("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms.")));
tpl_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tpl_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
tpl_label->set_size(Size2(630, 1) * EDSCALE);
license_thirdparty->add_child(tpl_label);
HSplitContainer *tpl_hbc = memnew(HSplitContainer);
tpl_hbc->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tpl_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tpl_hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tpl_hbc->set_split_offset(240 * EDSCALE);
license_thirdparty->add_child(tpl_hbc);
_tpl_tree = memnew(Tree);
_tpl_tree->set_hide_root(true);
TreeItem *root = _tpl_tree->create_item();
TreeItem *tpl_ti_all = _tpl_tree->create_item(root);
tpl_ti_all->set_text(0, TTRC("All Components"));
tpl_ti_all->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
TreeItem *tpl_ti_tp = _tpl_tree->create_item(root);
tpl_ti_tp->set_text(0, TTRC("Components"));
tpl_ti_tp->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
tpl_ti_tp->set_selectable(0, false);
TreeItem *tpl_ti_lc = _tpl_tree->create_item(root);
tpl_ti_lc->set_text(0, TTRC("Licenses"));
tpl_ti_lc->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
tpl_ti_lc->set_selectable(0, false);
String long_text = "";
for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {
const ComponentCopyright &component = COPYRIGHT_INFO[component_index];
TreeItem *ti = _tpl_tree->create_item(tpl_ti_tp);
String component_name = String::utf8(component.name);
ti->set_text(0, component_name);
String text = component_name + "\n";
long_text += "- " + component_name + "\n";
for (int part_index = 0; part_index < component.part_count; part_index++) {
const ComponentCopyrightPart &part = component.parts[part_index];
text += "\n Files:";
for (int file_num = 0; file_num < part.file_count; file_num++) {
text += "\n " + String::utf8(part.files[file_num]);
}
String copyright;
for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) {
copyright += String::utf8("\n \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]);
}
text += copyright;
long_text += copyright;
String license = "\n License: " + String::utf8(part.license) + "\n";
text += license;
long_text += license + "\n";
}
ti->set_metadata(0, text);
}
for (int i = 0; i < LICENSE_COUNT; i++) {
TreeItem *ti = _tpl_tree->create_item(tpl_ti_lc);
String licensename = String::utf8(LICENSE_NAMES[i]);
ti->set_text(0, licensename);
long_text += "- " + licensename + "\n\n";
String licensebody = String::utf8(LICENSE_BODIES[i]);
ti->set_metadata(0, licensebody);
long_text += " " + licensebody.replace("\n", "\n ") + "\n\n";
}
tpl_ti_all->set_metadata(0, long_text);
tpl_hbc->add_child(_tpl_tree);
_tpl_text = memnew(RichTextLabel);
_tpl_text->set_threaded(true);
_tpl_text->set_h_size_flags(Control::SIZE_EXPAND_FILL);
_tpl_text->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tpl_hbc->add_child(_tpl_text);
_tpl_tree->connect(SceneStringName(item_selected), callable_mp(this, &EditorAbout::_license_tree_selected));
tpl_ti_all->select(0);
_tpl_text->set_text(tpl_ti_all->get_metadata(0));
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_translation_preview_menu.h */
/* editor_about.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,17 +30,46 @@
#pragma once
#include "scene/gui/popup_menu.h"
#include "scene/gui/dialogs.h"
class EditorTranslationPreviewMenu : public PopupMenu {
GDCLASS(EditorTranslationPreviewMenu, PopupMenu);
class CreditsRoll;
class ItemList;
class Label;
class RichTextLabel;
class TextureRect;
class Tree;
void _prepare();
void _pressed(int p_index);
/**
* NOTE: Do not assume the EditorNode singleton to be available in this class' methods.
* EditorAbout is also used from the project manager where EditorNode isn't initialized.
*/
class EditorAbout : public AcceptDialog {
GDCLASS(EditorAbout, AcceptDialog);
private:
enum SectionFlags {
FLAG_SINGLE_COLUMN = 1 << 0,
FLAG_ALLOW_WEBSITE = 1 << 1,
FLAG_EASTER_EGG = 1 << 2,
};
void _license_tree_selected();
void _item_activated(int p_idx, ItemList *p_il);
void _item_list_resized(ItemList *p_il);
Label *_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField<SectionFlags> p_flags = 0);
Label *_about_text_label = nullptr;
Label *_project_manager_label = nullptr;
Tree *_tpl_tree = nullptr;
RichTextLabel *license_text_label = nullptr;
RichTextLabel *_tpl_text = nullptr;
TextureRect *_logo = nullptr;
Vector<ItemList *> name_lists;
CreditsRoll *credits_roll = nullptr;
protected:
void _notification(int p_what);
public:
EditorTranslationPreviewMenu();
EditorAbout();
};

View file

@ -31,11 +31,11 @@
#include "editor_bottom_panel.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_toaster.h"
#include "editor/gui/editor_version_button.h"
#include "editor/settings/editor_command_palette.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"

View file

@ -30,9 +30,9 @@
#include "editor_dir_dialog.h"
#include "editor/directory_create_dialog.h"
#include "editor/editor_file_system.h"
#include "editor/filesystem_dock.h"
#include "editor/docks/filesystem_dock.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/gui/directory_create_dialog.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/gui/box_container.h"
#include "scene/gui/tree.h"

View file

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

View file

@ -33,12 +33,12 @@
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/dependency_editor.h"
#include "editor/editor_file_system.h"
#include "editor/docks/filesystem_dock.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/filesystem_dock.h"
#include "editor/file_system/dependency_editor.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/inspector/editor_resource_preview.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/center_container.h"
#include "scene/gui/check_box.h"

View file

@ -31,7 +31,7 @@
#pragma once
#include "core/io/dir_access.h"
#include "editor/file_info.h"
#include "editor/file_system/file_info.h"
#include "scene/gui/dialogs.h"
#include "scene/property_list_helper.h"

View file

@ -32,13 +32,13 @@
#include "core/config/project_settings.h"
#include "core/string/fuzzy_search.h"
#include "editor/editor_file_system.h"
#include "editor/docks/filesystem_dock.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/filesystem_dock.h"
#include "editor/file_system/editor_file_system.h"
#include "editor/file_system/editor_paths.h"
#include "editor/inspector/editor_resource_preview.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/center_container.h"
#include "scene/gui/check_button.h"

View file

@ -1,678 +0,0 @@
/**************************************************************************/
/* editor_run_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_run_bar.h"
#include "core/config/project_settings.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_run_native.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/gui/editor_toaster.h"
#include "editor/project_settings_editor.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel_container.h"
#ifndef XR_DISABLED
#include "servers/xr_server.h"
#endif // XR_DISABLED
EditorRunBar *EditorRunBar::singleton = nullptr;
void EditorRunBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
_reset_play_buttons();
} break;
case NOTIFICATION_READY: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_show_dialog();
}
} break;
case NOTIFICATION_THEME_CHANGED: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadRecoveryMode"), EditorStringName(EditorStyles)));
recovery_mode_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
recovery_mode_button->add_theme_style_override("hover", get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
recovery_mode_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
recovery_mode_reload_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
recovery_mode_button->begin_bulk_theme_override();
recovery_mode_button->add_theme_color_override("icon_normal_color", Color(0.3, 0.3, 0.3, 1));
recovery_mode_button->add_theme_color_override("icon_pressed_color", Color(0.4, 0.4, 0.4, 1));
recovery_mode_button->add_theme_color_override("icon_hover_color", Color(0.6, 0.6, 0.6, 1));
Color dark_color = get_theme_color("recovery_mode_text_color", EditorStringName(Editor));
recovery_mode_button->add_theme_color_override(SceneStringName(font_color), dark_color);
recovery_mode_button->add_theme_color_override("font_pressed_color", dark_color.lightened(0.2));
recovery_mode_button->add_theme_color_override("font_hover_color", dark_color.lightened(0.4));
recovery_mode_button->add_theme_color_override("font_hover_pressed_color", dark_color.lightened(0.2));
recovery_mode_button->end_bulk_theme_override();
return;
}
_update_play_buttons();
profiler_autostart_indicator->set_button_icon(get_editor_theme_icon(SNAME("ProfilerAutostartWarning")));
pause_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
stop_button->set_button_icon(get_editor_theme_icon(SNAME("Stop")));
if (is_movie_maker_enabled()) {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
write_movie_button->set_theme_type_variation("RunBarButtonMovieMakerEnabled");
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
} else {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
write_movie_button->set_theme_type_variation("RunBarButtonMovieMakerDisabled");
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
}
write_movie_button->set_button_icon(get_editor_theme_icon(SNAME("MainMovieWrite")));
} break;
}
}
void EditorRunBar::_reset_play_buttons() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
play_button->set_pressed(false);
play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
play_button->set_tooltip_text(TTRC("Play the project."));
play_scene_button->set_pressed(false);
play_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayScene")));
play_scene_button->set_tooltip_text(TTRC("Play the edited scene."));
play_custom_scene_button->set_pressed(false);
play_custom_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayCustom")));
play_custom_scene_button->set_tooltip_text(TTRC("Play a custom scene."));
}
void EditorRunBar::_update_play_buttons() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
_reset_play_buttons();
if (!is_playing()) {
return;
}
Button *active_button = nullptr;
if (current_mode == RUN_CURRENT) {
active_button = play_scene_button;
} else if (current_mode == RUN_CUSTOM) {
active_button = play_custom_scene_button;
} else {
active_button = play_button;
}
if (active_button) {
active_button->set_pressed(true);
active_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
active_button->set_tooltip_text(TTRC("Reload the played scene."));
}
}
void EditorRunBar::_movie_maker_item_pressed(int p_id) {
switch (p_id) {
case MOVIE_MAKER_TOGGLE: {
bool new_enabled = !is_movie_maker_enabled();
set_movie_maker_enabled(new_enabled);
write_movie_button->get_popup()->set_item_checked(0, new_enabled);
write_movie_button->set_pressed(new_enabled);
_write_movie_toggled(new_enabled);
break;
}
case MOVIE_MAKER_OPEN_SETTINGS:
ProjectSettingsEditor::get_singleton()->popup_project_settings(true);
ProjectSettingsEditor::get_singleton()->set_general_page("editor/movie_writer");
break;
}
}
void EditorRunBar::_write_movie_toggled(bool p_enabled) {
if (p_enabled) {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
}
}
Vector<String> EditorRunBar::_get_xr_mode_play_args(int p_xr_mode_id) {
Vector<String> play_args;
if (p_xr_mode_id == 0) {
// Play in regular mode, xr mode off.
play_args.push_back("--xr-mode");
play_args.push_back("off");
} else if (p_xr_mode_id == 1) {
// Play in xr mode.
play_args.push_back("--xr-mode");
play_args.push_back("on");
}
return play_args;
}
void EditorRunBar::_quick_run_selected(const String &p_file_path, int p_id) {
play_custom_scene(p_file_path, _get_xr_mode_play_args(p_id));
}
void EditorRunBar::_play_custom_pressed(int p_id) {
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
stop_playing();
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected).bind(p_id));
play_custom_scene_button->set_pressed(false);
} else {
Vector<String> play_args = _get_xr_mode_play_args(p_id);
// Reload if already running a custom scene.
String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
play_custom_scene(last_custom_scene, play_args);
}
}
void EditorRunBar::_play_current_pressed(int p_id) {
Vector<String> play_args = _get_xr_mode_play_args(p_id);
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
play_current_scene(false, play_args);
} else {
// Reload if already running the current scene.
play_current_scene(true, play_args);
}
}
void EditorRunBar::_run_scene(const String &p_scene_path, const Vector<String> &p_run_args) {
ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path.");
if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
return;
}
if (!EditorNode::get_singleton()->validate_custom_directory()) {
return;
}
_reset_play_buttons();
String write_movie_file;
if (is_movie_maker_enabled()) {
if (current_mode == RUN_CURRENT) {
Node *scene_root = nullptr;
if (p_scene_path.is_empty()) {
scene_root = get_tree()->get_edited_scene_root();
} else {
int scene_index = EditorNode::get_editor_data().get_edited_scene_from_path(p_scene_path);
if (scene_index >= 0) {
scene_root = EditorNode::get_editor_data().get_edited_scene_root(scene_index);
}
}
if (scene_root && scene_root->has_meta("movie_file")) {
// If the scene file has a movie_file metadata set, use this as file.
// Quick workaround if you want to have multiple scenes that write to
// multiple movies.
write_movie_file = scene_root->get_meta("movie_file");
}
}
if (write_movie_file.is_empty()) {
write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
}
if (write_movie_file.is_empty()) {
// TODO: Provide options to directly resolve the issue with a custom dialog.
EditorNode::get_singleton()->show_accept(TTR("Movie Maker mode is enabled, but no movie file path has been specified.\nA default movie file path can be specified in the project settings under the Editor > Movie Writer category.\nAlternatively, for running single scenes, a `movie_file` string metadata can be added to the root node,\nspecifying the path to a movie file that will be used when recording that scene."), TTR("OK"));
return;
}
}
String run_filename;
switch (current_mode) {
case RUN_CUSTOM: {
run_filename = ResourceUID::ensure_path(p_scene_path);
run_custom_filename = run_filename;
} break;
case RUN_CURRENT: {
if (!p_scene_path.is_empty()) {
run_filename = p_scene_path;
run_current_filename = run_filename;
break;
}
Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
EditorNode::get_singleton()->show_accept(TTR("There is no defined scene to run."), TTR("OK"));
return;
}
if (scene_root->get_scene_file_path().is_empty()) {
EditorNode::get_singleton()->save_before_run();
return;
}
run_filename = scene_root->get_scene_file_path();
run_current_filename = run_filename;
} break;
default: {
if (!EditorNode::get_singleton()->ensure_main_scene(false)) {
return;
}
run_filename = GLOBAL_GET("application/run/main_scene");
} break;
}
EditorNode::get_singleton()->try_autosave();
if (!EditorNode::get_singleton()->call_build()) {
return;
}
EditorDebuggerNode::get_singleton()->start();
Error error = editor_run.run(run_filename, write_movie_file, p_run_args);
if (error != OK) {
EditorDebuggerNode::get_singleton()->stop();
EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
return;
}
_update_play_buttons();
stop_button->set_disabled(false);
emit_signal(SNAME("play_pressed"));
}
void EditorRunBar::_run_native(const Ref<EditorExportPreset> &p_preset) {
EditorNode::get_singleton()->try_autosave();
if (run_native->is_deploy_debug_remote_enabled()) {
stop_playing();
if (!EditorNode::get_singleton()->call_build()) {
return; // Build failed.
}
EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
emit_signal(SNAME("play_pressed"));
editor_run.run_native_notify();
}
}
void EditorRunBar::_profiler_autostart_indicator_pressed() {
// Switch to the first profiler tab in the bottom panel.
EditorNode::get_singleton()->get_bottom_panel()->make_item_visible(EditorDebuggerNode::get_singleton(), true);
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false)) {
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(3);
} else if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false)) {
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(4);
} else {
// Switch to the network profiler tab.
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(8);
}
}
void EditorRunBar::recovery_mode_show_dialog() {
recovery_mode_popup->popup_centered();
}
void EditorRunBar::recovery_mode_reload_project() {
EditorNode::get_singleton()->trigger_menu_option(EditorNode::PROJECT_RELOAD_CURRENT_PROJECT, false);
}
void EditorRunBar::play_main_scene(bool p_from_native) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
if (p_from_native) {
run_native->resume_run_native();
} else {
stop_playing();
current_mode = RunMode::RUN_MAIN;
_run_scene();
}
}
void EditorRunBar::play_current_scene(bool p_reload, const Vector<String> &p_play_args) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
EditorNode::get_singleton()->save_default_environment();
stop_playing();
current_mode = RunMode::RUN_CURRENT;
if (p_reload) {
_run_scene(last_current_scene, p_play_args);
} else {
_run_scene("", p_play_args);
}
}
void EditorRunBar::play_custom_scene(const String &p_custom, const Vector<String> &p_play_args) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
stop_playing();
current_mode = RunMode::RUN_CUSTOM;
_run_scene(p_custom, p_play_args);
}
void EditorRunBar::stop_playing() {
if (editor_run.get_status() == EditorRun::STATUS_STOP) {
return;
}
current_mode = RunMode::STOPPED;
editor_run.stop();
EditorDebuggerNode::get_singleton()->stop();
run_custom_filename.clear();
run_current_filename.clear();
stop_button->set_pressed(false);
stop_button->set_disabled(true);
_reset_play_buttons();
emit_signal(SNAME("stop_pressed"));
}
bool EditorRunBar::is_playing() const {
EditorRun::Status status = editor_run.get_status();
return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
}
String EditorRunBar::get_playing_scene() const {
String run_filename = editor_run.get_running_scene();
if (run_filename.is_empty() && is_playing()) {
run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
}
return run_filename;
}
Error EditorRunBar::start_native_device(int p_device_id) {
return run_native->start_run_native(p_device_id);
}
OS::ProcessID EditorRunBar::has_child_process(OS::ProcessID p_pid) const {
return editor_run.has_child_process(p_pid);
}
void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
if (!has_child_process(p_pid)) {
return;
}
editor_run.stop_child_process(p_pid);
if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
stop_playing();
}
}
OS::ProcessID EditorRunBar::get_current_process() const {
return editor_run.get_current_process();
}
void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
movie_maker_enabled = p_enabled;
write_movie_button->get_popup()->set_item_checked(0, p_enabled);
}
bool EditorRunBar::is_movie_maker_enabled() const {
return movie_maker_enabled;
}
void EditorRunBar::update_profiler_autostart_indicator() {
bool profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false);
bool visual_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false);
bool network_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false);
bool any_profiler_active = profiler_active | visual_profiler_active | network_profiler_active;
any_profiler_active &= !Engine::get_singleton()->is_recovery_mode_hint();
profiler_autostart_indicator->set_visible(any_profiler_active);
if (any_profiler_active) {
String tooltip = TTR("Autostart is enabled for the following profilers, which can have a performance impact:");
if (profiler_active) {
tooltip += "\n- " + TTR("Profiler");
}
if (visual_profiler_active) {
tooltip += "\n- " + TTR("Visual Profiler");
}
if (network_profiler_active) {
tooltip += "\n- " + TTR("Network Profiler");
}
tooltip += "\n\n" + TTR("Click to open the first profiler for which autostart is enabled.");
profiler_autostart_indicator->set_tooltip_text(tooltip);
}
}
HBoxContainer *EditorRunBar::get_buttons_container() {
return main_hbox;
}
void EditorRunBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("play_pressed"));
ADD_SIGNAL(MethodInfo("stop_pressed"));
}
EditorRunBar::EditorRunBar() {
singleton = this;
outer_hbox = memnew(HBoxContainer);
add_child(outer_hbox);
// Use a button for the indicator since it comes with a background panel and pixel perfect centering of an icon.
profiler_autostart_indicator = memnew(Button);
profiler_autostart_indicator->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
profiler_autostart_indicator->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
profiler_autostart_indicator->set_focus_mode(FOCUS_ACCESSIBILITY);
profiler_autostart_indicator->set_theme_type_variation("ProfilerAutostartIndicator");
profiler_autostart_indicator->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_profiler_autostart_indicator_pressed));
outer_hbox->add_child(profiler_autostart_indicator);
update_profiler_autostart_indicator();
main_panel = memnew(PanelContainer);
outer_hbox->add_child(main_panel);
main_hbox = memnew(HBoxContainer);
main_panel->add_child(main_hbox);
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_popup = memnew(AcceptDialog);
recovery_mode_popup->set_min_size(Size2(550, 70) * EDSCALE);
recovery_mode_popup->set_title(TTR("Recovery Mode"));
recovery_mode_popup->set_text(
TTR("Godot opened the project in Recovery Mode, which is a special mode that can help recover projects that crash the engine upon initialization. The following features have been temporarily disabled:") +
String::utf8("\n\n") + TTR("Tool scripts") +
String::utf8("\n") + TTR("Editor plugins") +
String::utf8("\n") + TTR("GDExtension addons") +
String::utf8("\n") + TTR("Automatic scene restoring") +
String::utf8("\n\n") + TTR("If the project cannot be opened outside of this mode, then it's very likely any of these components is preventing this project from launching. This mode is intended only for basic editing to troubleshoot such issues, and therefore it is not possible to run a project in this mode.") +
String::utf8("\n\n") + TTR("To disable Recovery Mode, reload the project by pressing the Reload button next to the Recovery Mode banner, or by reopening the project normally."));
recovery_mode_popup->set_autowrap(true);
add_child(recovery_mode_popup);
recovery_mode_reload_button = memnew(Button);
main_hbox->add_child(recovery_mode_reload_button);
recovery_mode_reload_button->set_theme_type_variation("RunBarButton");
recovery_mode_reload_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
recovery_mode_reload_button->set_tooltip_text(TTR("Disable recovery mode and reload the project."));
recovery_mode_reload_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_reload_project));
recovery_mode_panel = memnew(PanelContainer);
main_hbox->add_child(recovery_mode_panel);
recovery_mode_button = memnew(Button);
recovery_mode_panel->add_child(recovery_mode_button);
recovery_mode_button->set_theme_type_variation("RunBarButton");
recovery_mode_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
recovery_mode_button->set_text(TTR("Recovery Mode"));
recovery_mode_button->set_tooltip_text(TTR("Recovery Mode is enabled. Click for more details."));
recovery_mode_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_show_dialog));
return;
}
play_button = memnew(Button);
main_hbox->add_child(play_button);
play_button->set_theme_type_variation("RunBarButton");
play_button->set_toggle_mode(true);
play_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
play_button->set_tooltip_text(TTRC("Run the project's default scene."));
play_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::play_main_scene).bind(false));
ED_SHORTCUT_AND_COMMAND("editor/run_project", TTRC("Run Project"), Key::F5);
ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
pause_button = memnew(Button);
main_hbox->add_child(pause_button);
pause_button->set_theme_type_variation("RunBarButton");
pause_button->set_toggle_mode(true);
pause_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
pause_button->set_tooltip_text(TTRC("Pause the running project's execution for debugging."));
pause_button->set_disabled(true);
ED_SHORTCUT("editor/pause_running_project", TTRC("Pause Running Project"), Key::F7);
ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
stop_button = memnew(Button);
main_hbox->add_child(stop_button);
stop_button->set_theme_type_variation("RunBarButton");
stop_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
stop_button->set_tooltip_text(TTRC("Stop the currently running project."));
stop_button->set_disabled(true);
stop_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::stop_playing));
ED_SHORTCUT("editor/stop_running_project", TTRC("Stop Running Project"), Key::F8);
ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
run_native = memnew(EditorRunNative);
main_hbox->add_child(run_native);
run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native));
bool add_play_xr_mode_options = false;
#ifndef XR_DISABLED
if (XRServer::get_xr_mode() == XRServer::XRMODE_ON ||
(XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT && GLOBAL_GET("xr/openxr/enabled"))) {
// If OpenXR is enabled, we turn the `play_scene_button` and
// `play_custom_scene_button` into MenuButtons to provide the option to start a scene in
// either regular mode or XR mode.
add_play_xr_mode_options = true;
}
#endif // XR_DISABLED
if (add_play_xr_mode_options) {
MenuButton *menu_button = memnew(MenuButton);
PopupMenu *popup = menu_button->get_popup();
popup->add_item(TTRC("Run Scene in Regular Mode"), 0);
popup->add_item(TTRC("Run Scene in XR Mode"), 1);
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_current_pressed));
play_scene_button = menu_button;
} else {
play_scene_button = memnew(Button);
play_scene_button->set_toggle_mode(true);
play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed).bind(-1));
}
main_hbox->add_child(play_scene_button);
play_scene_button->set_theme_type_variation("RunBarButton");
play_scene_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
play_scene_button->set_tooltip_text(TTRC("Run the currently edited scene."));
ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTRC("Run Current Scene"), Key::F6);
ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
if (add_play_xr_mode_options) {
MenuButton *menu_button = memnew(MenuButton);
PopupMenu *popup = menu_button->get_popup();
popup->add_item(TTRC("Run in Regular Mode"), 0);
popup->add_item(TTRC("Run in XR Mode"), 1);
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed));
play_custom_scene_button = menu_button;
} else {
play_custom_scene_button = memnew(Button);
play_custom_scene_button->set_toggle_mode(true);
play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed).bind(-1));
}
main_hbox->add_child(play_custom_scene_button);
play_custom_scene_button->set_theme_type_variation("RunBarButton");
play_custom_scene_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
play_custom_scene_button->set_tooltip_text(TTRC("Run a specific scene."));
ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTRC("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
write_movie_panel = memnew(PanelContainer);
main_hbox->add_child(write_movie_panel);
write_movie_button = memnew(MenuButton);
PopupMenu *write_movie_popup = write_movie_button->get_popup();
write_movie_popup->add_check_item(TTRC("Enable Movie Maker Mode"), MOVIE_MAKER_TOGGLE);
write_movie_popup->add_item(TTRC("Open Movie Maker Settings..."), MOVIE_MAKER_OPEN_SETTINGS);
write_movie_popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_movie_maker_item_pressed));
write_movie_panel->add_child(write_movie_button);
write_movie_button->set_theme_type_variation("RunBarButtonMovieMakerDisabled");
write_movie_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
write_movie_button->set_tooltip_text(TTRC("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
write_movie_button->set_accessibility_name(TTRC("Enable Movie Maker Mode"));
}

View file

@ -1,140 +0,0 @@
/**************************************************************************/
/* editor_run_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 "editor/editor_run.h"
#include "editor/export/editor_export.h"
#include "scene/gui/margin_container.h"
class Button;
class EditorRunNative;
class MenuButton;
class PanelContainer;
class HBoxContainer;
class AcceptDialog;
class EditorRunBar : public MarginContainer {
GDCLASS(EditorRunBar, MarginContainer);
static EditorRunBar *singleton;
enum RunMode {
STOPPED = 0,
RUN_MAIN,
RUN_CURRENT,
RUN_CUSTOM,
};
PanelContainer *main_panel = nullptr;
HBoxContainer *main_hbox = nullptr;
HBoxContainer *outer_hbox = nullptr;
Button *profiler_autostart_indicator = nullptr;
PanelContainer *recovery_mode_panel = nullptr;
Button *recovery_mode_button = nullptr;
Button *recovery_mode_reload_button = nullptr;
AcceptDialog *recovery_mode_popup = nullptr;
Button *play_button = nullptr;
Button *pause_button = nullptr;
Button *stop_button = nullptr;
Button *play_scene_button = nullptr;
Button *play_custom_scene_button = nullptr;
EditorRun editor_run;
EditorRunNative *run_native = nullptr;
enum MovieMakerMenuItem {
MOVIE_MAKER_TOGGLE,
MOVIE_MAKER_OPEN_SETTINGS,
};
PanelContainer *write_movie_panel = nullptr;
MenuButton *write_movie_button = nullptr;
bool movie_maker_enabled = false;
RunMode current_mode = RunMode::STOPPED;
String run_custom_filename;
String run_current_filename;
void _reset_play_buttons();
void _update_play_buttons();
void _movie_maker_item_pressed(int p_id);
void _write_movie_toggled(bool p_enabled);
void _quick_run_selected(const String &p_file_path, int p_id = -1);
void _play_current_pressed(int p_id = -1);
void _play_custom_pressed(int p_id = -1);
void _run_scene(const String &p_scene_path = "", const Vector<String> &p_run_args = Vector<String>());
void _run_native(const Ref<EditorExportPreset> &p_preset);
void _profiler_autostart_indicator_pressed();
private:
static Vector<String> _get_xr_mode_play_args(int p_xr_mode_id);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
static EditorRunBar *get_singleton() { return singleton; }
void recovery_mode_show_dialog();
void recovery_mode_reload_project();
void play_main_scene(bool p_from_native = false);
void play_current_scene(bool p_reload = false, const Vector<String> &p_play_args = Vector<String>());
void play_custom_scene(const String &p_custom, const Vector<String> &p_play_args = Vector<String>());
void stop_playing();
bool is_playing() const;
String get_playing_scene() const;
Error start_native_device(int p_device_id);
OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
void stop_child_process(OS::ProcessID p_pid);
OS::ProcessID get_current_process() const;
void set_movie_maker_enabled(bool p_enabled);
bool is_movie_maker_enabled() const;
void update_profiler_autostart_indicator();
Button *get_pause_button() { return pause_button; }
HBoxContainer *get_buttons_container();
EditorRunBar();
};

View file

@ -1,465 +0,0 @@
/**************************************************************************/
/* editor_scene_tabs.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_scene_tabs.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/tab_bar.h"
#include "scene/gui/texture_rect.h"
void EditorSceneTabs::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
tabbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("tabbar_background"), SNAME("TabContainer")));
scene_tabs->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
scene_tab_add->set_button_icon(get_editor_theme_icon(SNAME("Add")));
scene_tab_add->add_theme_color_override("icon_normal_color", Color(0.6f, 0.6f, 0.6f, 0.8f));
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/scene_tabs")) {
scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int());
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
_scene_tabs_resized();
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
_scene_tabs_resized();
} break;
}
}
void EditorSceneTabs::_scene_tab_changed(int p_tab) {
tab_preview_panel->hide();
emit_signal("tab_changed", p_tab);
}
void EditorSceneTabs::_scene_tab_script_edited(int p_tab) {
Ref<Script> scr = EditorNode::get_editor_data().get_scene_root_script(p_tab);
if (scr.is_valid()) {
InspectorDock::get_singleton()->edit_resource(scr);
}
}
void EditorSceneTabs::_scene_tab_closed(int p_tab) {
emit_signal("tab_closed", p_tab);
}
void EditorSceneTabs::_scene_tab_hovered(int p_tab) {
if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) {
return;
}
// Currently the tab previews are displayed under the running game process when embed.
// Right now, the easiest technique to fix that is to prevent displaying the tab preview
// when the user is in the Game View.
if (EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME && EditorRunBar::get_singleton()->is_playing()) {
return;
}
int current_tab = scene_tabs->get_current_tab();
if (p_tab == current_tab || p_tab < 0) {
tab_preview_panel->hide();
} else {
String path = EditorNode::get_editor_data().get_scene_path(p_tab);
if (!path.is_empty()) {
EditorResourcePreview::get_singleton()->queue_resource_preview(path, this, "_tab_preview_done", p_tab);
}
}
}
void EditorSceneTabs::_scene_tab_exit() {
tab_preview_panel->hide();
}
void EditorSceneTabs::_scene_tab_input(const Ref<InputEvent> &p_input) {
Ref<InputEventMouseButton> mb = p_input;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) {
int tab_buttons = 0;
if (scene_tabs->get_offset_buttons_visible()) {
tab_buttons = get_theme_icon(SNAME("increment"), SNAME("TabBar"))->get_width() + get_theme_icon(SNAME("decrement"), SNAME("TabBar"))->get_width();
}
if ((is_layout_rtl() && mb->get_position().x > tab_buttons) || (!is_layout_rtl() && mb->get_position().x < scene_tabs->get_size().width - tab_buttons)) {
EditorNode::get_singleton()->trigger_menu_option(EditorNode::SCENE_NEW_SCENE, true);
}
}
if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
// Context menu.
_update_context_menu();
scene_tabs_context_menu->set_position(scene_tabs->get_screen_position() + mb->get_position());
scene_tabs_context_menu->reset_size();
scene_tabs_context_menu->popup();
}
}
}
void EditorSceneTabs::unhandled_key_input(const Ref<InputEvent> &p_event) {
if (!tab_preview_panel->is_visible()) {
return;
}
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
tab_preview_panel->hide();
}
}
void EditorSceneTabs::_reposition_active_tab(int p_to_index) {
EditorNode::get_editor_data().move_edited_scene_to_index(p_to_index);
update_scene_tabs();
}
void EditorSceneTabs::_update_context_menu() {
#define DISABLE_LAST_OPTION_IF(m_condition) \
if (m_condition) { \
scene_tabs_context_menu->set_item_disabled(-1, true); \
}
scene_tabs_context_menu->clear();
scene_tabs_context_menu->reset_size();
int tab_id = scene_tabs->get_hovered_tab();
bool no_root_node = !EditorNode::get_editor_data().get_edited_scene_root(tab_id);
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), EditorNode::SCENE_NEW_SCENE);
if (tab_id >= 0) {
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), EditorNode::SCENE_SAVE_SCENE);
DISABLE_LAST_OPTION_IF(no_root_node);
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), EditorNode::SCENE_SAVE_AS_SCENE);
DISABLE_LAST_OPTION_IF(no_root_node);
}
bool can_save_all_scenes = false;
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
if (!EditorNode::get_editor_data().get_scene_path(i).is_empty() && EditorNode::get_editor_data().get_edited_scene_root(i)) {
can_save_all_scenes = true;
break;
}
}
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::SCENE_SAVE_ALL_SCENES);
DISABLE_LAST_OPTION_IF(!can_save_all_scenes);
if (tab_id >= 0) {
scene_tabs_context_menu->add_separator();
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), SCENE_SHOW_IN_FILESYSTEM);
DISABLE_LAST_OPTION_IF(!ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id)));
scene_tabs_context_menu->add_item(TTR("Play This Scene"), SCENE_RUN);
DISABLE_LAST_OPTION_IF(no_root_node);
scene_tabs_context_menu->add_separator();
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene"), EditorNode::SCENE_CLOSE);
scene_tabs_context_menu->set_item_text(-1, TTR("Close Tab"));
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/reopen_closed_scene"), EditorNode::SCENE_OPEN_PREV);
scene_tabs_context_menu->set_item_text(-1, TTR("Undo Close Tab"));
DISABLE_LAST_OPTION_IF(!EditorNode::get_singleton()->has_previous_closed_scenes());
scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), SCENE_CLOSE_OTHERS);
DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() <= 1);
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), SCENE_CLOSE_RIGHT);
DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1);
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), SCENE_CLOSE_ALL);
const PackedStringArray paths = { EditorNode::get_editor_data().get_scene_path(tab_id) };
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, paths);
} else {
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, {});
}
#undef DISABLE_LAST_OPTION_IF
last_hovered_tab = tab_id;
}
void EditorSceneTabs::_custom_menu_option(int p_option) {
if (p_option >= EditorContextMenuPlugin::BASE_ID) {
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, p_option, last_hovered_tab >= 0 ? EditorNode::get_editor_data().get_scene_path(last_hovered_tab) : String());
}
}
void EditorSceneTabs::update_scene_tabs() {
static bool menu_initialized = false;
tab_preview_panel->hide();
if (menu_initialized && scene_tabs->get_tab_count() == EditorNode::get_editor_data().get_edited_scene_count()) {
_update_tab_titles();
return;
}
menu_initialized = true;
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
NativeMenu::get_singleton()->clear(dock_rid);
}
scene_tabs->set_block_signals(true);
scene_tabs->set_tab_count(EditorNode::get_editor_data().get_edited_scene_count());
scene_tabs->set_block_signals(false);
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
int global_menu_index = NativeMenu::get_singleton()->add_item(dock_rid, EditorNode::get_editor_data().get_scene_title(i), callable_mp(this, &EditorSceneTabs::_global_menu_scene), Callable(), i);
scene_tabs->set_tab_metadata(i, global_menu_index);
}
NativeMenu::get_singleton()->add_separator(dock_rid);
NativeMenu::get_singleton()->add_item(dock_rid, TTR("New Window"), callable_mp(this, &EditorSceneTabs::_global_menu_new_window));
}
_update_tab_titles();
}
void EditorSceneTabs::_update_tab_titles() {
bool show_rb = EDITOR_GET("interface/scene_tabs/show_script_button");
// Get all scene names, which may be ambiguous.
Vector<String> disambiguated_scene_names;
Vector<String> full_path_names;
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
disambiguated_scene_names.append(EditorNode::get_editor_data().get_scene_title(i));
full_path_names.append(EditorNode::get_editor_data().get_scene_path(i));
}
EditorNode::disambiguate_filenames(full_path_names, disambiguated_scene_names);
Ref<Texture2D> script_icon = get_editor_theme_icon(SNAME("Script"));
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
Node *type_node = EditorNode::get_editor_data().get_edited_scene_root(i);
Ref<Texture2D> icon;
if (type_node) {
icon = EditorNode::get_singleton()->get_object_icon(type_node, "Node");
}
scene_tabs->set_tab_icon(i, icon);
bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorNode::get_editor_data().get_scene_history_id(i));
scene_tabs->set_tab_title(i, disambiguated_scene_names[i] + (unsaved ? "(*)" : ""));
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
int global_menu_index = scene_tabs->get_tab_metadata(i);
NativeMenu::get_singleton()->set_item_text(dock_rid, global_menu_index, EditorNode::get_editor_data().get_scene_title(i) + (unsaved ? "(*)" : ""));
NativeMenu::get_singleton()->set_item_tag(dock_rid, global_menu_index, i);
}
if (show_rb && EditorNode::get_editor_data().get_scene_root_script(i).is_valid()) {
scene_tabs->set_tab_button_icon(i, script_icon);
} else {
scene_tabs->set_tab_button_icon(i, nullptr);
}
}
int current_tab = EditorNode::get_editor_data().get_edited_scene();
if (scene_tabs->get_tab_count() > 0 && scene_tabs->get_current_tab() != current_tab) {
scene_tabs->set_block_signals(true);
scene_tabs->set_current_tab(current_tab);
scene_tabs->set_block_signals(false);
}
_scene_tabs_resized();
}
void EditorSceneTabs::_scene_tabs_resized() {
const Size2 add_button_size = Size2(scene_tab_add->get_size().x, scene_tabs->get_size().y);
if (scene_tabs->get_offset_buttons_visible()) {
// Move the add button to a fixed position.
if (scene_tab_add->get_parent() == scene_tabs) {
scene_tabs->remove_child(scene_tab_add);
scene_tab_add_ph->add_child(scene_tab_add);
scene_tab_add->set_rect(Rect2(Point2(), add_button_size));
}
} else {
// Move the add button to be after the last tab.
if (scene_tab_add->get_parent() == scene_tab_add_ph) {
scene_tab_add_ph->remove_child(scene_tab_add);
scene_tabs->add_child(scene_tab_add);
}
if (scene_tabs->get_tab_count() == 0) {
scene_tab_add->set_rect(Rect2(Point2(), add_button_size));
return;
}
Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1);
int hsep = scene_tabs->get_theme_constant(SNAME("h_separation"));
if (scene_tabs->is_layout_rtl()) {
scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x - add_button_size.x - hsep, last_tab.position.y), add_button_size));
} else {
scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y), add_button_size));
}
}
}
void EditorSceneTabs::_tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
int p_tab = p_udata;
if (p_preview.is_valid()) {
tab_preview->set_texture(p_preview);
Rect2 rect = scene_tabs->get_tab_rect(p_tab);
rect.position += scene_tabs->get_global_position();
tab_preview_panel->set_global_position(rect.position + Vector2(0, rect.size.height));
tab_preview_panel->show();
}
}
void EditorSceneTabs::_global_menu_scene(const Variant &p_tag) {
int idx = (int)p_tag;
scene_tabs->set_current_tab(idx);
}
void EditorSceneTabs::_global_menu_new_window(const Variant &p_tag) {
if (OS::get_singleton()->get_main_loop()) {
List<String> args;
args.push_back("-p");
OS::get_singleton()->create_instance(args);
}
}
void EditorSceneTabs::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventKey> k = p_event;
if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) {
if (ED_IS_SHORTCUT("editor/next_tab", p_event)) {
int next_tab = EditorNode::get_editor_data().get_edited_scene() + 1;
next_tab %= EditorNode::get_editor_data().get_edited_scene_count();
_scene_tab_changed(next_tab);
}
if (ED_IS_SHORTCUT("editor/prev_tab", p_event)) {
int next_tab = EditorNode::get_editor_data().get_edited_scene() - 1;
next_tab = next_tab >= 0 ? next_tab : EditorNode::get_editor_data().get_edited_scene_count() - 1;
_scene_tab_changed(next_tab);
}
}
}
void EditorSceneTabs::add_extra_button(Button *p_button) {
tabbar_container->add_child(p_button);
}
void EditorSceneTabs::set_current_tab(int p_tab) {
scene_tabs->set_current_tab(p_tab);
}
int EditorSceneTabs::get_current_tab() const {
return scene_tabs->get_current_tab();
}
void EditorSceneTabs::_bind_methods() {
ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab_index")));
ADD_SIGNAL(MethodInfo("tab_closed", PropertyInfo(Variant::INT, "tab_index")));
ClassDB::bind_method("_tab_preview_done", &EditorSceneTabs::_tab_preview_done);
}
EditorSceneTabs::EditorSceneTabs() {
singleton = this;
set_process_shortcut_input(true);
set_process_unhandled_key_input(true);
tabbar_panel = memnew(PanelContainer);
add_child(tabbar_panel);
tabbar_container = memnew(HBoxContainer);
tabbar_panel->add_child(tabbar_container);
scene_tabs = memnew(TabBar);
scene_tabs->set_select_with_rmb(true);
scene_tabs->add_tab("unsaved");
scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int());
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
scene_tabs->set_drag_to_rearrange_enabled(true);
scene_tabs->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
scene_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tabbar_container->add_child(scene_tabs);
scene_tabs->connect("tab_changed", callable_mp(this, &EditorSceneTabs::_scene_tab_changed));
scene_tabs->connect("tab_button_pressed", callable_mp(this, &EditorSceneTabs::_scene_tab_script_edited));
scene_tabs->connect("tab_close_pressed", callable_mp(this, &EditorSceneTabs::_scene_tab_closed));
scene_tabs->connect("tab_hovered", callable_mp(this, &EditorSceneTabs::_scene_tab_hovered));
scene_tabs->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorSceneTabs::_scene_tab_exit));
scene_tabs->connect(SceneStringName(gui_input), callable_mp(this, &EditorSceneTabs::_scene_tab_input));
scene_tabs->connect("active_tab_rearranged", callable_mp(this, &EditorSceneTabs::_reposition_active_tab));
scene_tabs->connect(SceneStringName(resized), callable_mp(this, &EditorSceneTabs::_scene_tabs_resized), CONNECT_DEFERRED);
scene_tabs_context_menu = memnew(PopupMenu);
tabbar_container->add_child(scene_tabs_context_menu);
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(false));
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorSceneTabs::_custom_menu_option));
scene_tab_add = memnew(Button);
scene_tab_add->set_flat(true);
scene_tab_add->set_tooltip_text(TTR("Add a new scene."));
scene_tabs->add_child(scene_tab_add);
scene_tab_add->connect(SceneStringName(pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(EditorNode::SCENE_NEW_SCENE, false));
scene_tab_add_ph = memnew(Control);
scene_tab_add_ph->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
tabbar_container->add_child(scene_tab_add_ph);
// On-hover tab preview.
Control *tab_preview_anchor = memnew(Control);
tab_preview_anchor->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
add_child(tab_preview_anchor);
tab_preview_panel = memnew(Panel);
tab_preview_panel->set_size(Size2(100, 100) * EDSCALE);
tab_preview_panel->hide();
tab_preview_panel->set_self_modulate(Color(1, 1, 1, 0.7));
tab_preview_anchor->add_child(tab_preview_panel);
tab_preview = memnew(TextureRect);
tab_preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
tab_preview->set_size(Size2(96, 96) * EDSCALE);
tab_preview->set_position(Point2(2, 2) * EDSCALE);
tab_preview_panel->add_child(tab_preview);
}

View file

@ -33,7 +33,7 @@
#include "core/input/input.h"
#include "core/math/expression.h"
#include "core/os/keyboard.h"
#include "editor/editor_settings.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/theme/theme_db.h"

View file

@ -30,8 +30,8 @@
#include "editor_toaster.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"

View file

@ -1,76 +0,0 @@
/**************************************************************************/
/* editor_translation_preview_button.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_translation_preview_button.h"
#include "core/string/translation_server.h"
#include "editor/editor_node.h"
void EditorTranslationPreviewButton::_update() {
const String &locale = EditorNode::get_singleton()->get_preview_locale();
if (locale.is_empty()) {
hide();
return;
}
const String name = TranslationServer::get_singleton()->get_locale_name(locale);
set_text(vformat(TTR("Previewing: %s"), name == locale ? locale : name + " [" + locale + "]"));
show();
}
void EditorTranslationPreviewButton::pressed() {
EditorNode::get_singleton()->set_preview_locale(String());
}
void EditorTranslationPreviewButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
set_button_icon(get_editor_theme_icon(SNAME("Translation")));
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
_update();
} break;
case NOTIFICATION_READY: {
EditorNode::get_singleton()->connect("preview_locale_changed", callable_mp(this, &EditorTranslationPreviewButton::_update));
} break;
}
}
EditorTranslationPreviewButton::EditorTranslationPreviewButton() {
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_ALWAYS);
set_accessibility_name(TTRC("Disable Translation Preview"));
set_tooltip_text(TTRC("Previewing translation. Click to disable."));
set_focus_mode(FOCUS_NONE);
set_visible(false);
}

View file

@ -1,81 +0,0 @@
/**************************************************************************/
/* editor_translation_preview_menu.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/gui/editor_translation_preview_menu.h"
#include "core/string/translation_server.h"
#include "editor/editor_node.h"
void EditorTranslationPreviewMenu::_prepare() {
const String current_preview_locale = EditorNode::get_singleton()->get_preview_locale();
clear();
reset_size();
add_radio_check_item(TTRC("None"));
set_item_metadata(-1, "");
if (current_preview_locale.is_empty()) {
set_item_checked(-1, true);
}
const Vector<String> locales = TranslationServer::get_singleton()->get_loaded_locales();
if (!locales.is_empty()) {
add_separator();
}
for (const String &locale : locales) {
const String name = TranslationServer::get_singleton()->get_locale_name(locale);
add_radio_check_item(name == locale ? name : name + " [" + locale + "]");
set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);
set_item_metadata(-1, locale);
if (locale == current_preview_locale) {
set_item_checked(-1, true);
}
}
}
void EditorTranslationPreviewMenu::_pressed(int p_index) {
for (int i = 0; i < get_item_count(); i++) {
set_item_checked(i, i == p_index);
}
EditorNode::get_singleton()->set_preview_locale(get_item_metadata(p_index));
}
void EditorTranslationPreviewMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
connect("about_to_popup", callable_mp(this, &EditorTranslationPreviewMenu::_prepare));
connect("index_pressed", callable_mp(this, &EditorTranslationPreviewMenu::_pressed));
} break;
}
}
EditorTranslationPreviewMenu::EditorTranslationPreviewMenu() {
set_hide_on_checkable_item_selection(false);
}

View file

@ -31,7 +31,7 @@
#include "editor_zoom_widget.h"
#include "core/os/keyboard.h"
#include "editor/editor_settings.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
void EditorZoomWidget::_update_zoom_label() {

View file

@ -0,0 +1,296 @@
/**************************************************************************/
/* progress_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 "progress_dialog.h"
#include "core/os/os.h"
#include "editor/editor_interface.h"
#include "editor/editor_node.h"
#include "editor/themes/editor_scale.h"
#include "main/main.h"
#include "scene/gui/panel_container.h"
#include "scene/main/window.h"
#include "servers/display_server.h"
void BackgroundProgress::_add_task(const String &p_task, const String &p_label, int p_steps) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_MSG(tasks.has(p_task), "Task '" + p_task + "' already exists.");
BackgroundProgress::Task t;
t.hb = memnew(HBoxContainer);
Label *l = memnew(Label);
l->set_text(p_label + " ");
t.hb->add_child(l);
t.progress = memnew(ProgressBar);
t.progress->set_max(p_steps);
t.progress->set_value(p_steps);
Control *ec = memnew(Control);
ec->set_h_size_flags(SIZE_EXPAND_FILL);
ec->set_v_size_flags(SIZE_EXPAND_FILL);
t.progress->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
ec->add_child(t.progress);
ec->set_custom_minimum_size(Size2(80, 5) * EDSCALE);
t.hb->add_child(ec);
add_child(t.hb);
tasks[p_task] = t;
}
void BackgroundProgress::_update() {
_THREAD_SAFE_METHOD_
for (const KeyValue<String, int> &E : updates) {
if (tasks.has(E.key)) {
_task_step(E.key, E.value);
}
}
updates.clear();
}
void BackgroundProgress::_task_step(const String &p_task, int p_step) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!tasks.has(p_task));
Task &t = tasks[p_task];
if (p_step < 0) {
t.progress->set_value(t.progress->get_value() + 1);
} else {
t.progress->set_value(p_step);
}
}
void BackgroundProgress::_end_task(const String &p_task) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!tasks.has(p_task));
Task &t = tasks[p_task];
memdelete(t.hb);
tasks.erase(p_task);
}
void BackgroundProgress::add_task(const String &p_task, const String &p_label, int p_steps) {
callable_mp(this, &BackgroundProgress::_add_task).call_deferred(p_task, p_label, p_steps);
}
void BackgroundProgress::task_step(const String &p_task, int p_step) {
//this code is weird, but it prevents deadlock.
bool no_updates = true;
{
_THREAD_SAFE_METHOD_
no_updates = updates.is_empty();
}
if (no_updates) {
callable_mp(this, &BackgroundProgress::_update).call_deferred();
}
{
_THREAD_SAFE_METHOD_
updates[p_task] = p_step;
}
}
void BackgroundProgress::end_task(const String &p_task) {
callable_mp(this, &BackgroundProgress::_end_task).call_deferred(p_task);
}
////////////////////////////////////////////////
ProgressDialog *ProgressDialog::singleton = nullptr;
void ProgressDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
Ref<StyleBox> style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu"));
main_border_size = style->get_minimum_size();
main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT));
main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT));
main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP));
main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM));
center_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), "PopupPanel"));
} break;
}
}
void ProgressDialog::_update_ui() {
// Run main loop for two frames.
if (is_inside_tree()) {
DisplayServer::get_singleton()->process_events();
Main::iteration();
}
}
void ProgressDialog::_popup() {
// Activate processing of all inputs in EditorNode, and the EditorNode::input method
// will discard every key input.
EditorNode::get_singleton()->set_process_input(true);
// Disable all other windows to prevent interaction with them.
for (Window *w : host_windows) {
w->set_process_mode(PROCESS_MODE_DISABLED);
}
Size2 ms = main->get_combined_minimum_size();
ms.width = MAX(500 * EDSCALE, ms.width);
ms += main_border_size;
center_panel->set_custom_minimum_size(ms);
Window *current_window = SceneTree::get_singleton()->get_root()->get_last_exclusive_window();
ERR_FAIL_NULL(current_window);
reparent(current_window);
// Ensures that events are properly released before the dialog blocks input.
bool window_is_input_disabled = current_window->is_input_disabled();
current_window->set_disable_input(!window_is_input_disabled);
current_window->set_disable_input(window_is_input_disabled);
show();
}
void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
if (MessageQueue::get_singleton()->is_flushing()) {
ERR_PRINT("Do not use progress dialog (task) while flushing the message queue or using call_deferred()!");
return;
}
ERR_FAIL_COND_MSG(tasks.has(p_task), "Task '" + p_task + "' already exists.");
ProgressDialog::Task t;
t.vb = memnew(VBoxContainer);
VBoxContainer *vb2 = memnew(VBoxContainer);
t.vb->add_margin_child(p_label, vb2);
t.progress = memnew(ProgressBar);
t.progress->set_max(p_steps);
t.progress->set_value(p_steps);
vb2->add_child(t.progress);
t.state = memnew(Label);
t.state->set_clip_text(true);
vb2->add_child(t.state);
main->add_child(t.vb);
tasks[p_task] = t;
if (p_can_cancel) {
cancel_hb->show();
} else {
cancel_hb->hide();
}
cancel_hb->move_to_front();
canceled = false;
_popup();
if (p_can_cancel) {
cancel->grab_focus();
}
_update_ui();
}
bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
ERR_FAIL_COND_V(!tasks.has(p_task), canceled);
Task &t = tasks[p_task];
if (!p_force_redraw) {
uint64_t tus = OS::get_singleton()->get_ticks_usec();
if (tus - t.last_progress_tick < 200000) { //200ms
return canceled;
}
}
if (p_step < 0) {
t.progress->set_value(t.progress->get_value() + 1);
} else {
t.progress->set_value(p_step);
}
t.state->set_text(p_state);
t.last_progress_tick = OS::get_singleton()->get_ticks_usec();
_update_ui();
return canceled;
}
void ProgressDialog::end_task(const String &p_task) {
ERR_FAIL_COND(!tasks.has(p_task));
Task &t = tasks[p_task];
memdelete(t.vb);
tasks.erase(p_task);
if (tasks.is_empty()) {
hide();
EditorNode::get_singleton()->set_process_input(false);
for (Window *w : host_windows) {
w->set_process_mode(PROCESS_MODE_INHERIT);
}
} else {
_popup();
}
}
void ProgressDialog::add_host_window(Window *p_window) {
ERR_FAIL_NULL(p_window);
host_windows.push_back(p_window);
}
void ProgressDialog::remove_host_window(Window *p_window) {
ERR_FAIL_NULL(p_window);
host_windows.erase(p_window);
}
void ProgressDialog::_cancel_pressed() {
canceled = true;
}
ProgressDialog::ProgressDialog() {
// We want to cover the entire screen to prevent the user from interacting with the Editor.
set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
// Be sure it's the top most component.
set_z_index(RS::CANVAS_ITEM_Z_MAX);
singleton = this;
hide();
center_panel = memnew(PanelContainer);
add_child(center_panel);
center_panel->set_h_size_flags(SIZE_SHRINK_BEGIN);
center_panel->set_v_size_flags(SIZE_SHRINK_BEGIN);
main = memnew(VBoxContainer);
center_panel->add_child(main);
cancel_hb = memnew(HBoxContainer);
main->add_child(cancel_hb);
cancel_hb->hide();
cancel = memnew(Button);
cancel_hb->add_spacer();
cancel_hb->add_child(cancel);
cancel->set_text(TTR("Cancel"));
cancel_hb->add_spacer();
cancel->connect(SceneStringName(pressed), callable_mp(this, &ProgressDialog::_cancel_pressed));
}

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* editor_scene_tabs.h */
/* progress_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,78 +30,78 @@
#pragma once
#include "scene/gui/margin_container.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/center_container.h"
#include "scene/gui/label.h"
#include "scene/gui/progress_bar.h"
class Button;
class HBoxContainer;
class Panel;
class PanelContainer;
class PopupMenu;
class TabBar;
class TextureRect;
class BackgroundProgress : public HBoxContainer {
GDCLASS(BackgroundProgress, HBoxContainer);
class EditorSceneTabs : public MarginContainer {
GDCLASS(EditorSceneTabs, MarginContainer);
_THREAD_SAFE_CLASS_
inline static EditorSceneTabs *singleton = nullptr;
public:
enum {
SCENE_SHOW_IN_FILESYSTEM = 1000, // Prevents conflicts with EditorNode options.
SCENE_RUN,
SCENE_CLOSE_OTHERS,
SCENE_CLOSE_RIGHT,
SCENE_CLOSE_ALL,
struct Task {
HBoxContainer *hb = nullptr;
ProgressBar *progress = nullptr;
};
private:
PanelContainer *tabbar_panel = nullptr;
HBoxContainer *tabbar_container = nullptr;
HashMap<String, Task> tasks;
HashMap<String, int> updates;
void _update();
TabBar *scene_tabs = nullptr;
PopupMenu *scene_tabs_context_menu = nullptr;
Button *scene_tab_add = nullptr;
Control *scene_tab_add_ph = nullptr;
protected:
void _add_task(const String &p_task, const String &p_label, int p_steps);
void _task_step(const String &p_task, int p_step = -1);
void _end_task(const String &p_task);
Panel *tab_preview_panel = nullptr;
TextureRect *tab_preview = nullptr;
public:
void add_task(const String &p_task, const String &p_label, int p_steps);
void task_step(const String &p_task, int p_step = -1);
void end_task(const String &p_task);
};
int last_hovered_tab = -1;
class PanelContainer;
void _scene_tab_changed(int p_tab);
void _scene_tab_script_edited(int p_tab);
void _scene_tab_closed(int p_tab);
void _scene_tab_hovered(int p_tab);
void _scene_tab_exit();
void _scene_tab_input(const Ref<InputEvent> &p_input);
void _scene_tabs_resized();
class ProgressDialog : public CenterContainer {
GDCLASS(ProgressDialog, CenterContainer);
struct Task {
String task;
VBoxContainer *vb = nullptr;
ProgressBar *progress = nullptr;
Label *state = nullptr;
uint64_t last_progress_tick = 0;
};
HBoxContainer *cancel_hb = nullptr;
Button *cancel = nullptr;
void _update_tab_titles();
void _reposition_active_tab(int p_to_index);
void _update_context_menu();
void _custom_menu_option(int p_option);
HashMap<String, Task> tasks;
PanelContainer *center_panel = nullptr;
VBoxContainer *main = nullptr;
void _tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
LocalVector<Window *> host_windows;
void _global_menu_scene(const Variant &p_tag);
void _global_menu_new_window(const Variant &p_tag);
Size2 main_border_size;
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
static ProgressDialog *singleton;
void _popup();
void _cancel_pressed();
void _update_ui();
bool canceled = false;
protected:
void _notification(int p_what);
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
static void _bind_methods();
public:
static EditorSceneTabs *get_singleton() { return singleton; }
static ProgressDialog *get_singleton() { return singleton; }
void add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false);
bool task_step(const String &p_task, const String &p_state, int p_step = -1, bool p_force_redraw = true);
void end_task(const String &p_task);
void add_extra_button(Button *p_button);
void add_host_window(Window *p_window);
void remove_host_window(Window *p_window);
void set_current_tab(int p_tab);
int get_current_tab() const;
void update_scene_tabs();
EditorSceneTabs();
ProgressDialog();
};

File diff suppressed because it is too large Load diff

View file

@ -1,297 +0,0 @@
/**************************************************************************/
/* scene_tree_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/check_box.h"
#include "scene/gui/check_button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
class EditorSelection;
class TextureRect;
class Timer;
class SceneTreeEditor : public Control {
GDCLASS(SceneTreeEditor, Control);
EditorSelection *editor_selection = nullptr;
enum SceneTreeEditorButton {
BUTTON_SUBSCENE = 0,
BUTTON_VISIBILITY = 1,
BUTTON_SCRIPT = 2,
BUTTON_LOCK = 3,
BUTTON_GROUP = 4,
BUTTON_WARNING = 5,
BUTTON_SIGNALS = 6,
BUTTON_GROUPS = 7,
BUTTON_PIN = 8,
BUTTON_UNIQUE = 9,
};
struct CachedNode {
Node *node = nullptr;
TreeItem *item = nullptr;
int index = -1;
bool dirty = true;
bool has_moved_children = false;
bool removed = false;
// Store the iterator for faster removal. This is safe as
// HashMap never moves elements.
HashMap<Node *, CachedNode>::Iterator cache_iterator;
// This is safe because it gets compared to a uint8_t.
uint16_t delete_serial = UINT16_MAX;
// To know whether to update children or not.
bool can_process = false;
CachedNode() = delete; // Always an error.
CachedNode(Node *p_node, TreeItem *p_item) :
node(p_node), item(p_item) {}
};
struct NodeCache {
~NodeCache() {
clear();
}
NodeCache(SceneTreeEditor *p_editor) :
editor(p_editor) {}
HashMap<Node *, CachedNode>::Iterator add(Node *p_node, TreeItem *p_item);
HashMap<Node *, CachedNode>::Iterator get(Node *p_node, bool p_deleted_ok = true);
bool has(Node *p_node);
void remove(Node *p_node, bool p_recursive = false);
void mark_dirty(Node *p_node, bool p_parents = true);
void mark_children_dirty(Node *p_node, bool p_recursive = false);
void delete_pending();
void clear();
SceneTreeEditor *editor;
HashMap<Node *, CachedNode> cache;
HashSet<CachedNode *> to_delete;
Node *current_scene_node = nullptr;
Node *current_pinned_node = nullptr;
bool current_has_pin = false;
bool force_update = false;
uint8_t delete_serial = 0;
};
NodeCache node_cache;
Tree *tree = nullptr;
Node *selected = nullptr;
ObjectID instance_node;
String filter;
String filter_term_warning;
bool show_all_nodes = false;
AcceptDialog *error = nullptr;
AcceptDialog *warning = nullptr;
ConfirmationDialog *revoke_dialog = nullptr;
Label *revoke_dialog_label = nullptr;
CheckBox *ask_before_revoke_checkbox = nullptr;
Node *revoke_node = nullptr;
bool auto_expand_selected = true;
bool hide_filtered_out_parents = false;
bool accessibility_warnings = false;
bool connect_to_script_mode = false;
bool connecting_signal = false;
bool update_when_invisible = true;
int blocked;
void _compute_hash(Node *p_node, uint64_t &hash);
void _reset();
PackedStringArray _get_node_configuration_warnings(Node *p_node);
PackedStringArray _get_node_accessibility_configuration_warnings(Node *p_node);
void _update_node_path(Node *p_node, bool p_recursive = true);
void _update_node_subtree(Node *p_node, TreeItem *p_parent, bool p_force = false);
void _update_node(Node *p_node, TreeItem *p_item, bool p_part_of_subscene);
void _update_if_clean();
void _test_update_tree();
bool _update_filter(TreeItem *p_parent = nullptr, bool p_scroll_to_selected = false);
bool _node_matches_class_term(const Node *p_item_node, const String &p_term);
bool _item_matches_all_terms(TreeItem *p_item, const PackedStringArray &p_terms);
void _tree_changed();
void _tree_process_mode_changed();
void _move_node_children(HashMap<Node *, CachedNode>::Iterator &p_I);
void _move_node_item(TreeItem *p_parent, HashMap<Node *, CachedNode>::Iterator &p_I);
void _node_child_order_changed(Node *p_node);
void _node_editor_state_changed(Node *p_node);
void _node_added(Node *p_node);
void _node_removed(Node *p_node);
void _node_renamed(Node *p_node);
TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
void _notification(int p_what);
void _selected_changed();
void _deselect_items();
void _cell_collapsed(Object *p_obj);
uint64_t last_hash;
bool can_rename;
bool can_open_instance;
bool updating_tree = false;
bool show_enabled_subscene = false;
bool is_scene_tree_dock = false;
void _edited();
void _renamed(TreeItem *p_item, TreeItem *p_batch_item, Node *p_node = nullptr);
HashSet<Node *> marked;
bool marked_selectable = false;
bool marked_children_selectable = false;
bool display_foreign = false;
bool tree_dirty = true;
bool pending_test_update = false;
Timer *update_node_tooltip_delay = nullptr;
static void _bind_methods();
void _cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _toggle_visible(Node *p_node);
void _cell_multi_selected(Object *p_object, int p_cell, bool p_selected);
void _update_selection(TreeItem *item);
void _node_script_changed(Node *p_node);
void _node_visibility_changed(Node *p_node);
void _update_visibility_color(Node *p_node, TreeItem *p_item);
void _set_item_custom_color(TreeItem *p_item, Color p_color);
void _update_node_tooltip(Node *p_node, TreeItem *p_item);
void _queue_update_node_tooltip(Node *p_node, TreeItem *p_item);
void _tree_scroll_to_item(ObjectID p_item_id);
void _selection_changed();
Node *get_scene_node() 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 _empty_clicked(const Vector2 &p_pos, MouseButton p_button);
void _rmb_select(const Vector2 &p_pos, MouseButton p_button = MouseButton::RIGHT);
void _warning_changed(Node *p_for_node);
void _update_marking_list(const HashSet<Node *> &p_marked);
Timer *update_timer = nullptr;
LocalVector<StringName> *script_types;
bool _is_script_type(const StringName &p_type) const;
Vector<StringName> valid_types;
void _update_ask_before_revoking_unique_name();
void _revoke_unique_name();
public:
// Public for use with callable_mp.
void _update_tree(bool p_scroll_to_selected = false);
void rename_node(Node *p_node, const String &p_name, TreeItem *p_item = nullptr);
void set_filter(const String &p_filter);
String get_filter() const;
String get_filter_term_warning();
void set_show_all_nodes(bool p_show_all_nodes);
void set_as_scene_tree_dock();
void set_display_foreign_nodes(bool p_display);
void set_marked(const HashSet<Node *> &p_marked, bool p_selectable = true, bool p_children_selectable = true);
void set_marked(Node *p_marked, bool p_selectable = true, bool p_children_selectable = true);
void set_selected(Node *p_node, bool p_emit_selected = true);
Node *get_selected();
void set_can_rename(bool p_can_rename) { can_rename = p_can_rename; }
void set_editor_selection(EditorSelection *p_selection);
void set_show_enabled_subscene(bool p_show) { show_enabled_subscene = p_show; }
void set_valid_types(const Vector<StringName> &p_valid);
inline void update_tree() { _update_tree(); }
void set_auto_expand_selected(bool p_auto, bool p_update_settings);
void set_hide_filtered_out_parents(bool p_hide, bool p_update_settings);
void set_accessibility_warnings(bool p_enable, bool p_update_settings);
void set_connect_to_script_mode(bool p_enable);
void set_connecting_signal(bool p_enable);
void set_update_when_invisible(bool p_enable);
Tree *get_scene_tree() { return tree; }
void update_warning();
SceneTreeEditor(bool p_label = true, bool p_can_rename = false, bool p_can_open_instance = false);
~SceneTreeEditor();
};
class SceneTreeDialog : public ConfirmationDialog {
GDCLASS(SceneTreeDialog, ConfirmationDialog);
VBoxContainer *content = nullptr;
SceneTreeEditor *tree = nullptr;
LineEdit *filter = nullptr;
CheckButton *show_all_nodes = nullptr;
LocalVector<TextureRect *> valid_type_icons;
HBoxContainer *allowed_types_hbox = nullptr;
void _select();
void _cancel();
void _selected_changed();
void _filter_changed(const String &p_filter);
void _on_filter_gui_input(const Ref<InputEvent> &p_event);
void _show_all_nodes_changed(bool p_button_pressed);
protected:
void _update_valid_type_icons();
void _notification(int p_what);
static void _bind_methods();
public:
void popup_scenetree_dialog(Node *p_selected_node = nullptr, Node *p_marked_node = nullptr, bool p_marked_node_selectable = true, bool p_marked_node_children_selectable = true);
void set_valid_types(const Vector<StringName> &p_valid);
SceneTreeEditor *get_scene_tree() { return tree; }
LineEdit *get_filter_line_edit() { return filter; }
SceneTreeDialog();
};

View file

@ -31,8 +31,8 @@
#include "touch_actions_panel.h"
#include "core/input/input.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/settings/editor_settings.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/color_rect.h"

View file

@ -0,0 +1,530 @@
/**************************************************************************/
/* window_wrapper.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 "window_wrapper.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/progress_dialog.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/gui/popup.h"
#include "scene/main/window.h"
// WindowWrapper
// Capture all shortcut events not handled by other nodes.
class ShortcutBin : public Node {
GDCLASS(ShortcutBin, Node);
virtual void _notification(int what) {
switch (what) {
case NOTIFICATION_READY:
set_process_shortcut_input(true);
break;
}
}
virtual void shortcut_input(const Ref<InputEvent> &p_event) override {
if (!get_window()->is_visible()) {
return;
}
Window *grandparent_window = get_window()->get_parent_visible_window();
ERR_FAIL_NULL(grandparent_window);
if (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventShortcut>(p_event.ptr())) {
// HACK: Propagate the window input to the editor main window to handle global shortcuts.
grandparent_window->push_input(p_event);
if (grandparent_window->is_input_handled()) {
get_viewport()->set_input_as_handled();
}
}
}
};
Rect2 WindowWrapper::_get_default_window_rect() const {
// Assume that the control rect is the desired one for the window.
return wrapped_control->get_screen_rect();
}
Node *WindowWrapper::_get_wrapped_control_parent() const {
if (margins) {
return margins;
}
return window;
}
void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect) {
ERR_FAIL_NULL(wrapped_control);
if (!is_window_available()) {
return;
}
if (window->is_visible() == p_visible) {
if (p_visible) {
window->grab_focus();
}
return;
}
Node *parent = _get_wrapped_control_parent();
if (wrapped_control->get_parent() != parent) {
// Move the control to the window.
wrapped_control->reparent(parent, false);
_set_window_rect(p_rect);
wrapped_control->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
} else if (!p_visible) {
// Remove control from window.
wrapped_control->reparent(this, false);
}
window->set_visible(p_visible);
if (!p_visible && !override_close_request) {
emit_signal("window_close_requested");
}
emit_signal("window_visibility_changed", p_visible);
}
void WindowWrapper::_set_window_rect(const Rect2 p_rect) {
// Set the window rect even when the window is maximized to have a good default size
// when the user remove the maximized mode.
window->set_position(p_rect.position);
window->set_size(p_rect.size);
if (EDITOR_GET("interface/multi_window/maximize_window")) {
window->set_mode(Window::MODE_MAXIMIZED);
}
}
void WindowWrapper::_window_size_changed() {
emit_signal(SNAME("window_size_changed"));
}
void WindowWrapper::_window_close_request() {
if (override_close_request) {
emit_signal("window_close_requested");
} else {
set_window_enabled(false);
}
}
void WindowWrapper::_bind_methods() {
ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible")));
ADD_SIGNAL(MethodInfo("window_close_requested"));
ADD_SIGNAL(MethodInfo("window_size_changed"));
}
void WindowWrapper::_notification(int p_what) {
if (!is_window_available()) {
return;
}
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
// Grab the focus when WindowWrapper.set_visible(true) is called
// and the window is showing.
grab_window_focus();
} break;
case NOTIFICATION_READY: {
set_process_shortcut_input(true);
} break;
case NOTIFICATION_THEME_CHANGED: {
window_background->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("PanelForeground", EditorStringName(EditorStyles)));
} break;
}
}
void WindowWrapper::shortcut_input(const Ref<InputEvent> &p_event) {
if (enable_shortcut.is_valid() && enable_shortcut->matches_event(p_event)) {
set_window_enabled(true);
}
}
void WindowWrapper::set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut) {
ERR_FAIL_NULL(p_control);
ERR_FAIL_COND(wrapped_control);
wrapped_control = p_control;
enable_shortcut = p_enable_shortcut;
add_child(p_control);
}
Control *WindowWrapper::get_wrapped_control() const {
return wrapped_control;
}
Control *WindowWrapper::release_wrapped_control() {
set_window_enabled(false);
if (wrapped_control) {
Control *old_wrapped = wrapped_control;
wrapped_control->get_parent()->remove_child(wrapped_control);
wrapped_control = nullptr;
return old_wrapped;
}
return nullptr;
}
bool WindowWrapper::is_window_available() const {
return window != nullptr;
}
bool WindowWrapper::get_window_enabled() const {
return is_window_available() ? window->is_visible() : false;
}
void WindowWrapper::set_window_enabled(bool p_enabled) {
_set_window_enabled_with_rect(p_enabled, _get_default_window_rect());
}
Rect2i WindowWrapper::get_window_rect() const {
ERR_FAIL_COND_V(!get_window_enabled(), Rect2i());
return Rect2i(window->get_position(), window->get_size());
}
int WindowWrapper::get_window_screen() const {
ERR_FAIL_COND_V(!get_window_enabled(), -1);
return window->get_current_screen();
}
void WindowWrapper::restore_window(const Rect2i &p_rect, int p_screen) {
ERR_FAIL_COND(!is_window_available());
ERR_FAIL_INDEX(p_screen, DisplayServer::get_singleton()->get_screen_count());
_set_window_enabled_with_rect(true, p_rect);
window->set_current_screen(p_screen);
}
void WindowWrapper::restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect) {
ERR_FAIL_COND(!is_window_available());
Rect2 window_rect = p_window_rect;
int screen = p_screen;
Rect2 restored_screen_rect = p_screen_rect;
if (screen < 0 || screen >= DisplayServer::get_singleton()->get_screen_count()) {
// Fallback to the main window screen if the saved screen is not available.
screen = get_window()->get_window_id();
}
Rect2i real_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
if (restored_screen_rect == Rect2i()) {
// Fallback to the target screen rect.
restored_screen_rect = real_screen_rect;
}
if (window_rect == Rect2i()) {
// Fallback to a standard rect.
window_rect = Rect2i(restored_screen_rect.position + restored_screen_rect.size / 4, restored_screen_rect.size / 2);
}
// Adjust the window rect size in case the resolution changes.
Vector2 screen_ratio = Vector2(real_screen_rect.size) / Vector2(restored_screen_rect.size);
// The screen positioning may change, so remove the original screen position.
window_rect.position -= restored_screen_rect.position;
window_rect = Rect2i(window_rect.position * screen_ratio, window_rect.size * screen_ratio);
window_rect.position += real_screen_rect.position;
// Make sure to restore the window if the user minimized it the last time it was displayed.
if (window->get_mode() == Window::MODE_MINIMIZED) {
window->set_mode(Window::MODE_WINDOWED);
}
// All good, restore the window.
window->set_current_screen(p_screen);
if (window->is_visible()) {
_set_window_rect(window_rect);
} else {
_set_window_enabled_with_rect(true, window_rect);
}
}
void WindowWrapper::enable_window_on_screen(int p_screen, bool p_auto_scale) {
int current_screen = Object::cast_to<Window>(get_viewport())->get_current_screen();
int screen = p_screen < 0 ? current_screen : p_screen;
bool auto_scale = p_auto_scale && !EDITOR_GET("interface/multi_window/maximize_window");
if (auto_scale && current_screen != screen) {
Rect2 control_rect = _get_default_window_rect();
Rect2i source_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(current_screen);
Rect2i dest_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
// Adjust the window rect size in case the resolution changes.
Vector2 screen_ratio = Vector2(source_screen_rect.size) / Vector2(dest_screen_rect.size);
// The screen positioning may change, so remove the original screen position.
control_rect.position -= source_screen_rect.position;
control_rect = Rect2i(control_rect.position * screen_ratio, control_rect.size * screen_ratio);
control_rect.position += dest_screen_rect.position;
restore_window(control_rect, p_screen);
} else {
window->set_current_screen(p_screen);
set_window_enabled(true);
}
}
void WindowWrapper::set_window_title(const String &p_title) {
if (!is_window_available()) {
return;
}
window->set_title(p_title);
}
void WindowWrapper::set_margins_enabled(bool p_enabled) {
if (!is_window_available()) {
return;
}
if (!p_enabled && margins) {
margins->queue_free();
margins = nullptr;
} else if (p_enabled && !margins) {
Size2 borders = Size2(4, 4) * EDSCALE;
margins = memnew(MarginContainer);
margins->add_theme_constant_override("margin_right", borders.width);
margins->add_theme_constant_override("margin_top", borders.height);
margins->add_theme_constant_override("margin_left", borders.width);
margins->add_theme_constant_override("margin_bottom", borders.height);
window->add_child(margins);
margins->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
}
}
Size2 WindowWrapper::get_margins_size() {
if (!margins) {
return Size2();
}
return Size2(margins->get_margin_size(SIDE_LEFT) + margins->get_margin_size(SIDE_RIGHT), margins->get_margin_size(SIDE_TOP) + margins->get_margin_size(SIDE_RIGHT));
}
Size2 WindowWrapper::get_margins_top_left() {
if (!margins) {
return Size2();
}
return Size2(margins->get_margin_size(SIDE_LEFT), margins->get_margin_size(SIDE_TOP));
}
void WindowWrapper::grab_window_focus() {
if (get_window_enabled() && is_visible()) {
window->grab_focus();
}
}
void WindowWrapper::set_override_close_request(bool p_enabled) {
override_close_request = p_enabled;
}
WindowWrapper::WindowWrapper() {
if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
return;
}
window = memnew(Window);
window_id = window->get_instance_id();
window->set_wrap_controls(true);
add_child(window);
window->hide();
window->connect("close_requested", callable_mp(this, &WindowWrapper::_window_close_request));
window->connect("size_changed", callable_mp(this, &WindowWrapper::_window_size_changed));
ShortcutBin *capturer = memnew(ShortcutBin);
window->add_child(capturer);
window_background = memnew(Panel);
window_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
window->add_child(window_background);
ProgressDialog::get_singleton()->add_host_window(window);
}
WindowWrapper::~WindowWrapper() {
if (ObjectDB::get_instance(window_id)) {
ProgressDialog::get_singleton()->remove_host_window(window);
}
}
// ScreenSelect
void ScreenSelect::_build_advanced_menu() {
// Clear old screen list.
while (screen_list->get_child_count(false) > 0) {
Node *child = screen_list->get_child(0);
screen_list->remove_child(child);
child->queue_free();
}
// Populate screen list.
const real_t height = real_t(get_theme_font_size(SceneStringName(font_size))) * 1.5;
int current_screen = get_window()->get_current_screen();
for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
Button *button = memnew(Button);
Size2 screen_size = Size2(DisplayServer::get_singleton()->screen_get_size(i));
Size2 button_size = Size2(height * (screen_size.x / screen_size.y), height);
button->set_custom_minimum_size(button_size);
screen_list->add_child(button);
button->set_text(itos(i));
button->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
button->set_tooltip_text(vformat(TTR("Make this panel floating in the screen %d."), i));
if (i == current_screen) {
Color accent_color = get_theme_color("accent_color", EditorStringName(Editor));
button->add_theme_color_override(SceneStringName(font_color), accent_color);
}
button->connect(SceneStringName(pressed), callable_mp(this, &ScreenSelect::_emit_screen_signal).bind(i));
button->connect(SceneStringName(pressed), callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false));
button->connect(SceneStringName(pressed), callable_mp(static_cast<Window *>(popup), &Popup::hide));
}
}
void ScreenSelect::_emit_screen_signal(int p_screen_idx) {
if (!is_disabled()) {
emit_signal("request_open_in_screen", p_screen_idx);
}
}
void ScreenSelect::_bind_methods() {
ADD_SIGNAL(MethodInfo("request_open_in_screen", PropertyInfo(Variant::INT, "screen")));
}
void ScreenSelect::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
connect(SceneStringName(gui_input), callable_mp(this, &ScreenSelect::_handle_mouse_shortcut));
} break;
case NOTIFICATION_THEME_CHANGED: {
set_button_icon(get_editor_theme_icon("MakeFloating"));
const real_t popup_height = real_t(get_theme_font_size(SceneStringName(font_size))) * 2.0;
popup->set_min_size(Size2(0, popup_height * 3));
} break;
}
}
void ScreenSelect::_handle_mouse_shortcut(const Ref<InputEvent> &p_event) {
const Ref<InputEventMouseButton> mouse_button = p_event;
if (mouse_button.is_valid()) {
if (mouse_button->is_pressed() && mouse_button->get_button_index() == MouseButton::LEFT) {
_emit_screen_signal(get_window()->get_current_screen());
accept_event();
}
}
}
void ScreenSelect::_show_popup() {
// Adapted from /scene/gui/menu_button.cpp::show_popup
if (!get_viewport()) {
return;
}
Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
popup->set_size(Size2(size.width, 0));
Point2 gp = get_screen_position();
gp.y += size.y;
if (is_layout_rtl()) {
gp.x += size.width - popup->get_size().width;
}
popup->set_position(gp);
popup->popup();
}
void ScreenSelect::pressed() {
if (popup->is_visible()) {
popup->hide();
return;
}
_build_advanced_menu();
_show_popup();
}
ScreenSelect::ScreenSelect() {
set_button_mask(MouseButtonMask::RIGHT);
set_flat(true);
set_toggle_mode(true);
set_focus_mode(FOCUS_NONE);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
set_disabled(true);
set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
} else {
set_tooltip_text(TTR("Make this panel floating.") + "\n" + TTR("Right-click to open the screen selector."));
}
// Create the popup.
const Size2 borders = Size2(4, 4) * EDSCALE;
popup = memnew(PopupPanel);
popup->connect("popup_hide", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false));
add_child(popup);
MarginContainer *popup_root = memnew(MarginContainer);
popup_root->add_theme_constant_override("margin_right", borders.width);
popup_root->add_theme_constant_override("margin_top", borders.height);
popup_root->add_theme_constant_override("margin_left", borders.width);
popup_root->add_theme_constant_override("margin_bottom", borders.height);
popup->add_child(popup_root);
VBoxContainer *vb = memnew(VBoxContainer);
vb->set_alignment(BoxContainer::ALIGNMENT_CENTER);
popup_root->add_child(vb);
Label *description = memnew(Label(TTR("Select Screen")));
description->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
vb->add_child(description);
screen_list = memnew(HBoxContainer);
screen_list->set_alignment(BoxContainer::ALIGNMENT_CENTER);
vb->add_child(screen_list);
popup_root->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
}

117
editor/gui/window_wrapper.h Normal file
View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* window_wrapper.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/math/rect2.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/menu_button.h"
class Window;
class HBoxContainer;
class WindowWrapper : public MarginContainer {
GDCLASS(WindowWrapper, MarginContainer);
Control *wrapped_control = nullptr;
MarginContainer *margins = nullptr;
Window *window = nullptr;
ObjectID window_id;
Panel *window_background = nullptr;
Ref<Shortcut> enable_shortcut;
bool override_close_request = false;
Rect2 _get_default_window_rect() const;
Node *_get_wrapped_control_parent() const;
void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect);
void _set_window_rect(const Rect2 p_rect);
void _window_size_changed();
void _window_close_request();
protected:
static void _bind_methods();
void _notification(int p_what);
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
void set_wrapped_control(Control *p_control, const Ref<Shortcut> &p_enable_shortcut = Ref<Shortcut>());
Control *get_wrapped_control() const;
Control *release_wrapped_control();
bool is_window_available() const;
bool get_window_enabled() const;
void set_window_enabled(bool p_enabled);
Rect2i get_window_rect() const;
int get_window_screen() const;
void restore_window(const Rect2i &p_rect, int p_screen = -1);
void restore_window_from_saved_position(const Rect2 p_window_rect, int p_screen, const Rect2 p_screen_rect);
void enable_window_on_screen(int p_screen = -1, bool p_auto_scale = false);
void set_window_title(const String &p_title);
void set_margins_enabled(bool p_enabled);
Size2 get_margins_size();
Size2 get_margins_top_left();
void grab_window_focus();
void set_override_close_request(bool p_enabled);
WindowWrapper();
~WindowWrapper();
};
class ScreenSelect : public Button {
GDCLASS(ScreenSelect, Button);
Popup *popup = nullptr;
HBoxContainer *screen_list = nullptr;
void _build_advanced_menu();
void _emit_screen_signal(int p_screen_idx);
void _handle_mouse_shortcut(const Ref<InputEvent> &p_event);
void _show_popup();
protected:
virtual void pressed() override;
static void _bind_methods();
void _notification(int p_what);
public:
ScreenSelect();
};