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

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

6
editor/gui/SCsub Normal file
View File

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

2018
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,938 @@
/**************************************************************************/
/* 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::for_inherit() {
allow_abstract_scripts = true;
}
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() || (!allow_abstract_scripts && 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;
if (!allow_abstract_scripts) {
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 && !(!allow_abstract_scripts && 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);
}

132
editor/gui/create_dialog.h Normal file
View File

@@ -0,0 +1,132 @@
/**************************************************************************/
/* 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;
bool allow_abstract_scripts = 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 = "");
void for_inherit();
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);
}

68
editor/gui/credits_roll.h Normal file
View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* credits_roll.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/popup.h"
class Label;
class VBoxContainer;
class Font;
class CreditsRoll : public Popup {
GDCLASS(CreditsRoll, Popup);
enum class LabelSize {
NORMAL,
HEADER,
BIG_HEADER,
};
int font_size_normal = 0;
int font_size_header = 0;
int font_size_big_header = 0;
Ref<Font> bold_font;
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);
public:
void roll_credits();
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

@@ -0,0 +1,69 @@
/**************************************************************************/
/* directory_create_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/dialogs.h"
class EditorValidationPanel;
class Label;
class LineEdit;
class DirectoryCreateDialog : public ConfirmationDialog {
GDCLASS(DirectoryCreateDialog, ConfirmationDialog);
public:
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));
}

75
editor/gui/editor_about.h Normal file
View File

@@ -0,0 +1,75 @@
/**************************************************************************/
/* editor_about.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/dialogs.h"
class CreditsRoll;
class ItemList;
class Label;
class RichTextLabel;
class TextureRect;
class Tree;
/**
* 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:
EditorAbout();
};

View File

@@ -0,0 +1,369 @@
/**************************************************************************/
/* editor_bottom_panel.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_bottom_panel.h"
#include "editor/debugger/editor_debugger_node.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"
#include "scene/gui/scroll_container.h"
#include "scene/gui/split_container.h"
void EditorBottomPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
if (is_layout_rtl()) {
bottom_hbox->move_child(left_button, button_scroll->get_index() + 1);
bottom_hbox->move_child(right_button, 0);
} else {
bottom_hbox->move_child(right_button, button_scroll->get_index() + 1);
bottom_hbox->move_child(left_button, 0);
}
} break;
}
}
void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock) {
for (int i = 0; i < items.size(); i++) {
if (items[i].control == p_control) {
_switch_to_item(p_visible, i, p_ignore_lock);
return;
}
}
}
void EditorBottomPanel::_scroll(bool p_right) {
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
h_scroll->set_value(p_right ? h_scroll->get_max() : 0);
} else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1));
} else {
h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1));
}
}
void EditorBottomPanel::_update_scroll_buttons() {
bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width;
left_button->set_visible(show_arrows);
right_button->set_visible(show_arrows);
if (show_arrows) {
_update_disabled_buttons();
}
}
void EditorBottomPanel::_update_disabled_buttons() {
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
left_button->set_disabled(h_scroll->get_value() == 0);
right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
}
void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].control->is_visible() == p_visible) {
return;
}
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);
if (p_visible) {
if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
return;
}
for (int i = 0; i < items.size(); i++) {
items[i].button->set_pressed_no_signal(i == p_idx);
items[i].control->set_visible(i == p_idx);
}
if (EditorDebuggerNode::get_singleton() == items[p_idx].control) {
// This is the debug panel which uses tabs, so the top section should be smaller.
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
}
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
center_split->set_collapsed(false);
pin_button->show();
expand_button->show();
if (expand_button->is_pressed()) {
EditorNode::get_top_split()->hide();
}
callable_mp(button_scroll, &ScrollContainer::ensure_control_visible).call_deferred(items[p_idx].button);
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
items[p_idx].button->set_pressed_no_signal(false);
items[p_idx].control->set_visible(false);
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
center_split->set_collapsed(true);
pin_button->hide();
expand_button->hide();
if (expand_button->is_pressed()) {
EditorNode::get_top_split()->show();
}
}
last_opened_control = items[p_idx].control;
}
void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
lock_panel_switching = p_pressed;
}
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
EditorNode::get_top_split()->set_visible(!p_pressed);
}
bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
if (!p_button->is_pressed()) {
_switch_by_control(true, p_control, true);
}
return false;
}
void EditorBottomPanel::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
int selected_item_idx = -1;
for (int i = 0; i < items.size(); i++) {
if (items[i].button->is_pressed()) {
selected_item_idx = i;
break;
}
}
if (selected_item_idx != -1) {
p_config_file->set_value(p_section, "selected_bottom_panel_item", selected_item_idx);
} else {
p_config_file->set_value(p_section, "selected_bottom_panel_item", Variant());
}
}
void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
bool has_active_tab = false;
if (p_config_file->has_section_key(p_section, "selected_bottom_panel_item")) {
int selected_item_idx = p_config_file->get_value(p_section, "selected_bottom_panel_item");
if (selected_item_idx >= 0 && selected_item_idx < items.size()) {
// Make sure we don't try to open contextual editors which are not enabled in the current context.
if (items[selected_item_idx].button->is_visible()) {
_switch_to_item(true, selected_item_idx);
has_active_tab = true;
}
}
}
// If there is no active tab we need to collapse the panel.
if (!has_active_tab) {
items[0].control->show(); // _switch_to_item() can collapse only visible tabs.
_switch_to_item(false, 0);
}
}
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
tb->set_theme_type_variation("BottomPanelButton");
tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item, true));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
tb->set_shortcut(p_shortcut);
tb->set_toggle_mode(true);
tb->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
item_vbox->add_child(p_item);
bottom_hbox->move_to_front();
button_hbox->add_child(tb);
if (p_at_front) {
button_hbox->move_child(tb, 0);
}
p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
p_item->hide();
BottomPanelItem bpi;
bpi.button = tb;
bpi.control = p_item;
bpi.name = p_text;
if (p_at_front) {
items.insert(0, bpi);
} else {
items.push_back(bpi);
}
return tb;
}
void EditorBottomPanel::remove_item(Control *p_item) {
bool was_visible = false;
for (int i = 0; i < items.size(); i++) {
if (items[i].control == p_item) {
if (p_item->is_visible_in_tree()) {
was_visible = true;
}
item_vbox->remove_child(items[i].control);
button_hbox->remove_child(items[i].button);
memdelete(items[i].button);
items.remove_at(i);
break;
}
}
if (was_visible) {
// Open the first panel to ensure that if the removed dock was visible, the bottom
// panel will not collapse.
_switch_to_item(true, 0, true);
} else if (last_opened_control == p_item) {
// When a dock is removed by plugins, it might not have been visible, and it
// might have been the last_opened_control. We need to make sure to reset the last opened control.
last_opened_control = items[0].control;
}
}
void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible, bool p_ignore_lock) {
_switch_by_control(p_visible, p_item, p_ignore_lock);
}
void EditorBottomPanel::move_item_to_end(Control *p_item) {
for (int i = 0; i < items.size(); i++) {
if (items[i].control == p_item) {
items[i].button->move_to_front();
SWAP(items.write[i], items.write[items.size() - 1]);
break;
}
}
}
void EditorBottomPanel::hide_bottom_panel() {
for (int i = 0; i < items.size(); i++) {
if (items[i].control->is_visible()) {
_switch_to_item(false, i);
break;
}
}
}
void EditorBottomPanel::toggle_last_opened_bottom_panel() {
// Select by control instead of index, so that the last bottom panel is opened correctly
// if it's been reordered since.
if (last_opened_control) {
_switch_by_control(!last_opened_control->is_visible(), last_opened_control, true);
} else {
// Open the first panel in the list if no panel was opened this session.
_switch_to_item(true, 0, true);
}
}
void EditorBottomPanel::set_expanded(bool p_expanded) {
expand_button->set_pressed(p_expanded);
}
EditorBottomPanel::EditorBottomPanel() {
item_vbox = memnew(VBoxContainer);
add_child(item_vbox);
bottom_hbox = memnew(HBoxContainer);
bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
item_vbox->add_child(bottom_hbox);
left_button = memnew(Button);
left_button->set_tooltip_text(TTRC("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page."));
left_button->set_accessibility_name(TTRC("Scroll Left"));
left_button->set_theme_type_variation("BottomPanelButton");
left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false));
bottom_hbox->add_child(left_button);
left_button->hide();
button_scroll = memnew(ScrollContainer);
button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL);
button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER);
button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED);
button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED);
bottom_hbox->add_child(button_scroll);
right_button = memnew(Button);
right_button->set_tooltip_text(TTRC("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page."));
right_button->set_accessibility_name(TTRC("Scroll Right"));
right_button->set_theme_type_variation("BottomPanelButton");
right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true));
bottom_hbox->add_child(right_button);
right_button->hide();
callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred();
button_hbox = memnew(HBoxContainer);
button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN);
button_scroll->add_child(button_hbox);
editor_toaster = memnew(EditorToaster);
bottom_hbox->add_child(editor_toaster);
EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_BASIC));
// Fade out the version label to be less prominent, but still readable.
version_btn->set_self_modulate(Color(1, 1, 1, 0.65));
version_btn->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
bottom_hbox->add_child(version_btn);
// Add a dummy control node for horizontal spacing.
Control *h_spacer = memnew(Control);
bottom_hbox->add_child(h_spacer);
pin_button = memnew(Button);
bottom_hbox->add_child(pin_button);
pin_button->hide();
pin_button->set_theme_type_variation("FlatMenuButton");
pin_button->set_toggle_mode(true);
pin_button->set_tooltip_text(TTRC("Pin Bottom Panel Switching"));
pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
expand_button = memnew(Button);
bottom_hbox->add_child(expand_button);
expand_button->hide();
expand_button->set_theme_type_variation("FlatMenuButton");
expand_button->set_toggle_mode(true);
expand_button->set_accessibility_name(TTRC("Expand Bottom Panel"));
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTRC("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
}

View File

@@ -0,0 +1,91 @@
/**************************************************************************/
/* editor_bottom_panel.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/panel_container.h"
class Button;
class ConfigFile;
class EditorToaster;
class HBoxContainer;
class VBoxContainer;
class ScrollContainer;
class EditorBottomPanel : public PanelContainer {
GDCLASS(EditorBottomPanel, PanelContainer);
struct BottomPanelItem {
String name;
Control *control = nullptr;
Button *button = nullptr;
};
Vector<BottomPanelItem> items;
bool lock_panel_switching = false;
VBoxContainer *item_vbox = nullptr;
HBoxContainer *bottom_hbox = nullptr;
Button *left_button = nullptr;
Button *right_button = nullptr;
ScrollContainer *button_scroll = nullptr;
HBoxContainer *button_hbox = nullptr;
EditorToaster *editor_toaster = nullptr;
Button *pin_button = nullptr;
Button *expand_button = nullptr;
Control *last_opened_control = nullptr;
void _switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock = false);
void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false);
void _pin_button_toggled(bool p_pressed);
void _expand_button_toggled(bool p_pressed);
void _scroll(bool p_right);
void _update_scroll_buttons();
void _update_disabled_buttons();
bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);
protected:
void _notification(int p_what);
public:
void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
Button *add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut = nullptr, bool p_at_front = false);
void remove_item(Control *p_item);
void make_item_visible(Control *p_item, bool p_visible = true, bool p_ignore_lock = false);
void move_item_to_end(Control *p_item);
void hide_bottom_panel();
void toggle_last_opened_bottom_panel();
void set_expanded(bool p_expanded);
EditorBottomPanel();
};

View File

@@ -0,0 +1,234 @@
/**************************************************************************/
/* editor_dir_dialog.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_dir_dialog.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"
#include "servers/display_server.h"
void EditorDirDialog::_update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) {
updating = true;
const String path = p_dir->get_path();
p_item->set_metadata(0, path);
p_item->set_icon(0, tree->get_editor_theme_icon(SNAME("Folder")));
if (!p_item->get_parent()) {
p_item->set_text(0, "res://");
p_item->set_icon_modulate(0, p_default_folder_color);
} else {
if (!opened_paths.has(path) && (p_select_path.is_empty() || !p_select_path.begins_with(path))) {
p_item->set_collapsed(true);
}
p_item->set_text(0, p_dir->get_name());
if (p_assigned_folder_colors.has(path)) {
const Color &folder_color = p_folder_colors[p_assigned_folder_colors[path]];
p_item->set_icon_modulate(0, p_is_dark_theme ? folder_color : folder_color * FileSystemDock::ITEM_COLOR_SCALE);
p_item->set_custom_bg_color(0, Color(folder_color, p_is_dark_theme ? FileSystemDock::ITEM_ALPHA_MIN : FileSystemDock::ITEM_ALPHA_MAX));
} else {
TreeItem *parent_item = p_item->get_parent();
Color parent_bg_color = parent_item->get_custom_bg_color(0);
if (parent_bg_color != Color()) {
p_item->set_custom_bg_color(0, p_assigned_folder_colors.has(parent_item->get_metadata(0)) ? parent_bg_color.darkened(FileSystemDock::ITEM_BG_DARK_SCALE) : parent_bg_color);
p_item->set_icon_modulate(0, parent_item->get_icon_modulate(0));
} else {
p_item->set_icon_modulate(0, p_default_folder_color);
}
}
}
if (path == new_dir_path || !p_item->get_parent()) {
p_item->select(0);
}
updating = false;
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
TreeItem *ti = tree->create_item(p_item);
_update_dir(p_default_folder_color, p_assigned_folder_colors, p_folder_colors, p_is_dark_theme, ti, p_dir->get_subdir(i));
}
}
void EditorDirDialog::config(const Vector<String> &p_paths) {
ERR_FAIL_COND(p_paths.is_empty());
if (p_paths.size() == 1) {
String path = p_paths[0];
if (path.ends_with("/")) {
path = path.substr(0, path.length() - 1);
}
// TRANSLATORS: %s is the file name that will be moved or duplicated.
set_title(vformat(TTR("Move/Duplicate: %s"), path.get_file()));
} else {
// TRANSLATORS: %d is the number of files that will be moved or duplicated.
set_title(vformat(TTRN("Move/Duplicate %d Item", "Move/Duplicate %d Items", p_paths.size()), p_paths.size()));
}
}
void EditorDirDialog::reload(const String &p_path) {
if (!is_visible()) {
must_reload = true;
return;
}
tree->clear();
TreeItem *root = tree->create_item();
_update_dir(tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")), FileSystemDock::get_singleton()->get_assigned_folder_colors(), FileSystemDock::get_singleton()->get_folder_colors(), EditorThemeManager::is_dark_theme(), root, EditorFileSystem::get_singleton()->get_filesystem(), p_path);
_item_collapsed(root);
new_dir_path.clear();
must_reload = false;
}
void EditorDirDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
reload();
} break;
case NOTIFICATION_EXIT_TREE: {
EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload));
FileSystemDock::get_singleton()->disconnect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload));
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (must_reload && is_visible()) {
reload();
}
} break;
}
}
void EditorDirDialog::_item_collapsed(Object *p_item) {
TreeItem *item = Object::cast_to<TreeItem>(p_item);
if (updating) {
return;
}
if (item->is_collapsed()) {
opened_paths.erase(item->get_metadata(0));
} else {
opened_paths.insert(item->get_metadata(0));
}
}
void EditorDirDialog::_item_activated() {
TreeItem *ti = tree->get_selected();
ERR_FAIL_NULL(ti);
if (ti->get_child_count() > 0) {
ti->set_collapsed(!ti->is_collapsed());
}
}
void EditorDirDialog::_copy_pressed() {
TreeItem *ti = tree->get_selected();
ERR_FAIL_NULL(ti);
hide();
emit_signal(SNAME("copy_pressed"), ti->get_metadata(0));
}
void EditorDirDialog::ok_pressed() {
TreeItem *ti = tree->get_selected();
ERR_FAIL_NULL(ti);
hide();
emit_signal(SNAME("move_pressed"), ti->get_metadata(0));
}
void EditorDirDialog::_make_dir() {
TreeItem *ti = tree->get_selected();
ERR_FAIL_NULL(ti);
const String &directory = ti->get_metadata(0);
makedialog->config(directory, callable_mp(this, &EditorDirDialog::_make_dir_confirm).bind(directory), DirectoryCreateDialog::MODE_DIRECTORY, "new folder");
makedialog->popup_centered();
}
void EditorDirDialog::_make_dir_confirm(const String &p_path, const String &p_base_dir) {
FileSystemDock::get_singleton()->create_directory(p_path, p_base_dir);
// Multiple level of directories can be created at once.
String base_dir = p_path.get_base_dir();
while (true) {
opened_paths.insert(base_dir + "/");
if (base_dir == "res://") {
break;
}
base_dir = base_dir.get_base_dir();
}
new_dir_path = p_path + "/";
}
void EditorDirDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("copy_pressed", PropertyInfo(Variant::STRING, "dir")));
ADD_SIGNAL(MethodInfo("move_pressed", PropertyInfo(Variant::STRING, "dir")));
}
EditorDirDialog::EditorDirDialog() {
set_hide_on_ok(false);
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
HBoxContainer *hb = memnew(HBoxContainer);
vb->add_child(hb);
hb->add_child(memnew(Label(TTR("Choose target directory:"))));
hb->add_spacer();
makedir = memnew(Button(TTR("Create Folder")));
hb->add_child(makedir);
makedir->connect(SceneStringName(pressed), callable_mp(this, &EditorDirDialog::_make_dir));
tree = memnew(Tree);
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vb->add_child(tree);
tree->connect("item_activated", callable_mp(this, &EditorDirDialog::_item_activated));
tree->connect("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed), CONNECT_DEFERRED);
set_ok_button_text(TTR("Move"));
copy = add_button(TTR("Copy"), !DisplayServer::get_singleton()->get_swap_cancel_ok());
copy->connect(SceneStringName(pressed), callable_mp(this, &EditorDirDialog::_copy_pressed));
makedialog = memnew(DirectoryCreateDialog);
add_child(makedialog);
}

View File

@@ -0,0 +1,74 @@
/**************************************************************************/
/* editor_dir_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/dialogs.h"
class DirectoryCreateDialog;
class EditorFileSystemDirectory;
class Tree;
class TreeItem;
class EditorDirDialog : public ConfirmationDialog {
GDCLASS(EditorDirDialog, ConfirmationDialog);
DirectoryCreateDialog *makedialog = nullptr;
Button *makedir = nullptr;
Button *copy = nullptr;
HashSet<String> opened_paths;
String new_dir_path;
Tree *tree = nullptr;
bool updating = false;
void _item_collapsed(Object *p_item);
void _item_activated();
void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String());
void _make_dir();
void _make_dir_confirm(const String &p_path, const String &p_base_dir);
void _copy_pressed();
void ok_pressed() override;
bool must_reload = false;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void config(const Vector<String> &p_paths);
void reload(const String &p_path = "");
EditorDirDialog();
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,377 @@
/**************************************************************************/
/* editor_file_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 "core/io/dir_access.h"
#include "editor/file_system/file_info.h"
#include "scene/gui/dialogs.h"
#include "scene/property_list_helper.h"
class DependencyRemoveDialog;
class GridContainer;
class HSplitContainer;
class HFlowContainer;
class ItemList;
class MenuButton;
class OptionButton;
class PopupMenu;
class TextureRect;
class VSeparator;
class EditorFileDialog : public ConfirmationDialog {
GDCLASS(EditorFileDialog, ConfirmationDialog);
public:
enum DisplayMode {
DISPLAY_THUMBNAILS,
DISPLAY_LIST
};
enum Access {
ACCESS_RESOURCES,
ACCESS_USERDATA,
ACCESS_FILESYSTEM
};
enum FileMode {
FILE_MODE_OPEN_FILE,
FILE_MODE_OPEN_FILES,
FILE_MODE_OPEN_DIR,
FILE_MODE_OPEN_ANY,
FILE_MODE_SAVE_FILE
};
typedef Ref<Texture2D> (*GetIconFunc)(const String &);
typedef void (*RegisterFunc)(EditorFileDialog *);
static inline GetIconFunc get_icon_func = nullptr;
static inline GetIconFunc get_thumbnail_func = nullptr;
static inline RegisterFunc register_func = nullptr;
static inline RegisterFunc unregister_func = nullptr;
private:
enum ItemMenu {
ITEM_MENU_COPY_PATH,
ITEM_MENU_DELETE,
ITEM_MENU_REFRESH,
ITEM_MENU_NEW_FOLDER,
ITEM_MENU_SHOW_IN_EXPLORER,
ITEM_MENU_SHOW_BUNDLE_CONTENT,
};
ConfirmationDialog *makedialog = nullptr;
LineEdit *makedirname = nullptr;
VSeparator *makedir_sep = nullptr;
Button *makedir = nullptr;
Access access = ACCESS_RESOURCES;
HFlowContainer *flow_checkbox_options = nullptr;
GridContainer *grid_select_options = nullptr;
VBoxContainer *vbox = nullptr;
FileMode mode = FILE_MODE_SAVE_FILE;
bool can_create_dir = false;
LineEdit *dir = nullptr;
Button *dir_prev = nullptr;
Button *dir_next = nullptr;
Button *dir_up = nullptr;
HBoxContainer *drives_container = nullptr;
HBoxContainer *shortcuts_container = nullptr;
OptionButton *drives = nullptr;
ItemList *item_list = nullptr;
PopupMenu *item_menu = nullptr;
TextureRect *preview = nullptr;
VBoxContainer *preview_vb = nullptr;
HSplitContainer *body_hsplit = nullptr;
HSplitContainer *list_hb = nullptr;
HBoxContainer *file_box = nullptr;
LineEdit *file = nullptr;
OptionButton *filter = nullptr;
AcceptDialog *error_dialog = nullptr;
Ref<DirAccess> dir_access;
ConfirmationDialog *confirm_save = nullptr;
DependencyRemoveDialog *dep_remove_dialog = nullptr;
ConfirmationDialog *global_remove_dialog = nullptr;
VBoxContainer *side_vbox = nullptr;
VBoxContainer *vbc = nullptr;
HBoxContainer *pathhb = nullptr;
Button *mode_thumbnails = nullptr;
Button *mode_list = nullptr;
Button *refresh = nullptr;
Button *favorite = nullptr;
Button *show_hidden = nullptr;
Button *show_search_filter_button = nullptr;
String search_string;
bool show_search_filter = false;
HBoxContainer *filter_hb = nullptr;
LineEdit *filter_box = nullptr;
FileSortOption file_sort = FileSortOption::FILE_SORT_NAME;
MenuButton *file_sort_button = nullptr;
Button *fav_up = nullptr;
Button *fav_down = nullptr;
ItemList *favorites = nullptr;
ItemList *recent = nullptr;
Vector<String> local_history;
int local_history_pos = 0;
void _push_history();
Vector<String> filters;
Vector<String> processed_filters;
bool previews_enabled = true;
bool preview_waiting = false;
int preview_wheel_index = 0;
float preview_wheel_timeout = 0.0f;
static inline bool default_show_hidden_files = false;
static inline DisplayMode default_display_mode = DISPLAY_THUMBNAILS;
bool show_hidden_files;
DisplayMode display_mode;
bool disable_overwrite_warning = false;
bool is_invalidating = false;
struct ThemeCache {
Ref<Texture2D> parent_folder;
Ref<Texture2D> forward_folder;
Ref<Texture2D> back_folder;
Ref<Texture2D> open_folder;
Ref<Texture2D> reload;
Ref<Texture2D> toggle_hidden;
Ref<Texture2D> toggle_filename_filter;
Ref<Texture2D> favorite;
Ref<Texture2D> mode_thumbnails;
Ref<Texture2D> mode_list;
Ref<Texture2D> create_folder;
Ref<Texture2D> favorites_up;
Ref<Texture2D> favorites_down;
Ref<Texture2D> filter_box;
Ref<Texture2D> file_sort_button;
Ref<Texture2D> folder;
Color folder_icon_color;
Ref<Texture2D> action_copy;
Ref<Texture2D> action_delete;
Ref<Texture2D> filesystem;
Ref<Texture2D> folder_medium_thumbnail;
Ref<Texture2D> file_medium_thumbnail;
Ref<Texture2D> folder_big_thumbnail;
Ref<Texture2D> file_big_thumbnail;
Ref<Texture2D> progress[8]{};
} theme_cache;
struct Option {
String name;
Vector<String> values;
int default_idx = 0;
};
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
Vector<Option> options;
Dictionary selected_options;
bool options_dirty = false;
String full_dir;
void update_dir();
void update_file_name();
void update_file_list();
void update_search_filter_gui();
void update_filters();
void _focus_file_text();
void _update_favorites();
void _favorite_pressed();
void _favorite_selected(int p_idx);
void _favorite_move_up();
void _favorite_move_down();
void _update_recent();
void _recent_selected(int p_idx);
void _item_selected(int p_item);
void _multi_selected(int p_item, bool p_selected);
void _items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index);
void _item_dc_selected(int p_item);
void _item_list_item_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index);
void _item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
void _item_menu_id_pressed(int p_option);
void _select_drive(int p_idx);
void _dir_submitted(const String &p_dir);
void _action_pressed();
void _save_confirm_pressed();
void _cancel_pressed();
void _filter_selected(int);
void _make_dir();
void _make_dir_confirm();
void _focus_filter_box();
void _filter_changed(const String &p_text);
void _search_filter_selected();
void _file_sort_popup(int p_id);
void _delete_items();
void _delete_files_global();
void _update_drives(bool p_select = true);
void _update_icons();
void _go_up();
void _go_back();
void _go_forward();
void _invalidate();
virtual void _post_popup() override;
void _save_to_recent();
// Callback function is callback(String p_path,Ref<Texture2D> preview,Variant udata) preview null if could not load.
void _thumbnail_result(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
void _thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
void _request_single_thumbnail(const String &p_path);
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
bool _is_open_should_be_disabled();
void _update_side_menu_visibility(bool p_native_dlg);
void _native_popup();
void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
TypedArray<Dictionary> _get_options() const;
void _update_option_controls();
void _option_changed_checkbox_toggled(bool p_pressed, const String &p_name);
void _option_changed_item_selected(int p_idx, const String &p_name);
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
virtual void set_visible(bool p_visible) override;
virtual void popup(const Rect2i &p_rect = Rect2i()) override;
// Public for use with callable_mp.
void _file_submitted(const String &p_file);
void popup_file_dialog();
void clear_filters();
void add_filter(const String &p_filter, const String &p_description = "");
void set_filters(const Vector<String> &p_filters);
Vector<String> get_filters() const;
void clear_search_filter();
void set_search_filter(const String &p_search_filter);
String get_search_filter() const;
void set_enable_multiple_selection(bool p_enable);
Vector<String> get_selected_files() const;
String get_current_dir() const;
String get_current_file() const;
String get_current_path() const;
void set_current_dir(const String &p_dir);
void set_current_file(const String &p_file);
void set_current_path(const String &p_path);
String get_option_name(int p_option) const;
Vector<String> get_option_values(int p_option) const;
int get_option_default(int p_option) const;
void set_option_name(int p_option, const String &p_name);
void set_option_values(int p_option, const Vector<String> &p_values);
void set_option_default(int p_option, int p_index);
void add_option(const String &p_name, const Vector<String> &p_values, int p_index);
void set_option_count(int p_count);
int get_option_count() const;
Dictionary get_selected_options() const;
void set_display_mode(DisplayMode p_mode);
DisplayMode get_display_mode() const;
void set_file_mode(FileMode p_mode);
FileMode get_file_mode() const;
VBoxContainer *get_vbox();
LineEdit *get_line_edit() { return file; }
void set_access(Access p_access);
Access get_access() const;
static void set_default_show_hidden_files(bool p_show);
static void set_default_display_mode(DisplayMode p_mode);
void set_show_hidden_files(bool p_show);
void set_show_search_filter(bool p_show);
bool is_showing_hidden_files() const;
void invalidate();
void set_disable_overwrite_warning(bool p_disable);
bool is_overwrite_warning_disabled() const;
void set_previews_enabled(bool p_enabled);
bool are_previews_enabled();
void add_side_menu(Control *p_menu, const String &p_title = "");
EditorFileDialog();
~EditorFileDialog();
};
VARIANT_ENUM_CAST(EditorFileDialog::FileMode);
VARIANT_ENUM_CAST(EditorFileDialog::Access);
VARIANT_ENUM_CAST(EditorFileDialog::DisplayMode);

View File

@@ -0,0 +1,244 @@
/**************************************************************************/
/* editor_object_selector.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_object_selector.h"
#include "editor/editor_data.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/margin_container.h"
Size2 EditorObjectSelector::get_minimum_size() const {
Ref<Font> font = get_theme_font(SceneStringName(font));
int font_size = get_theme_font_size(SceneStringName(font_size));
return Button::get_minimum_size() + Size2(0, font->get_height(font_size));
}
void EditorObjectSelector::_add_children_to_popup(Object *p_obj, int p_depth) {
if (p_depth > 8) {
return;
}
List<PropertyInfo> pinfo;
p_obj->get_property_list(&pinfo);
for (const PropertyInfo &E : pinfo) {
if (!(E.usage & PROPERTY_USAGE_EDITOR)) {
continue;
}
if (E.hint != PROPERTY_HINT_RESOURCE_TYPE) {
continue;
}
Variant value = p_obj->get(E.name);
if (value.get_type() != Variant::OBJECT) {
continue;
}
Object *obj = value;
if (!obj) {
continue;
}
Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
String proper_name = "";
Vector<String> name_parts = E.name.split("/");
for (int i = 0; i < name_parts.size(); i++) {
if (i > 0) {
proper_name += " > ";
}
proper_name += name_parts[i].capitalize();
}
int index = sub_objects_menu->get_item_count();
sub_objects_menu->add_icon_item(obj_icon, proper_name, objects.size());
sub_objects_menu->set_item_indent(index, p_depth);
objects.push_back(obj->get_instance_id());
_add_children_to_popup(obj, p_depth + 1);
}
}
void EditorObjectSelector::_show_popup() {
if (sub_objects_menu->is_visible()) {
sub_objects_menu->hide();
return;
}
sub_objects_menu->clear();
Size2 size = get_size();
Point2 gp = get_screen_position();
gp.y += size.y;
sub_objects_menu->popup(Rect2(gp, Size2(size.width, 0)));
}
void EditorObjectSelector::_about_to_show() {
Object *obj = ObjectDB::get_instance(history->get_path_object(history->get_path_size() - 1));
if (!obj) {
return;
}
objects.clear();
_add_children_to_popup(obj);
if (sub_objects_menu->get_item_count() == 0) {
sub_objects_menu->add_item(TTR("No sub-resources found."));
sub_objects_menu->set_item_disabled(0, true);
}
}
void EditorObjectSelector::update_path() {
for (int i = 0; i < history->get_path_size(); i++) {
Object *obj = ObjectDB::get_instance(history->get_path_object(i));
if (!obj) {
continue;
}
Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
if (obj_icon.is_valid()) {
current_object_icon->set_texture(obj_icon);
}
if (i == history->get_path_size() - 1) {
String name;
if (obj->has_method("_get_editor_name")) {
name = obj->call("_get_editor_name");
} else if (Object::cast_to<Resource>(obj)) {
Resource *r = Object::cast_to<Resource>(obj);
if (r->get_path().is_resource_file()) {
name = r->get_path().get_file();
} else {
name = r->get_name();
}
if (name.is_empty()) {
name = r->get_class();
}
} else if (obj->is_class("EditorDebuggerRemoteObjects")) {
name = obj->call("get_title");
} else if (Object::cast_to<Node>(obj)) {
name = Object::cast_to<Node>(obj)->get_name();
} else if (Object::cast_to<Resource>(obj) && !Object::cast_to<Resource>(obj)->get_name().is_empty()) {
name = Object::cast_to<Resource>(obj)->get_name();
} else {
name = obj->get_class();
}
current_object_label->set_text(name);
set_tooltip_text(obj->get_class());
}
}
}
void EditorObjectSelector::clear_path() {
set_disabled(true);
set_tooltip_text("");
current_object_label->set_text("");
current_object_icon->set_texture(nullptr);
sub_objects_icon->hide();
}
void EditorObjectSelector::enable_path() {
set_disabled(false);
sub_objects_icon->show();
}
void EditorObjectSelector::_id_pressed(int p_idx) {
ERR_FAIL_INDEX(p_idx, objects.size());
Object *obj = ObjectDB::get_instance(objects[p_idx]);
if (!obj) {
return;
}
EditorNode::get_singleton()->push_item(obj);
}
void EditorObjectSelector::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
update_path();
int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
current_object_icon->set_custom_minimum_size(Size2(icon_size, icon_size));
current_object_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("main"), EditorStringName(EditorFonts)));
sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton")));
sub_objects_menu->add_theme_constant_override("icon_max_width", icon_size);
} break;
case NOTIFICATION_READY: {
connect(SceneStringName(pressed), callable_mp(this, &EditorObjectSelector::_show_popup));
} break;
}
}
EditorObjectSelector::EditorObjectSelector(EditorSelectionHistory *p_history) {
history = p_history;
MarginContainer *main_mc = memnew(MarginContainer);
main_mc->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
main_mc->add_theme_constant_override("margin_left", 4 * EDSCALE);
main_mc->add_theme_constant_override("margin_right", 6 * EDSCALE);
add_child(main_mc);
HBoxContainer *main_hb = memnew(HBoxContainer);
main_mc->add_child(main_hb);
current_object_icon = memnew(TextureRect);
current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
current_object_icon->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
main_hb->add_child(current_object_icon);
current_object_label = memnew(Label);
current_object_label->set_focus_mode(FOCUS_ACCESSIBILITY);
current_object_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
current_object_label->set_h_size_flags(SIZE_EXPAND_FILL);
current_object_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
current_object_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
main_hb->add_child(current_object_label);
sub_objects_icon = memnew(TextureRect);
sub_objects_icon->hide();
sub_objects_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
main_hb->add_child(sub_objects_icon);
sub_objects_menu = memnew(PopupMenu);
sub_objects_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
add_child(sub_objects_menu);
sub_objects_menu->connect("about_to_popup", callable_mp(this, &EditorObjectSelector::_about_to_show));
sub_objects_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorObjectSelector::_id_pressed));
set_tooltip_text(TTR("Open a list of sub-resources."));
}

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* editor_object_selector.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/texture_rect.h"
class EditorSelectionHistory;
class EditorObjectSelector : public Button {
GDCLASS(EditorObjectSelector, Button);
EditorSelectionHistory *history = nullptr;
TextureRect *current_object_icon = nullptr;
Label *current_object_label = nullptr;
TextureRect *sub_objects_icon = nullptr;
PopupMenu *sub_objects_menu = nullptr;
Vector<ObjectID> objects;
void _show_popup();
void _id_pressed(int p_idx);
void _about_to_show();
void _add_children_to_popup(Object *p_obj, int p_depth = 0);
protected:
void _notification(int p_what);
public:
virtual Size2 get_minimum_size() const override;
void update_path();
void clear_path();
void enable_path();
EditorObjectSelector(EditorSelectionHistory *p_history);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,270 @@
/**************************************************************************/
/* editor_quick_open_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 "core/templates/a_hash_map.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/margin_container.h"
class Button;
class CenterContainer;
class CheckButton;
class ConfigFile;
class EditorFileSystemDirectory;
class LineEdit;
class HFlowContainer;
class MarginContainer;
class PanelContainer;
class PopupMenu;
class ScrollContainer;
class StringName;
class Texture2D;
class TextureRect;
class VBoxContainer;
class FuzzySearchResult;
class QuickOpenResultItem;
enum class QuickOpenDisplayMode {
GRID,
LIST,
};
struct QuickOpenResultCandidate {
String file_path;
Ref<Texture2D> thumbnail;
const FuzzySearchResult *result = nullptr;
};
class HighlightedLabel : public Label {
GDCLASS(HighlightedLabel, Label)
Vector<Vector2i> highlights;
void draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing);
public:
void add_highlight(const Vector2i &p_interval);
void reset_highlights();
protected:
void _notification(int p_notification);
};
class QuickOpenResultContainer : public VBoxContainer {
GDCLASS(QuickOpenResultContainer, VBoxContainer)
enum {
FILE_SHOW_IN_FILESYSTEM,
FILE_SHOW_IN_FILE_MANAGER
};
public:
void init(const Vector<StringName> &p_base_types);
void handle_search_box_input(const Ref<InputEvent> &p_ie);
void set_query_and_update(const String &p_query);
void update_results();
bool has_nothing_selected() const;
String get_selected() const;
void save_selected_item();
void cleanup();
QuickOpenResultContainer();
protected:
void _notification(int p_what);
private:
static constexpr int MAX_HISTORY_SIZE = 20;
Vector<FuzzySearchResult> search_results;
Vector<StringName> base_types;
Vector<String> filepaths;
AHashMap<String, StringName> filetypes;
Vector<QuickOpenResultCandidate> candidates;
AHashMap<StringName, Vector<QuickOpenResultCandidate>> selected_history;
HashSet<String> history_set;
String query;
int selection_index = -1;
int num_visible_results = 0;
int max_total_results = 0;
bool never_opened = true;
Ref<ConfigFile> history_file;
QuickOpenDisplayMode content_display_mode = QuickOpenDisplayMode::LIST;
Vector<QuickOpenResultItem *> result_items;
ScrollContainer *scroll_container = nullptr;
VBoxContainer *list = nullptr;
HFlowContainer *grid = nullptr;
PopupMenu *file_context_menu = nullptr;
PanelContainer *panel_container = nullptr;
CenterContainer *no_results_container = nullptr;
Label *no_results_label = nullptr;
Label *file_details_path = nullptr;
Button *display_mode_toggle = nullptr;
CheckButton *include_addons_toggle = nullptr;
CheckButton *fuzzy_search_toggle = nullptr;
AHashMap<StringName, Ref<Texture2D>> file_type_icons;
static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
void _ensure_result_vector_capacity();
void _sort_filepaths(int p_max_results);
void _create_initial_results();
void _find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
Vector<QuickOpenResultCandidate> *_get_history();
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath);
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result);
void _update_fuzzy_search_results();
void _use_default_candidates();
void _score_and_sort_candidates();
void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
void _move_selection_index(Key p_key);
void _select_item(int p_index);
void _item_input(const Ref<InputEvent> &p_ev, int p_index);
CanvasItem *_get_result_root();
void _layout_result_item(QuickOpenResultItem *p_item);
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
void _toggle_display_mode();
void _toggle_include_addons(bool p_pressed);
void _toggle_fuzzy_search(bool p_pressed);
void _menu_option(int p_option);
String _get_cache_file_path() const;
static void _bind_methods();
};
class QuickOpenResultGridItem : public MarginContainer {
GDCLASS(QuickOpenResultGridItem, MarginContainer)
public:
QuickOpenResultGridItem();
void reset();
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
void highlight_item(const Color &p_color);
void remove_highlight();
private:
VBoxContainer *vbc = nullptr;
TextureRect *thumbnail = nullptr;
HighlightedLabel *name = nullptr;
};
class QuickOpenResultListItem : public MarginContainer {
GDCLASS(QuickOpenResultListItem, MarginContainer)
public:
QuickOpenResultListItem();
void reset();
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
void highlight_item(const Color &p_color);
void remove_highlight();
protected:
void _notification(int p_what);
private:
HBoxContainer *hbc = nullptr;
VBoxContainer *text_container = nullptr;
TextureRect *thumbnail = nullptr;
HighlightedLabel *name = nullptr;
HighlightedLabel *path = nullptr;
};
class QuickOpenResultItem : public HBoxContainer {
GDCLASS(QuickOpenResultItem, HBoxContainer)
public:
QuickOpenResultItem();
bool enable_highlights = true;
void reset();
void set_content(const QuickOpenResultCandidate &p_candidate);
void set_display_mode(QuickOpenDisplayMode p_display_mode);
void highlight_item(bool p_enabled);
protected:
void _notification(int p_what);
private:
QuickOpenResultListItem *list_item = nullptr;
QuickOpenResultGridItem *grid_item = nullptr;
Ref<StyleBox> selected_stylebox;
Ref<StyleBox> hovering_stylebox;
Color highlighted_font_color;
bool is_hovering = false;
bool is_selected = false;
void _set_enabled(bool p_enabled);
};
class EditorQuickOpenDialog : public AcceptDialog {
GDCLASS(EditorQuickOpenDialog, AcceptDialog);
public:
void popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback);
EditorQuickOpenDialog();
protected:
virtual void cancel_pressed() override;
virtual void ok_pressed() override;
private:
static String get_dialog_title(const Vector<StringName> &p_base_types);
LineEdit *search_box = nullptr;
QuickOpenResultContainer *container = nullptr;
Callable item_selected_callback;
void _search_box_text_changed(const String &p_query);
};

View File

@@ -0,0 +1,761 @@
/**************************************************************************/
/* editor_spin_slider.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_spin_slider.h"
#include "core/input/input.h"
#include "core/math/expression.h"
#include "core/os/keyboard.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/theme/theme_db.h"
String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const {
if (!read_only && grabber->is_visible()) {
Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
return TS->format_number(rtos(get_value())) + "\n\n" + vformat(TTR("Hold %s to round to integers.\nHold Shift for more precise changes."), find_keycode_name(key));
}
return TS->format_number(rtos(get_value()));
}
String EditorSpinSlider::get_text_value() const {
return TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step())));
}
void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (read_only) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
if (updown_offset != -1 && ((!is_layout_rtl() && mb->get_position().x > updown_offset) || (is_layout_rtl() && mb->get_position().x < updown_offset))) {
// Updown pressed.
if (mb->get_position().y < get_size().height / 2) {
set_value(get_value() + get_step());
} else {
set_value(get_value() - get_step());
}
emit_signal("updown_pressed");
return;
}
_grab_start();
} else {
_grab_end();
}
} else if (mb->get_button_index() == MouseButton::RIGHT) {
if (mb->is_pressed() && is_grabbing()) {
_grab_end();
set_value(pre_grab_value);
}
} else if (mb->get_button_index() == MouseButton::WHEEL_UP || mb->get_button_index() == MouseButton::WHEEL_DOWN) {
if (grabber->is_visible()) {
callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw).call_deferred();
}
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (grabbing_spinner_attempt) {
double diff_x = mm->get_relative().x;
if (mm->is_shift_pressed() && grabbing_spinner) {
diff_x *= 0.1;
}
grabbing_spinner_dist_cache += diff_x * grabbing_spinner_speed;
if (!grabbing_spinner && Math::abs(grabbing_spinner_dist_cache) > 4 * grabbing_spinner_speed * EDSCALE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
grabbing_spinner = true;
}
if (grabbing_spinner) {
// Don't make the user scroll all the way back to 'in range' if they went off the end.
if (pre_grab_value < get_min() && !is_lesser_allowed()) {
pre_grab_value = get_min();
}
if (pre_grab_value > get_max() && !is_greater_allowed()) {
pre_grab_value = get_max();
}
if (mm->is_command_or_control_pressed()) {
// If control was just pressed, don't make the value do a huge jump in magnitude.
if (grabbing_spinner_dist_cache != 0) {
pre_grab_value += grabbing_spinner_dist_cache * get_step();
grabbing_spinner_dist_cache = 0;
}
set_value(Math::round(pre_grab_value + get_step() * grabbing_spinner_dist_cache * 10));
} else {
set_value(pre_grab_value + get_step() * grabbing_spinner_dist_cache);
}
}
} else if (updown_offset != -1) {
bool new_hover = (!is_layout_rtl() && mm->get_position().x > updown_offset) || (is_layout_rtl() && mm->get_position().x < updown_offset);
if (new_hover != hover_updown) {
hover_updown = new_hover;
queue_redraw();
}
}
}
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
if (k->is_action("ui_accept", true)) {
_focus_entered();
} else if (is_grabbing()) {
if (k->is_action("ui_cancel", true)) {
_grab_end();
set_value(pre_grab_value);
}
accept_event();
}
}
}
void EditorSpinSlider::_grab_start() {
grabbing_spinner_attempt = true;
grabbing_spinner_dist_cache = 0;
pre_grab_value = get_value();
grabbing_spinner = false;
grabbing_spinner_mouse_pos = get_global_mouse_position();
emit_signal("grabbed");
}
void EditorSpinSlider::_grab_end() {
if (grabbing_spinner_attempt) {
if (grabbing_spinner) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
queue_redraw();
grabbing_spinner = false;
emit_signal("ungrabbed");
} else {
_focus_entered();
}
grabbing_spinner_attempt = false;
}
if (grabbing_grabber) {
grabbing_grabber = false;
mousewheel_over_grabber = false;
emit_signal("ungrabbed");
}
}
void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
if (read_only) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
if (grabbing_grabber) {
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
set_value(get_value() + get_step());
mousewheel_over_grabber = true;
accept_event();
} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
set_value(get_value() - get_step());
mousewheel_over_grabber = true;
accept_event();
}
}
}
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
grabbing_grabber = true;
pre_grab_value = get_value();
if (!mousewheel_over_grabber) {
grabbing_ratio = get_as_ratio();
grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
}
grab_focus();
emit_signal("grabbed");
} else {
grabbing_grabber = false;
mousewheel_over_grabber = false;
emit_signal("ungrabbed");
}
} else if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT) {
if (mb->is_pressed() && grabbing_grabber) {
grabbing_grabber = false;
mousewheel_over_grabber = false;
set_value(pre_grab_value);
emit_signal("ungrabbed");
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && grabbing_grabber) {
if (mousewheel_over_grabber) {
return;
}
float scale_x = get_global_transform_with_canvas().get_scale().x;
ERR_FAIL_COND(Math::is_zero_approx(scale_x));
float grabbing_ofs = (grabber->get_transform().xform(mm->get_position()).x - grabbing_from) / float(grabber_range) / scale_x;
set_as_ratio(grabbing_ratio + grabbing_ofs);
queue_redraw();
}
}
void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed() && !read_only) {
Key code = k->get_keycode();
switch (code) {
case Key::UP:
case Key::DOWN: {
double step = get_step();
if (step < 1) {
double divisor = 1.0 / step;
if (std::trunc(divisor) == divisor) {
step = 1.0;
}
}
if (k->is_command_or_control_pressed()) {
step *= 100.0;
} else if (k->is_shift_pressed()) {
step *= 10.0;
} else if (k->is_alt_pressed()) {
step *= 0.1;
}
_evaluate_input_text();
double last_value = get_value();
if (code == Key::DOWN) {
step *= -1;
}
set_value(last_value + step);
value_input_dirty = true;
set_process_internal(true);
} break;
case Key::ESCAPE: {
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
if (value_input_popup) {
value_input_popup->hide();
}
} break;
default:
break;
}
}
}
void EditorSpinSlider::_update_value_input_stylebox() {
if (!value_input) {
return;
}
// Add a left margin to the stylebox to make the number align with the Label
// when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's
// default margins.
Ref<StyleBox> stylebox = get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit"))->duplicate();
// EditorSpinSliders with a label have more space on the left, so add an
// higher margin to match the location where the text begins.
// The margin values below were determined by empirical testing.
if (is_layout_rtl()) {
stylebox->set_content_margin(SIDE_RIGHT, (!get_label().is_empty() ? 23 : 16) * EDSCALE);
} else {
stylebox->set_content_margin(SIDE_LEFT, (!get_label().is_empty() ? 23 : 16) * EDSCALE);
}
value_input->add_theme_style_override(CoreStringName(normal), stylebox);
}
void EditorSpinSlider::_draw_spin_slider() {
updown_offset = -1;
RID ci = get_canvas_item();
bool rtl = is_layout_rtl();
Vector2 size = get_size();
Ref<StyleBox> sb = get_theme_stylebox(read_only ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
if (!flat) {
draw_style_box(sb, Rect2(Vector2(), size));
}
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("LineEdit"));
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("LineEdit"));
int sep_base = 4 * EDSCALE;
int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better
int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
Ref<Texture2D> updown = get_theme_icon(read_only ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
String numstr = get_text_value();
int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
Color fc = get_theme_color(read_only ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
Color lc = get_theme_color(read_only ? SNAME("read_only_label_color") : SNAME("label_color"));
if (flat && !label.is_empty()) {
Ref<StyleBox> label_bg = get_theme_stylebox(SNAME("label_bg"), SNAME("EditorSpinSlider"));
if (rtl) {
draw_style_box(label_bg, Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)));
} else {
draw_style_box(label_bg, Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)));
}
}
if (has_focus()) {
Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
draw_style_box(focus, Rect2(Vector2(), size));
}
if (rtl) {
draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5));
} else {
draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5));
}
int suffix_start = numstr.length();
RID num_rid = TS->create_shaped_text();
TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size, font->get_opentype_features());
float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep);
Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs);
int v_size = TS->shaped_text_get_glyph_count(num_rid);
const Glyph *glyphs = TS->shaped_text_get_glyphs(num_rid);
for (int i = 0; i < v_size; i++) {
for (int j = 0; j < glyphs[i].repeat; j++) {
if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) {
Color color = fc;
if (glyphs[i].start >= suffix_start) {
color.a *= 0.4;
}
if (glyphs[i].font_rid != RID()) {
TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
} else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
}
}
text_ofs.x += glyphs[i].advance;
}
}
TS->free_rid(num_rid);
if (!hide_slider) {
if (editing_integer) {
Ref<Texture2D> updown2 = read_only ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
int updown_vofs = (size.height - updown2->get_height()) / 2;
if (rtl) {
updown_offset = sb->get_margin(SIDE_LEFT);
} else {
updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
}
Color c(1, 1, 1);
if (hover_updown) {
c *= Color(1.2, 1.2, 1.2);
}
draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
if (rtl) {
updown_offset += updown2->get_width();
}
if (grabber->is_visible()) {
grabber->hide();
}
} else {
const int grabber_w = 4 * EDSCALE;
const int width = size.width - sb->get_minimum_size().width - grabber_w;
const int ofs = sb->get_offset().x;
const int svofs = (size.height + vofs) / 2 - 1;
Color c = fc;
// Draw the horizontal slider's background.
c.a = 0.2;
draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
// Draw the horizontal slider's filled part on the left.
const int gofs = get_as_ratio() * width;
c.a = 0.45;
draw_rect(Rect2(ofs, svofs + 1, gofs, 2 * EDSCALE), c);
// Draw the horizontal slider's grabber.
c.a = 0.9;
const Rect2 grabber_rect = Rect2(ofs + gofs, svofs, grabber_w, 4 * EDSCALE);
draw_rect(grabber_rect, c);
grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.get_center();
bool display_grabber = !read_only && (grabbing_grabber || mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
if (grabber->is_visible() != display_grabber) {
grabber->set_visible(display_grabber);
}
if (display_grabber) {
Ref<Texture2D> grabber_tex;
if (mouse_over_grabber) {
grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
} else {
grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
}
if (grabber->get_texture() != grabber_tex) {
grabber->set_texture(grabber_tex);
}
grabber->reset_size();
grabber->set_position(grabber_rect.get_center() - grabber->get_size() * 0.5);
if (mousewheel_over_grabber) {
Input::get_singleton()->warp_mouse(grabber->get_global_position() + grabber_rect.size);
}
grabber_range = width;
}
}
}
}
void EditorSpinSlider::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
grabbing_spinner_speed = EDITOR_GET("interface/inspector/float_drag_speed");
_update_value_input_stylebox();
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
_update_value_input_stylebox();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (value_input_dirty) {
value_input_dirty = false;
value_input->set_text(get_text_value());
}
set_process_internal(false);
} break;
case NOTIFICATION_DRAW: {
_draw_spin_slider();
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT:
case NOTIFICATION_WM_CLOSE_REQUEST:
case NOTIFICATION_EXIT_TREE: {
if (grabbing_spinner) {
grabber->hide();
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
grabbing_spinner = false;
grabbing_spinner_attempt = false;
}
} break;
case NOTIFICATION_MOUSE_ENTER: {
mouse_over_spin = true;
queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
mouse_over_spin = false;
queue_redraw();
} break;
case NOTIFICATION_FOCUS_ENTER: {
if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
_focus_entered();
}
value_input_closed_frame = 0;
} break;
}
}
LineEdit *EditorSpinSlider::get_line_edit() {
_ensure_input_popup();
return value_input;
}
Size2 EditorSpinSlider::get_minimum_size() const {
Ref<StyleBox> sb = get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit"));
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("LineEdit"));
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("LineEdit"));
Size2 ms = sb->get_minimum_size();
ms.height += font->get_height(font_size);
return ms;
}
void EditorSpinSlider::set_hide_slider(bool p_hide) {
hide_slider = p_hide;
queue_redraw();
}
bool EditorSpinSlider::is_hiding_slider() const {
return hide_slider;
}
void EditorSpinSlider::set_editing_integer(bool p_editing_integer) {
if (p_editing_integer == editing_integer) {
return;
}
editing_integer = p_editing_integer;
queue_redraw();
}
bool EditorSpinSlider::is_editing_integer() const {
return editing_integer;
}
void EditorSpinSlider::set_label(const String &p_label) {
label = p_label;
queue_redraw();
}
String EditorSpinSlider::get_label() const {
return label;
}
void EditorSpinSlider::set_suffix(const String &p_suffix) {
suffix = p_suffix;
queue_redraw();
}
String EditorSpinSlider::get_suffix() const {
return suffix;
}
void EditorSpinSlider::_evaluate_input_text() {
Ref<Expression> expr;
expr.instantiate();
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
String text = value_input->get_text().replace_char(',', '.');
text = text.replace_char(';', ',');
text = TS->parse_number(text);
Error err = expr->parse(text);
if (err != OK) {
// If the expression failed try without converting commas to dots - they might have been for parameter separation.
text = value_input->get_text();
text = TS->parse_number(text);
err = expr->parse(text);
if (err != OK) {
return;
}
}
Variant v = expr->execute(Array(), nullptr, false, true);
if (v.get_type() == Variant::NIL) {
return;
}
set_value(v);
}
//text_submitted signal
void EditorSpinSlider::_value_input_submitted(const String &p_text) {
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
if (value_input_popup) {
value_input_popup->hide();
}
}
//modal_closed signal
void EditorSpinSlider::_value_input_closed() {
_evaluate_input_text();
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
}
//focus_exited signal
void EditorSpinSlider::_value_focus_exited() {
// discontinue because the focus_exit was caused by right-click context menu
if (value_input->is_menu_visible()) {
return;
}
if (read_only) {
// Spin slider has become read only while it was being edited.
return;
}
_evaluate_input_text();
// focus is not on the same element after the value_input was exited
// -> focus is on next element
// -> TAB was pressed
// -> modal_close was not called
// -> need to close/hide manually
if (!is_visible_in_tree() || value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
// Hidden or something else took focus.
if (value_input_popup) {
value_input_popup->hide();
}
} else {
// Enter or Esc was pressed.
grab_focus();
}
emit_signal("value_focus_exited");
}
void EditorSpinSlider::_grabber_mouse_entered() {
mouse_over_grabber = true;
queue_redraw();
}
void EditorSpinSlider::_grabber_mouse_exited() {
mouse_over_grabber = false;
queue_redraw();
}
void EditorSpinSlider::set_read_only(bool p_enable) {
read_only = p_enable;
if (read_only && value_input && value_input->is_inside_tree()) {
value_input->release_focus();
}
queue_redraw();
}
bool EditorSpinSlider::is_read_only() const {
return read_only;
}
void EditorSpinSlider::set_flat(bool p_enable) {
flat = p_enable;
queue_redraw();
}
bool EditorSpinSlider::is_flat() const {
return flat;
}
bool EditorSpinSlider::is_grabbing() const {
return grabbing_grabber || grabbing_spinner;
}
void EditorSpinSlider::_focus_entered() {
if (read_only) {
return;
}
_ensure_input_popup();
value_input->set_text(get_text_value());
value_input_popup->set_size(get_size());
value_input->set_focus_next(find_next_valid_focus()->get_path());
value_input->set_focus_previous(find_prev_valid_focus()->get_path());
callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred();
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred();
callable_mp(value_input, &LineEdit ::select_all).call_deferred();
emit_signal("value_focus_entered");
}
void EditorSpinSlider::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label);
ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label);
ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &EditorSpinSlider::set_suffix);
ClassDB::bind_method(D_METHOD("get_suffix"), &EditorSpinSlider::get_suffix);
ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorSpinSlider::set_read_only);
ClassDB::bind_method(D_METHOD("is_read_only"), &EditorSpinSlider::is_read_only);
ClassDB::bind_method(D_METHOD("set_flat", "flat"), &EditorSpinSlider::set_flat);
ClassDB::bind_method(D_METHOD("is_flat"), &EditorSpinSlider::is_flat);
ClassDB::bind_method(D_METHOD("set_hide_slider", "hide_slider"), &EditorSpinSlider::set_hide_slider);
ClassDB::bind_method(D_METHOD("is_hiding_slider"), &EditorSpinSlider::is_hiding_slider);
ClassDB::bind_method(D_METHOD("set_editing_integer", "editing_integer"), &EditorSpinSlider::set_editing_integer);
ClassDB::bind_method(D_METHOD("is_editing_integer"), &EditorSpinSlider::is_editing_integer);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_slider"), "set_hide_slider", "is_hiding_slider");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editing_integer"), "set_editing_integer", "is_editing_integer");
ADD_SIGNAL(MethodInfo("grabbed"));
ADD_SIGNAL(MethodInfo("ungrabbed"));
ADD_SIGNAL(MethodInfo("updown_pressed"));
ADD_SIGNAL(MethodInfo("value_focus_entered"));
ADD_SIGNAL(MethodInfo("value_focus_exited"));
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_icon, "updown");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_disabled_icon, "updown_disabled");
}
void EditorSpinSlider::_ensure_input_popup() {
if (value_input_popup) {
return;
}
value_input_popup = memnew(Control);
value_input_popup->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
add_child(value_input_popup);
value_input = memnew(LineEdit);
value_input->set_emoji_menu_enabled(false);
value_input->set_focus_mode(FOCUS_CLICK);
value_input_popup->add_child(value_input);
value_input->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
value_input_popup->connect(SceneStringName(hidden), callable_mp(this, &EditorSpinSlider::_value_input_closed));
value_input->connect(SceneStringName(text_submitted), callable_mp(this, &EditorSpinSlider::_value_input_submitted));
value_input->connect(SceneStringName(focus_exited), callable_mp(this, &EditorSpinSlider::_value_focus_exited));
value_input->connect(SceneStringName(gui_input), callable_mp(this, &EditorSpinSlider::_value_input_gui_input));
if (is_inside_tree()) {
_update_value_input_stylebox();
}
}
EditorSpinSlider::EditorSpinSlider() {
set_focus_mode(FOCUS_ALL);
grabber = memnew(TextureRect);
add_child(grabber);
grabber->hide();
grabber->set_z_index(1);
grabber->set_mouse_filter(MOUSE_FILTER_STOP);
grabber->connect(SceneStringName(mouse_entered), callable_mp(this, &EditorSpinSlider::_grabber_mouse_entered));
grabber->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorSpinSlider::_grabber_mouse_exited));
grabber->connect(SceneStringName(gui_input), callable_mp(this, &EditorSpinSlider::_grabber_gui_input));
}

View File

@@ -0,0 +1,132 @@
/**************************************************************************/
/* editor_spin_slider.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/line_edit.h"
#include "scene/gui/range.h"
#include "scene/gui/texture_rect.h"
class EditorSpinSlider : public Range {
GDCLASS(EditorSpinSlider, Range);
String label;
String suffix;
int updown_offset = -1;
bool hover_updown = false;
bool mouse_hover = false;
TextureRect *grabber = nullptr;
int grabber_range = 1;
bool mouse_over_spin = false;
bool mouse_over_grabber = false;
bool mousewheel_over_grabber = false;
bool grabbing_grabber = false;
int grabbing_from = 0;
float grabbing_ratio = 0.0f;
bool grabbing_spinner_attempt = false;
bool grabbing_spinner = false;
bool read_only = false;
float grabbing_spinner_dist_cache = 0.0f;
float grabbing_spinner_speed = 0.0f;
Vector2 grabbing_spinner_mouse_pos;
double pre_grab_value = 0.0;
Control *value_input_popup = nullptr;
LineEdit *value_input = nullptr;
uint64_t value_input_closed_frame = 0;
bool value_input_dirty = false;
bool hide_slider = false;
bool flat = false;
bool editing_integer = false;
void _grab_start();
void _grab_end();
void _grabber_gui_input(const Ref<InputEvent> &p_event);
void _value_input_closed();
void _value_input_submitted(const String &);
void _value_focus_exited();
void _value_input_gui_input(const Ref<InputEvent> &p_event);
void _evaluate_input_text();
void _update_value_input_stylebox();
void _ensure_input_popup();
void _draw_spin_slider();
struct ThemeCache {
Ref<Texture2D> updown_icon;
Ref<Texture2D> updown_disabled_icon;
} theme_cache;
protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
static void _bind_methods();
void _grabber_mouse_entered();
void _grabber_mouse_exited();
void _focus_entered();
public:
String get_tooltip(const Point2 &p_pos) const override;
String get_text_value() const;
void set_label(const String &p_label);
String get_label() const;
void set_suffix(const String &p_suffix);
String get_suffix() const;
void set_hide_slider(bool p_hide);
bool is_hiding_slider() const;
void set_editing_integer(bool p_editing_integer);
bool is_editing_integer() const;
void set_read_only(bool p_enable);
bool is_read_only() const;
void set_flat(bool p_enable);
bool is_flat() const;
bool is_grabbing() const;
void setup_and_show() { _focus_entered(); }
LineEdit *get_line_edit();
virtual Size2 get_minimum_size() const override;
EditorSpinSlider();
};

View File

@@ -0,0 +1,153 @@
/**************************************************************************/
/* editor_title_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_title_bar.h"
void EditorTitleBar::gui_input(const Ref<InputEvent> &p_event) {
if (!can_move) {
return;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && moving) {
if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
Window *w = Object::cast_to<Window>(get_viewport());
if (w) {
Point2 mouse = DisplayServer::get_singleton()->mouse_get_position();
w->set_position(mouse - click_pos);
}
} else {
moving = false;
}
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && has_point(mb->get_position())) {
Window *w = Object::cast_to<Window>(get_viewport());
if (w) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_DRAG)) {
DisplayServer::get_singleton()->window_start_drag(w->get_window_id());
} else {
click_pos = DisplayServer::get_singleton()->mouse_get_position() - w->get_position();
moving = true;
}
} else {
moving = false;
}
}
if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) {
if (DisplayServer::get_singleton()->window_maximize_on_title_dbl_click()) {
if (w->get_mode() == Window::MODE_WINDOWED) {
w->set_mode(Window::MODE_MAXIMIZED);
} else if (w->get_mode() == Window::MODE_MAXIMIZED) {
w->set_mode(Window::MODE_WINDOWED);
}
} else if (DisplayServer::get_singleton()->window_minimize_on_title_dbl_click()) {
w->set_mode(Window::MODE_MINIMIZED);
}
moving = false;
}
}
}
}
void EditorTitleBar::set_center_control(Control *p_center_control) {
center_control = p_center_control;
}
Control *EditorTitleBar::get_center_control() const {
return center_control;
}
void EditorTitleBar::_notification(int p_what) {
if (!center_control || p_what != NOTIFICATION_SORT_CHILDREN) {
return;
}
Control *prev = nullptr;
Control *base = nullptr;
Control *next = nullptr;
bool rtl = is_layout_rtl();
int start;
int end;
int delta;
if (rtl) {
start = get_child_count() - 1;
end = -1;
delta = -1;
} else {
start = 0;
end = get_child_count();
delta = +1;
}
for (int i = start; i != end; i += delta) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
if (base) {
next = c;
break;
}
if (c != center_control) {
prev = c;
continue;
}
base = c;
}
if (base && prev && next) {
Size2i title_size = get_size();
Size2i c_size = base->get_combined_minimum_size();
int min_offset = prev->get_position().x + prev->get_combined_minimum_size().x;
int max_offset = next->get_position().x + next->get_size().x - next->get_combined_minimum_size().x - c_size.x;
int offset = (title_size.width - c_size.width) / 2;
offset = CLAMP(offset, min_offset, max_offset);
fit_child_in_rect(prev, Rect2i(prev->get_position().x, 0, offset - prev->get_position().x, title_size.height));
fit_child_in_rect(base, Rect2i(offset, 0, c_size.width, title_size.height));
fit_child_in_rect(next, Rect2i(offset + c_size.width, 0, next->get_position().x + next->get_size().x - (offset + c_size.width), title_size.height));
}
}
void EditorTitleBar::set_can_move_window(bool p_enabled) {
can_move = p_enabled;
set_process_input(can_move);
}
bool EditorTitleBar::get_can_move_window() const {
return can_move;
}

View File

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

View File

@@ -0,0 +1,613 @@
/**************************************************************************/
/* editor_toaster.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_toaster.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"
#include "scene/gui/panel_container.h"
#include "scene/resources/style_box_flat.h"
EditorToaster *EditorToaster::singleton = nullptr;
void EditorToaster::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
double delta = get_process_delta_time();
// Check if one element is hovered, if so, don't elapse time.
bool hovered = false;
for (const KeyValue<Control *, Toast> &element : toasts) {
if (Rect2(Vector2(), element.key->get_size()).has_point(element.key->get_local_mouse_position())) {
hovered = true;
break;
}
}
// Elapses the time and remove toasts if needed.
if (!hovered) {
for (const KeyValue<Control *, Toast> &element : toasts) {
if (!element.value.popped || element.value.duration <= 0) {
continue;
}
toasts[element.key].remaining_time -= delta;
if (toasts[element.key].remaining_time < 0) {
close(element.key);
}
element.key->queue_redraw();
}
} else {
// Reset the timers when hovered.
for (const KeyValue<Control *, Toast> &element : toasts) {
if (!element.value.popped || element.value.duration <= 0) {
continue;
}
toasts[element.key].remaining_time = element.value.duration;
element.key->queue_redraw();
}
}
// Change alpha over time.
bool needs_update = false;
for (const KeyValue<Control *, Toast> &element : toasts) {
Color modulate_fade = element.key->get_modulate();
// Change alpha over time.
if (element.value.popped && modulate_fade.a < 1.0) {
modulate_fade.a += delta * 3;
element.key->set_modulate(modulate_fade);
} else if (!element.value.popped && modulate_fade.a > 0.0) {
modulate_fade.a -= delta * 2;
element.key->set_modulate(modulate_fade);
}
// Hide element if it is not visible anymore.
if (modulate_fade.a <= 0.0 && element.key->is_visible()) {
element.key->hide();
needs_update = true;
} else if (modulate_fade.a > 0.0 && !element.key->is_visible()) {
element.key->show();
needs_update = true;
}
}
if (needs_update) {
_update_vbox_position();
_update_disable_notifications_button();
main_button->queue_redraw();
}
} break;
case NOTIFICATION_THEME_CHANGED: {
if (vbox_container->is_visible()) {
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
} else {
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
}
disable_notifications_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
// Styleboxes background.
info_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
warning_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
warning_panel_style_background->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
error_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
error_panel_style_background->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
// Styleboxes progress.
info_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
warning_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
warning_panel_style_progress->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
error_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
error_panel_style_progress->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
_update_vbox_position();
_update_disable_notifications_button();
} break;
}
}
void EditorToaster::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
// This may be called from a thread. Since we will deal with non-thread-safe elements,
// we have to put it in the queue for safety.
callable_mp_static(&EditorToaster::_error_handler_impl).call_deferred(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type);
}
void EditorToaster::_error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type) {
if (!EditorToaster::get_singleton() || !EditorToaster::get_singleton()->is_inside_tree()) {
return;
}
#ifdef DEV_ENABLED
bool in_dev = true;
#else
bool in_dev = false;
#endif
int show_all_setting = EDITOR_GET("interface/editor/show_internal_errors_in_toast_notifications");
if (p_editor_notify || (show_all_setting == 0 && in_dev) || show_all_setting == 1) {
String err_str = !p_errorexp.is_empty() ? p_errorexp : p_error;
String tooltip_str = p_file + ":" + itos(p_line);
if (!p_editor_notify) {
if (p_type == ERR_HANDLER_WARNING) {
err_str = "INTERNAL WARNING: " + err_str;
} else {
err_str = "INTERNAL ERROR: " + err_str;
}
}
Severity severity = ((ErrorHandlerType)p_type == ERR_HANDLER_WARNING) ? SEVERITY_WARNING : SEVERITY_ERROR;
EditorToaster::get_singleton()->popup_str(err_str, severity, tooltip_str);
}
}
void EditorToaster::_update_vbox_position() {
// This is kind of a workaround because it's hard to keep the VBox anchroed to the bottom.
vbox_container->set_size(Vector2());
vbox_container->set_position(get_global_position() - vbox_container->get_size() + Vector2(get_size().x, -5 * EDSCALE));
}
void EditorToaster::_update_disable_notifications_button() {
bool any_visible = false;
for (KeyValue<Control *, Toast> element : toasts) {
if (element.key->is_visible()) {
any_visible = true;
break;
}
}
if (!any_visible || !vbox_container->is_visible()) {
disable_notifications_panel->hide();
} else {
disable_notifications_panel->show();
disable_notifications_panel->set_position(get_global_position() + Vector2(5 * EDSCALE, -disable_notifications_panel->get_minimum_size().y) + Vector2(get_size().x, -5 * EDSCALE));
}
}
void EditorToaster::_auto_hide_or_free_toasts() {
// Hide or free old temporary items.
int visible_temporary = 0;
int temporary = 0;
LocalVector<Control *> to_delete;
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
if (toasts[control].duration <= 0) {
continue; // Ignore non-temporary toasts.
}
temporary++;
if (control->is_visible()) {
visible_temporary++;
}
// Hide
if (visible_temporary > max_temporary_count) {
close(control);
}
// Free
if (temporary > max_temporary_count * 2) {
to_delete.push_back(control);
}
}
// Delete the control right away (removed as child) as it might cause issues otherwise when iterative over the vbox_container children.
for (Control *c : to_delete) {
vbox_container->remove_child(c);
c->queue_free();
toasts.erase(c);
}
if (toasts.is_empty()) {
main_button->set_tooltip_text(TTRC("No notifications."));
main_button->set_modulate(Color(0.5, 0.5, 0.5));
main_button->set_disabled(true);
set_process_internal(false);
} else {
main_button->set_tooltip_text(TTRC("Show notifications."));
main_button->set_modulate(Color(1, 1, 1));
main_button->set_disabled(false);
}
}
void EditorToaster::_draw_button() {
bool has_one = false;
Severity highest_severity = SEVERITY_INFO;
for (const KeyValue<Control *, Toast> &element : toasts) {
if (!element.key->is_visible()) {
continue;
}
has_one = true;
if (element.value.severity > highest_severity) {
highest_severity = element.value.severity;
}
}
if (!has_one) {
return;
}
Color color;
real_t button_radius = main_button->get_size().x / 8;
switch (highest_severity) {
case SEVERITY_INFO:
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
break;
case SEVERITY_WARNING:
color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
break;
case SEVERITY_ERROR:
color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
break;
default:
break;
}
main_button->draw_circle(Vector2(button_radius * 2, button_radius * 2), button_radius, color);
}
void EditorToaster::_draw_progress(Control *panel) {
if (toasts.has(panel) && toasts[panel].remaining_time > 0 && toasts[panel].duration > 0) {
Size2 size = panel->get_size();
size.x *= MIN(1, Math::remap(toasts[panel].remaining_time, 0, toasts[panel].duration, 0, 2));
Ref<StyleBoxFlat> stylebox;
switch (toasts[panel].severity) {
case SEVERITY_INFO:
stylebox = info_panel_style_progress;
break;
case SEVERITY_WARNING:
stylebox = warning_panel_style_progress;
break;
case SEVERITY_ERROR:
stylebox = error_panel_style_progress;
break;
default:
break;
}
panel->draw_style_box(stylebox, Rect2(Vector2(), size));
}
}
void EditorToaster::_set_notifications_enabled(bool p_enabled) {
vbox_container->set_visible(p_enabled);
if (p_enabled) {
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
} else {
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
}
_update_disable_notifications_button();
}
void EditorToaster::_repop_old() {
// Repop olds, up to max_temporary_count
bool needs_update = false;
int visible_count = 0;
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
if (!control->is_visible()) {
control->show();
toasts[control].remaining_time = toasts[control].duration;
toasts[control].popped = true;
needs_update = true;
}
visible_count++;
if (visible_count >= max_temporary_count) {
break;
}
}
if (needs_update) {
_update_vbox_position();
_update_disable_notifications_button();
main_button->queue_redraw();
}
}
Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_time, const String &p_tooltip) {
// Create the panel according to the severity.
PanelContainer *panel = memnew(PanelContainer);
panel->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
panel->set_tooltip_text(p_tooltip);
switch (p_severity) {
case SEVERITY_INFO:
panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
break;
case SEVERITY_WARNING:
panel->add_theme_style_override(SceneStringName(panel), warning_panel_style_background);
break;
case SEVERITY_ERROR:
panel->add_theme_style_override(SceneStringName(panel), error_panel_style_background);
break;
default:
break;
}
panel->set_modulate(Color(1, 1, 1, 0));
panel->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_progress).bind(panel));
panel->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_toast_theme_changed).bind(panel));
Toast &toast = toasts[panel];
// Horizontal container.
HBoxContainer *hbox_container = memnew(HBoxContainer);
hbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
panel->add_child(hbox_container);
// Content control.
p_control->set_h_size_flags(SIZE_EXPAND_FILL);
hbox_container->add_child(p_control);
// Add buttons.
if (p_time > 0.0) {
Button *copy_button = memnew(Button);
copy_button->set_accessibility_name(TTRC("Copy"));
copy_button->set_flat(true);
copy_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::copy).bind(panel));
hbox_container->add_child(copy_button);
Button *close_button = memnew(Button);
close_button->set_accessibility_name(TTRC("Close"));
close_button->set_flat(true);
close_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::instant_close).bind(panel));
hbox_container->add_child(close_button);
toast.copy_button = copy_button;
toast.close_button = close_button;
}
toast.severity = p_severity;
if (p_time > 0.0) {
toast.duration = p_time;
toast.remaining_time = p_time;
} else {
toast.duration = -1.0;
}
toast.popped = true;
vbox_container->add_child(panel);
_auto_hide_or_free_toasts();
_update_vbox_position();
_update_disable_notifications_button();
main_button->queue_redraw();
return panel;
}
void EditorToaster::popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
if (is_processing_error) {
return;
}
// Since "_popup_str" adds nodes to the tree, and since the "add_child" method is not
// thread-safe, it's better to defer the call to the next cycle to be thread-safe.
is_processing_error = true;
MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &EditorToaster::_popup_str), p_message, p_severity, p_tooltip);
is_processing_error = false;
}
void EditorToaster::_popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
is_processing_error = true;
// Check if we already have a popup with the given message.
Control *control = nullptr;
for (KeyValue<Control *, Toast> element : toasts) {
if (element.value.message == p_message && element.value.severity == p_severity && element.value.tooltip == p_tooltip) {
control = element.key;
break;
}
}
// Create a new message if needed.
if (control == nullptr) {
HBoxContainer *hb = memnew(HBoxContainer);
hb->add_theme_constant_override("separation", 0);
Label *label = memnew(Label);
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
label->set_focus_mode(FOCUS_ACCESSIBILITY);
hb->add_child(label);
Label *count_label = memnew(Label);
count_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
hb->add_child(count_label);
control = popup(hb, p_severity, default_message_duration, p_tooltip);
Toast &toast = toasts[control];
toast.message = p_message;
toast.tooltip = p_tooltip;
toast.count = 1;
toast.message_label = label;
toast.message_count_label = count_label;
} else {
Toast &toast = toasts[control];
if (toast.popped) {
toast.count += 1;
} else {
toast.count = 1;
}
toast.remaining_time = toast.duration;
toast.popped = true;
control->show();
vbox_container->move_child(control, vbox_container->get_child_count());
_auto_hide_or_free_toasts();
_update_vbox_position();
_update_disable_notifications_button();
main_button->queue_redraw();
}
// Retrieve the label back, then update the text.
Label *message_label = toasts[control].message_label;
ERR_FAIL_NULL(message_label);
message_label->set_text(p_message);
message_label->set_text_overrun_behavior(TextServer::OVERRUN_NO_TRIMMING);
message_label->set_custom_minimum_size(Size2());
Size2i size = message_label->get_combined_minimum_size();
int limit_width = get_viewport_rect().size.x / 2; // Limit label size to half the viewport size.
if (size.x > limit_width) {
message_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
message_label->set_custom_minimum_size(Size2(limit_width, 0));
}
// Retrieve the count label back, then update the text.
Label *message_count_label = toasts[control].message_count_label;
if (toasts[control].count == 1) {
message_count_label->hide();
} else {
message_count_label->set_text(vformat("(%d)", toasts[control].count));
message_count_label->show();
}
vbox_container->reset_size();
is_processing_error = false;
set_process_internal(true);
}
void EditorToaster::_toast_theme_changed(Control *p_control) {
ERR_FAIL_COND(!toasts.has(p_control));
Toast &toast = toasts[p_control];
if (toast.close_button) {
toast.close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
}
if (toast.copy_button) {
toast.copy_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
}
}
void EditorToaster::close(Control *p_control) {
ERR_FAIL_COND(!toasts.has(p_control));
toasts[p_control].remaining_time = -1.0;
toasts[p_control].popped = false;
}
void EditorToaster::instant_close(Control *p_control) {
close(p_control);
p_control->set_modulate(Color(1, 1, 1, 0));
}
void EditorToaster::copy(Control *p_control) {
ERR_FAIL_COND(!toasts.has(p_control));
DisplayServer::get_singleton()->clipboard_set(toasts[p_control].message);
}
void EditorToaster::_bind_methods() {
ClassDB::bind_method(D_METHOD("push_toast", "message", "severity", "tooltip"), &EditorToaster::_popup_str, DEFVAL(EditorToaster::SEVERITY_INFO), DEFVAL(String()));
BIND_ENUM_CONSTANT(SEVERITY_INFO);
BIND_ENUM_CONSTANT(SEVERITY_WARNING);
BIND_ENUM_CONSTANT(SEVERITY_ERROR);
}
EditorToaster *EditorToaster::get_singleton() {
return singleton;
}
EditorToaster::EditorToaster() {
set_notify_transform(true);
// VBox.
vbox_container = memnew(VBoxContainer);
vbox_container->set_as_top_level(true);
vbox_container->connect(SceneStringName(resized), callable_mp(this, &EditorToaster::_update_vbox_position));
add_child(vbox_container);
// Theming (background).
info_panel_style_background.instantiate();
info_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
warning_panel_style_background.instantiate();
warning_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
warning_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
error_panel_style_background.instantiate();
error_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
error_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
Ref<StyleBoxFlat> boxes[] = { info_panel_style_background, warning_panel_style_background, error_panel_style_background };
for (int i = 0; i < 3; i++) {
boxes[i]->set_content_margin_individual(int(stylebox_radius * 2.5), 3, int(stylebox_radius * 2.5), 3);
}
// Theming (progress).
info_panel_style_progress.instantiate();
info_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
warning_panel_style_progress.instantiate();
warning_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
warning_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
error_panel_style_progress.instantiate();
error_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
error_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
// Main button.
main_button = memnew(Button);
main_button->set_accessibility_name(TTRC("Notifications:"));
main_button->set_tooltip_text(TTRC("No notifications."));
main_button->set_modulate(Color(0.5, 0.5, 0.5));
main_button->set_disabled(true);
main_button->set_theme_type_variation("FlatMenuButton");
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(true));
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_repop_old));
main_button->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_button));
add_child(main_button);
// Disable notification button.
disable_notifications_panel = memnew(PanelContainer);
disable_notifications_panel->set_as_top_level(true);
disable_notifications_panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
add_child(disable_notifications_panel);
disable_notifications_button = memnew(Button);
disable_notifications_button->set_tooltip_text(TTRC("Silence the notifications."));
disable_notifications_button->set_flat(true);
disable_notifications_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(false));
disable_notifications_panel->add_child(disable_notifications_button);
// Other
singleton = this;
eh.errfunc = _error_handler;
add_error_handler(&eh);
}
EditorToaster::~EditorToaster() {
singleton = nullptr;
remove_error_handler(&eh);
}

126
editor/gui/editor_toaster.h Normal file
View File

@@ -0,0 +1,126 @@
/**************************************************************************/
/* editor_toaster.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
class Button;
class PanelContainer;
class StyleBoxFlat;
class EditorToaster : public HBoxContainer {
GDCLASS(EditorToaster, HBoxContainer);
public:
enum Severity {
SEVERITY_INFO = 0,
SEVERITY_WARNING,
SEVERITY_ERROR,
};
private:
ErrorHandlerList eh;
const int stylebox_radius = 3;
Ref<StyleBoxFlat> info_panel_style_background;
Ref<StyleBoxFlat> warning_panel_style_background;
Ref<StyleBoxFlat> error_panel_style_background;
Ref<StyleBoxFlat> info_panel_style_progress;
Ref<StyleBoxFlat> warning_panel_style_progress;
Ref<StyleBoxFlat> error_panel_style_progress;
Button *main_button = nullptr;
PanelContainer *disable_notifications_panel = nullptr;
Button *disable_notifications_button = nullptr;
VBoxContainer *vbox_container = nullptr;
const int max_temporary_count = 5;
struct Toast {
Severity severity = SEVERITY_INFO;
// Timing.
real_t duration = -1.0;
real_t remaining_time = 0.0;
bool popped = false;
// Buttons
Button *copy_button = nullptr;
Button *close_button = nullptr;
// Messages
String message;
String tooltip;
int count = 0;
Label *message_label = nullptr;
Label *message_count_label = nullptr;
};
HashMap<Control *, Toast> toasts;
bool is_processing_error = false; // Makes sure that we don't handle errors that are triggered within the EditorToaster error processing.
const double default_message_duration = 5.0;
static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type);
static void _error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type);
void _update_vbox_position();
void _update_disable_notifications_button();
void _auto_hide_or_free_toasts();
void _draw_button();
void _draw_progress(Control *panel);
void _set_notifications_enabled(bool p_enabled);
void _repop_old();
void _popup_str(const String &p_message, Severity p_severity, const String &p_tooltip);
void _toast_theme_changed(Control *p_control);
protected:
static void _bind_methods();
static EditorToaster *singleton;
void _notification(int p_what);
public:
static EditorToaster *get_singleton();
Control *popup(Control *p_control, Severity p_severity = SEVERITY_INFO, double p_time = 0.0, const String &p_tooltip = String());
void popup_str(const String &p_message, Severity p_severity = SEVERITY_INFO, const String &p_tooltip = String());
void close(Control *p_control);
void instant_close(Control *p_control);
void copy(Control *p_control);
EditorToaster();
~EditorToaster();
};
VARIANT_ENUM_CAST(EditorToaster::Severity);

View File

@@ -0,0 +1,137 @@
/**************************************************************************/
/* editor_validation_panel.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_validation_panel.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
void EditorValidationPanel::_update() {
for (const KeyValue<int, String> &E : valid_messages) {
set_message(E.key, E.value, MSG_OK);
}
valid = true;
update_callback.callv(Array());
if (accept_button) {
accept_button->set_disabled(!valid);
}
pending_update = false;
}
void EditorValidationPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
theme_cache.valid_color = get_theme_color(SNAME("success_color"), EditorStringName(Editor));
theme_cache.warning_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
theme_cache.error_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
} break;
}
}
void EditorValidationPanel::add_line(int p_id, const String &p_valid_message) {
ERR_FAIL_COND(valid_messages.has(p_id));
Label *label = memnew(Label);
label->set_focus_mode(FOCUS_ACCESSIBILITY);
message_container->add_child(label);
label->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
valid_messages[p_id] = p_valid_message;
labels[p_id] = label;
}
void EditorValidationPanel::set_accept_button(Button *p_button) {
accept_button = p_button;
}
void EditorValidationPanel::set_update_callback(const Callable &p_callback) {
update_callback = p_callback;
}
void EditorValidationPanel::update() {
ERR_FAIL_COND(!update_callback.is_valid());
if (pending_update) {
return;
}
pending_update = true;
callable_mp(this, &EditorValidationPanel::_update).call_deferred();
}
void EditorValidationPanel::set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix) {
ERR_FAIL_COND(!valid_messages.has(p_id));
Label *label = labels[p_id];
if (p_text.is_empty()) {
label->hide();
return;
}
label->show();
if (p_auto_prefix) {
label->set_text(String(U"") + p_text);
} else {
label->set_text(p_text);
}
switch (p_type) {
case MSG_OK:
label->add_theme_color_override(SceneStringName(font_color), theme_cache.valid_color);
break;
case MSG_WARNING:
label->add_theme_color_override(SceneStringName(font_color), theme_cache.warning_color);
break;
case MSG_ERROR:
label->add_theme_color_override(SceneStringName(font_color), theme_cache.error_color);
valid = false;
break;
case MSG_INFO:
label->remove_theme_color_override(SceneStringName(font_color));
break;
}
}
bool EditorValidationPanel::is_valid() const {
return valid;
}
EditorValidationPanel::EditorValidationPanel() {
set_v_size_flags(SIZE_EXPAND_FILL);
message_container = memnew(VBoxContainer);
add_child(message_container);
}

View File

@@ -0,0 +1,85 @@
/**************************************************************************/
/* editor_validation_panel.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/panel_container.h"
class Button;
class Label;
class VBoxContainer;
class EditorValidationPanel : public PanelContainer {
GDCLASS(EditorValidationPanel, PanelContainer);
public:
enum MessageType {
MSG_OK,
MSG_WARNING,
MSG_ERROR,
MSG_INFO,
};
static const int MSG_ID_DEFAULT = 0; // Avoids hard-coding ID in dialogs with single-line validation.
private:
VBoxContainer *message_container = nullptr;
HashMap<int, String> valid_messages;
HashMap<int, Label *> labels;
bool valid = false;
bool pending_update = false;
struct ThemeCache {
Color valid_color;
Color warning_color;
Color error_color;
} theme_cache;
void _update();
Callable update_callback;
Button *accept_button = nullptr;
protected:
void _notification(int p_what);
public:
void add_line(int p_id, const String &p_valid_message = "");
void set_accept_button(Button *p_button);
void set_update_callback(const Callable &p_callback);
void update();
void set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix = true);
bool is_valid() const;
EditorValidationPanel();
};

View File

@@ -0,0 +1,179 @@
/**************************************************************************/
/* editor_variant_type_selectors.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_variant_type_selectors.h"
struct CompareVariantTypeNames {
bool operator()(const String &p_lhs, const String &p_rhs) const {
// Variant type names should not be empty, but just in case.
DEV_ASSERT(!p_lhs.is_empty() && !p_rhs.is_empty());
// Variant type names are ascii strings.
const bool lhs_lower = is_ascii_lower_case(p_lhs[0]);
const bool rhs_lower = is_ascii_lower_case(p_rhs[0]);
if (lhs_lower != rhs_lower) {
// Lowercase types like `int` and `float` come first.
return lhs_lower > rhs_lower;
}
return p_lhs < p_rhs;
}
};
// EditorVariantTypeOptionButton
void EditorVariantTypeOptionButton::_update_menu_icons() {
for (int i = 0; i < get_item_count(); i++) {
const Variant::Type type = Variant::Type(get_item_id(i));
const String &type_name = Variant::get_type_name(type);
set_item_icon(i, get_editor_theme_icon(type_name));
}
}
void EditorVariantTypeOptionButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
} break;
case NOTIFICATION_THEME_CHANGED: {
_update_menu_icons();
} break;
}
}
Variant::Type EditorVariantTypeOptionButton::get_selected_type() const {
int selected = get_selected();
if (selected == -1) {
return Variant::NIL;
}
return Variant::Type(get_item_id(selected));
}
void EditorVariantTypeOptionButton::populate(const LocalVector<Variant::Type> &p_disabled_types, const HashMap<Variant::Type, String> &p_renames) {
LocalVector<String> names;
HashMap<String, Variant::Type> name_to_type;
names.reserve(Variant::VARIANT_MAX);
name_to_type.reserve(Variant::VARIANT_MAX);
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
const Variant::Type type = Variant::Type(i);
if (p_disabled_types.has(type) || type == Variant::RID || type == Variant::CALLABLE || type == Variant::SIGNAL) {
continue;
}
const String &type_name = Variant::get_type_name(type);
const String &display_name = p_renames.has(type) ? p_renames[type] : type_name;
names.push_back(display_name);
name_to_type[display_name] = type;
}
names.sort_custom<CompareVariantTypeNames>();
for (const String &name : names) {
add_item(name, name_to_type[name]);
}
_update_menu_icons();
}
// EditorVariantTypeArrayItemMenu
void EditorVariantTypePopupMenu::_populate() {
if (remove_item) {
add_item(TTRC("Remove Item"), Variant::VARIANT_MAX);
set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
add_separator();
}
LocalVector<String> names;
names.reserve(Variant::VARIANT_MAX);
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
const Variant::Type type = Variant::Type(i);
if (type == Variant::RID || type == Variant::CALLABLE || type == Variant::SIGNAL) {
continue;
}
names.push_back(Variant::get_type_name(type));
}
names.sort_custom<CompareVariantTypeNames>();
for (const String &name : names) {
add_item(name, Variant::get_type_by_name(name));
}
}
void EditorVariantTypePopupMenu::_update_menu_icons() {
if (remove_item) {
set_item_icon(get_item_index(Variant::VARIANT_MAX), get_editor_theme_icon(SNAME("Remove")));
}
for (int i = 0; i < get_item_count(); i++) {
int id = get_item_id(i);
// Skip the Remove Item option and the separator without hardcoding the index.
if (id < 0 || id >= Variant::VARIANT_MAX) {
continue;
}
const Variant::Type type = Variant::Type(id);
const String &type_name = Variant::get_type_name(type);
set_item_icon(i, get_editor_theme_icon(type_name));
}
}
void EditorVariantTypePopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_populate();
} break;
case NOTIFICATION_THEME_CHANGED: {
icons_dirty = true;
} break;
}
}
void EditorVariantTypePopupMenu::popup(const Rect2i &p_bounds) {
if (icons_dirty) {
_update_menu_icons();
icons_dirty = false;
}
PopupMenu::popup(p_bounds);
}
EditorVariantTypePopupMenu::EditorVariantTypePopupMenu(bool p_remove_item) {
remove_item = p_remove_item;
}

View File

@@ -0,0 +1,67 @@
/**************************************************************************/
/* editor_variant_type_selectors.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/variant/variant.h"
#include "scene/gui/option_button.h"
#include "scene/gui/popup_menu.h"
class EditorVariantTypeOptionButton : public OptionButton {
GDCLASS(EditorVariantTypeOptionButton, OptionButton);
void _update_menu_icons();
protected:
void _notification(int p_what);
public:
Variant::Type get_selected_type() const;
void populate(const LocalVector<Variant::Type> &p_disabled_types, const HashMap<Variant::Type, String> &p_renames = {});
};
class EditorVariantTypePopupMenu : public PopupMenu {
GDCLASS(EditorVariantTypePopupMenu, PopupMenu);
bool remove_item = false;
bool icons_dirty = true;
void _populate();
void _update_menu_icons();
protected:
void _notification(int p_what);
public:
virtual void popup(const Rect2i &p_bounds = Rect2i()) override;
EditorVariantTypePopupMenu(bool p_remove_item);
};

View File

@@ -0,0 +1,87 @@
/**************************************************************************/
/* editor_version_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_version_button.h"
#include "core/os/time.h"
#include "core/version.h"
String _get_version_string(EditorVersionButton::VersionFormat p_format) {
String main;
switch (p_format) {
case EditorVersionButton::FORMAT_BASIC: {
return GODOT_VERSION_FULL_CONFIG;
} break;
case EditorVersionButton::FORMAT_WITH_BUILD: {
main = "v" GODOT_VERSION_FULL_BUILD;
} break;
case EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD: {
main = GODOT_VERSION_FULL_NAME;
} break;
default: {
ERR_FAIL_V_MSG(GODOT_VERSION_FULL_NAME, "Unexpected format: " + itos(p_format));
} break;
}
String hash = GODOT_VERSION_HASH;
if (!hash.is_empty()) {
hash = vformat(" [%s]", hash.left(9));
}
return main + hash;
}
void EditorVersionButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
// This can't be done in the constructor because theme cache is not ready yet.
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
set_text(_get_version_string(format));
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
String build_date;
if (GODOT_VERSION_TIMESTAMP > 0) {
build_date = Time::get_singleton()->get_datetime_string_from_unix_time(GODOT_VERSION_TIMESTAMP, true) + " UTC";
} else {
build_date = TTR("(unknown)");
}
set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
} break;
}
}
void EditorVersionButton::pressed() {
DisplayServer::get_singleton()->clipboard_set(_get_version_string(FORMAT_WITH_BUILD));
}
EditorVersionButton::EditorVersionButton(VersionFormat p_format) {
format = p_format;
set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
}

View File

@@ -0,0 +1,58 @@
/**************************************************************************/
/* editor_version_button.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/link_button.h"
class EditorVersionButton : public LinkButton {
GDCLASS(EditorVersionButton, LinkButton);
public:
enum VersionFormat {
// 4.3.2.stable
FORMAT_BASIC,
// v4.3.2.stable.mono [HASH]
FORMAT_WITH_BUILD,
// Godot Engine v4.3.2.stable.mono.official [HASH]
FORMAT_WITH_NAME_AND_BUILD,
};
private:
VersionFormat format = FORMAT_WITH_NAME_AND_BUILD;
protected:
void _notification(int p_what);
virtual void pressed() override;
public:
EditorVersionButton(VersionFormat p_format);
};

View File

@@ -0,0 +1,231 @@
/**************************************************************************/
/* editor_zoom_widget.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_zoom_widget.h"
#include "core/os/keyboard.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
void EditorZoomWidget::_update_zoom_label() {
String zoom_text;
// The zoom level displayed is relative to the editor scale
// (like in most image editors). Its lower bound is clamped to 1 as some people
// lower the editor scale to increase the available real estate,
// even if their display doesn't have a particularly low DPI.
if (zoom >= 10) {
zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100)));
} else {
// 2 decimal places if the zoom is below 10%, 1 decimal place if it's below 1000%.
zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, (zoom >= 0.1) ? 0.1 : 0.01)));
}
zoom_text += " " + TS->percent_sign();
zoom_reset->set_text(zoom_text);
}
void EditorZoomWidget::_button_zoom_minus() {
set_zoom_by_increments(-6, Input::get_singleton()->is_key_pressed(Key::ALT));
emit_signal(SNAME("zoom_changed"), zoom);
}
void EditorZoomWidget::_button_zoom_reset() {
set_zoom(1.0 * MAX(1, EDSCALE));
emit_signal(SNAME("zoom_changed"), zoom);
}
void EditorZoomWidget::_button_zoom_plus() {
set_zoom_by_increments(6, Input::get_singleton()->is_key_pressed(Key::ALT));
emit_signal(SNAME("zoom_changed"), zoom);
}
float EditorZoomWidget::get_zoom() {
return zoom;
}
void EditorZoomWidget::set_zoom(float p_zoom) {
float new_zoom = CLAMP(p_zoom, min_zoom, max_zoom);
if (zoom != new_zoom) {
zoom = new_zoom;
_update_zoom_label();
}
}
float EditorZoomWidget::get_min_zoom() {
return min_zoom;
}
float EditorZoomWidget::get_max_zoom() {
return max_zoom;
}
void EditorZoomWidget::setup_zoom_limits(float p_min, float p_max) {
ERR_FAIL_COND(p_min < 0 || p_min > p_max);
min_zoom = p_min;
max_zoom = p_max;
if (zoom > max_zoom) {
set_zoom(max_zoom);
emit_signal(SNAME("zoom_changed"), zoom);
} else if (zoom < min_zoom) {
set_zoom(min_zoom);
emit_signal(SNAME("zoom_changed"), zoom);
}
}
void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_integer_only) {
// Remove editor scale from the index computation.
const float zoom_noscale = zoom / MAX(1, EDSCALE);
if (p_integer_only) {
// Only visit integer scaling factors above 100%, and fractions with an integer denominator below 100%
// (1/2 = 50%, 1/3 = 33.33%, 1/4 = 25%, …).
// This is useful when working on pixel art projects to avoid distortion.
// This algorithm is designed to handle fractional start zoom values correctly
// (e.g. 190% will zoom up to 200% and down to 100%).
if (zoom_noscale + p_increment_count * 0.001 >= 1.0 - CMP_EPSILON) {
// New zoom is certain to be above 100%.
if (p_increment_count >= 1) {
// Zooming.
set_zoom(Math::floor(zoom_noscale + p_increment_count) * MAX(1, EDSCALE));
} else {
// Dezooming.
set_zoom(Math::ceil(zoom_noscale + p_increment_count) * MAX(1, EDSCALE));
}
} else {
if (p_increment_count >= 1) {
// Zooming in. Convert the current zoom into a denominator.
float new_zoom = 1.0 / Math::ceil(1.0 / zoom_noscale - p_increment_count);
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
// New zoom is identical to the old zoom, so try again.
// This can happen due to floating-point precision issues.
new_zoom = 1.0 / Math::ceil(1.0 / zoom_noscale - p_increment_count - 1);
}
set_zoom(new_zoom * MAX(1, EDSCALE));
} else {
// Zooming out. Convert the current zoom into a denominator.
float new_zoom = 1.0 / Math::floor(1.0 / zoom_noscale - p_increment_count);
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
// New zoom is identical to the old zoom, so try again.
// This can happen due to floating-point precision issues.
new_zoom = 1.0 / Math::floor(1.0 / zoom_noscale - p_increment_count + 1);
}
set_zoom(new_zoom * MAX(1, EDSCALE));
}
}
} else {
if (zoom < CMP_EPSILON || p_increment_count == 0) {
return;
}
// Zoom is calculated as pow(zoom_factor, zoom_step).
// This ensures the zoom will always equal 100% when zoom_step is 0.
float zoom_factor = EDITOR_GET("editors/2d/zoom_speed_factor");
float current_zoom_step = Math::round(Math::log(zoom_noscale) / Math::log(zoom_factor));
float new_zoom = Math::pow(zoom_factor, current_zoom_step + p_increment_count);
// Restore Editor scale transformation.
new_zoom *= MAX(1, EDSCALE);
set_zoom(new_zoom);
}
}
void EditorZoomWidget::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
zoom_minus->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
zoom_plus->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
} break;
}
}
void EditorZoomWidget::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &EditorZoomWidget::set_zoom);
ClassDB::bind_method(D_METHOD("get_zoom"), &EditorZoomWidget::get_zoom);
ClassDB::bind_method(D_METHOD("set_zoom_by_increments", "increment", "integer_only"), &EditorZoomWidget::set_zoom_by_increments);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom");
ADD_SIGNAL(MethodInfo("zoom_changed", PropertyInfo(Variant::FLOAT, "zoom")));
}
void EditorZoomWidget::set_shortcut_context(Node *p_node) const {
zoom_minus->set_shortcut_context(p_node);
zoom_plus->set_shortcut_context(p_node);
zoom_reset->set_shortcut_context(p_node);
}
EditorZoomWidget::EditorZoomWidget() {
// Zoom buttons
zoom_minus = memnew(Button);
zoom_minus->set_accessibility_name(TTRC("Zoom Out"));
zoom_minus->set_flat(true);
zoom_minus->set_shortcut(ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_minus", TTRC("Zoom Out"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));
zoom_minus->set_shortcut_context(this);
zoom_minus->set_focus_mode(FOCUS_ACCESSIBILITY);
add_child(zoom_minus);
zoom_minus->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_minus));
zoom_reset = memnew(Button);
zoom_reset->set_flat(true);
zoom_reset->set_accessibility_name(TTRC("Reset Zoom"));
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
zoom_reset->add_theme_style_override(CoreStringName(normal), empty_stylebox);
zoom_reset->add_theme_style_override(SceneStringName(hover), empty_stylebox);
zoom_reset->add_theme_style_override("focus", empty_stylebox);
zoom_reset->add_theme_style_override(SceneStringName(pressed), empty_stylebox);
zoom_reset->add_theme_constant_override("outline_size", Math::ceil(2 * EDSCALE));
zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
zoom_reset->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
zoom_reset->set_shortcut(ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent"));
zoom_reset->set_shortcut_context(this);
zoom_reset->set_focus_mode(FOCUS_ACCESSIBILITY);
zoom_reset->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
// Prevent the button's size from changing when the text size changes
zoom_reset->set_custom_minimum_size(Size2(56 * EDSCALE, 0));
add_child(zoom_reset);
zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_reset));
zoom_plus = memnew(Button);
zoom_plus->set_accessibility_name(TTRC("Zoom In"));
zoom_plus->set_flat(true);
zoom_plus->set_shortcut(ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_plus", TTRC("Zoom In"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));
zoom_plus->set_shortcut_context(this);
zoom_plus->set_focus_mode(FOCUS_ACCESSIBILITY);
add_child(zoom_plus);
zoom_plus->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_plus));
_update_zoom_label();
add_theme_constant_override("separation", 0);
}

View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* editor_zoom_widget.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
class EditorZoomWidget : public HBoxContainer {
GDCLASS(EditorZoomWidget, HBoxContainer);
Button *zoom_minus = nullptr;
Button *zoom_reset = nullptr;
Button *zoom_plus = nullptr;
float zoom = 1.0;
float min_zoom = 1.0 / 128;
float max_zoom = 128.0;
void _update_zoom_label();
void _button_zoom_minus();
void _button_zoom_reset();
void _button_zoom_plus();
protected:
void _notification(int p_what);
static void _bind_methods();
public:
EditorZoomWidget();
float get_zoom();
void set_zoom(float p_zoom);
void set_zoom_by_increments(int p_increment_count, bool p_integer_only = false);
float get_min_zoom();
float get_max_zoom();
// It's best to setup simultaneously, so min < max can be checked easily.
void setup_zoom_limits(float p_min, float p_max);
// Sets the shortcut context for the zoom buttons. By default their context is this EditorZoomWidget control.
void set_shortcut_context(Node *p_node) const;
};

View File

@@ -0,0 +1,304 @@
/**************************************************************************/
/* 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);
if (is_ready()) {
_reparent_and_show();
} else {
callable_mp(this, &ProgressDialog::_reparent_and_show).call_deferred();
}
}
void ProgressDialog::_reparent_and_show() {
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

@@ -0,0 +1,108 @@
/**************************************************************************/
/* progress_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/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 BackgroundProgress : public HBoxContainer {
GDCLASS(BackgroundProgress, HBoxContainer);
_THREAD_SAFE_CLASS_
struct Task {
HBoxContainer *hb = nullptr;
ProgressBar *progress = nullptr;
};
HashMap<String, Task> tasks;
HashMap<String, int> updates;
void _update();
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);
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);
};
class PanelContainer;
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;
HashMap<String, Task> tasks;
PanelContainer *center_panel = nullptr;
VBoxContainer *main = nullptr;
LocalVector<Window *> host_windows;
Size2 main_border_size;
static ProgressDialog *singleton;
void _popup();
void _cancel_pressed();
void _update_ui();
void _reparent_and_show();
bool canceled = false;
protected:
void _notification(int p_what);
public:
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_host_window(Window *p_window);
void remove_host_window(Window *p_window);
ProgressDialog();
};

View File

@@ -0,0 +1,303 @@
/**************************************************************************/
/* touch_actions_panel.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 "touch_actions_panel.h"
#include "core/input/input.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"
#include "scene/gui/texture_rect.h"
#include "scene/resources/style_box_flat.h"
void TouchActionsPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
DisplayServer::get_singleton()->set_hardware_keyboard_connection_change_callback(callable_mp(this, &TouchActionsPanel::_hardware_keyboard_connected));
_hardware_keyboard_connected(DisplayServer::get_singleton()->has_hardware_keyboard());
if (!is_floating) {
get_parent()->move_child(this, embedded_panel_index);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
set_process_input(is_visible_in_tree());
} break;
case NOTIFICATION_THEME_CHANGED: {
if (is_floating) {
drag_handle->set_texture(get_editor_theme_icon(SNAME("DragHandle")));
layout_toggle_button->set_button_icon(get_editor_theme_icon(SNAME("Orientation")));
lock_panel_button->set_button_icon(get_editor_theme_icon(SNAME("Lock")));
} else {
if (embedded_panel_index == 1) {
panel_pos_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignLeftWide")));
} else {
panel_pos_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignRightWide")));
}
}
save_button->set_button_icon(get_editor_theme_icon(SNAME("Save")));
delete_button->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
undo_button->set_button_icon(get_editor_theme_icon(SNAME("UndoRedo")));
redo_button->set_button_icon(get_editor_theme_icon(SNAME("Redo")));
cut_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCut")));
copy_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
paste_button->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));
} break;
}
}
void TouchActionsPanel::input(const Ref<InputEvent> &event) {
if (ctrl_btn_pressed) {
event->call(SNAME("set_ctrl_pressed"), true);
}
if (shift_btn_pressed) {
event->call(SNAME("set_shift_pressed"), true);
}
if (alt_btn_pressed) {
event->call(SNAME("set_alt_pressed"), true);
}
}
void TouchActionsPanel::_hardware_keyboard_connected(bool p_connected) {
set_visible(!p_connected);
}
void TouchActionsPanel::_simulate_editor_shortcut(const String &p_shortcut_name) {
Ref<Shortcut> shortcut = ED_GET_SHORTCUT(p_shortcut_name);
if (shortcut.is_valid() && !shortcut->get_events().is_empty()) {
Ref<InputEventKey> event = shortcut->get_events()[0];
if (event.is_valid()) {
event->set_pressed(true);
Input::get_singleton()->parse_input_event(event);
}
}
}
void TouchActionsPanel::_simulate_key_press(Key p_keycode) {
Ref<InputEventKey> event;
event.instantiate();
event->set_keycode(p_keycode);
event->set_pressed(true);
Input::get_singleton()->parse_input_event(event);
}
void TouchActionsPanel::_on_modifier_button_toggled(bool p_pressed, int p_modifier) {
switch ((Modifier)p_modifier) {
case MODIFIER_CTRL:
ctrl_btn_pressed = p_pressed;
break;
case MODIFIER_SHIFT:
shift_btn_pressed = p_pressed;
break;
case MODIFIER_ALT:
alt_btn_pressed = p_pressed;
break;
}
}
Button *TouchActionsPanel::_add_new_action_button(const String &p_shortcut, const String &p_name, Key p_keycode) {
Button *action_button = memnew(Button);
action_button->set_theme_type_variation("FlatMenuButton");
action_button->set_accessibility_name(p_name);
action_button->set_focus_mode(FOCUS_ACCESSIBILITY);
action_button->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
if (p_keycode == Key::NONE) {
action_button->connect(SceneStringName(pressed), callable_mp(this, &TouchActionsPanel::_simulate_editor_shortcut).bind(p_shortcut));
} else {
action_button->connect(SceneStringName(pressed), callable_mp(this, &TouchActionsPanel::_simulate_key_press).bind(p_keycode));
}
box->add_child(action_button);
return action_button;
}
void TouchActionsPanel::_add_new_modifier_button(Modifier p_modifier) {
String text;
switch (p_modifier) {
case MODIFIER_CTRL:
text = "Ctrl";
break;
case MODIFIER_SHIFT:
text = "Shift";
break;
case MODIFIER_ALT:
text = "Alt";
break;
}
Button *toggle_button = memnew(Button);
toggle_button->set_text(text);
toggle_button->set_toggle_mode(true);
toggle_button->set_theme_type_variation("FlatMenuButton");
toggle_button->set_accessibility_name(text);
toggle_button->set_focus_mode(FOCUS_ACCESSIBILITY);
toggle_button->connect(SceneStringName(toggled), callable_mp(this, &TouchActionsPanel::_on_modifier_button_toggled).bind((int)p_modifier));
box->add_child(toggle_button);
}
void TouchActionsPanel::_on_drag_handle_gui_input(const Ref<InputEvent> &p_event) {
if (locked_panel) {
return;
}
Ref<InputEventMouseButton> mouse_button_event = p_event;
if (mouse_button_event.is_valid() && mouse_button_event->get_button_index() == MouseButton::LEFT) {
if (mouse_button_event->is_pressed()) {
dragging = true;
drag_offset = mouse_button_event->get_position();
} else {
if (dragging) {
dragging = false;
EditorSettings::get_singleton()->set("_touch_actions_panel_position", get_position());
EditorSettings::get_singleton()->save();
}
}
}
Ref<InputEventMouseMotion> mouse_motion_event = p_event;
if (dragging && mouse_motion_event.is_valid()) {
Vector2 new_position = get_position() + mouse_motion_event->get_relative();
const float margin = 25.0;
Vector2 parent_size = get_parent_area_size();
Vector2 panel_size = get_size();
new_position = new_position.clamp(Vector2(margin, margin), parent_size - panel_size - Vector2(margin, margin));
set_position(new_position);
}
}
void TouchActionsPanel::_switch_layout() {
box->set_vertical(!box->is_vertical());
reset_size();
queue_redraw();
EditorSettings::get_singleton()->set("_touch_actions_panel_vertical_layout", box->is_vertical());
EditorSettings::get_singleton()->save();
}
void TouchActionsPanel::_lock_panel_toggled(bool p_pressed) {
locked_panel = p_pressed;
layout_toggle_button->set_visible(!p_pressed);
drag_handle->set_visible(!p_pressed);
reset_size();
queue_redraw();
}
void TouchActionsPanel::_switch_embedded_panel_side() {
if (embedded_panel_index == 0) {
embedded_panel_index = 1;
panel_pos_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignLeftWide")));
} else {
embedded_panel_index = 0;
panel_pos_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignRightWide")));
}
get_parent()->move_child(this, embedded_panel_index); // Parent is a hbox with only two children -- TouchActionsPanel and main Editor UI.
EditorSettings::get_singleton()->set("_touch_actions_panel_embed_index", embedded_panel_index);
EditorSettings::get_singleton()->save();
}
TouchActionsPanel::TouchActionsPanel() {
int panel_mode = EDITOR_GET("interface/touchscreen/touch_actions_panel");
is_floating = panel_mode == 2;
if (is_floating) {
Ref<StyleBoxFlat> panel_style;
panel_style.instantiate();
panel_style->set_bg_color(Color(0.1, 0.1, 0.1, 1));
panel_style->set_border_color(Color(0.3, 0.3, 0.3, 1));
panel_style->set_border_width_all(3);
panel_style->set_corner_radius_all(10);
panel_style->set_content_margin_all(12);
add_theme_style_override(SceneStringName(panel), panel_style);
set_position(EDITOR_DEF("_touch_actions_panel_position", Point2(480, 480))); // Dropped it here for no good reason — users can move it anyway.
}
box = memnew(BoxContainer);
box->add_theme_constant_override("separation", 20);
if (is_floating) {
box->set_vertical(EDITOR_DEF("_touch_actions_panel_vertical_layout", false));
} else {
box->set_vertical(true);
}
add_child(box);
if (is_floating) {
drag_handle = memnew(TextureRect);
drag_handle->set_custom_minimum_size(Size2(40, 40));
drag_handle->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
drag_handle->connect(SceneStringName(gui_input), callable_mp(this, &TouchActionsPanel::_on_drag_handle_gui_input));
box->add_child(drag_handle);
layout_toggle_button = memnew(Button);
layout_toggle_button->set_theme_type_variation("FlatMenuButton");
layout_toggle_button->set_accessibility_name(TTRC("Switch Layout"));
layout_toggle_button->set_focus_mode(FOCUS_ACCESSIBILITY);
layout_toggle_button->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
layout_toggle_button->connect(SceneStringName(pressed), callable_mp(this, &TouchActionsPanel::_switch_layout));
box->add_child(layout_toggle_button);
lock_panel_button = memnew(Button);
lock_panel_button->set_toggle_mode(true);
lock_panel_button->set_theme_type_variation("FlatMenuButton");
lock_panel_button->set_accessibility_name(TTRC("Lock Panel"));
lock_panel_button->set_focus_mode(FOCUS_ACCESSIBILITY);
lock_panel_button->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
lock_panel_button->connect(SceneStringName(toggled), callable_mp(this, &TouchActionsPanel::_lock_panel_toggled));
box->add_child(lock_panel_button);
} else {
panel_pos_button = memnew(Button);
panel_pos_button->set_theme_type_variation("FlatMenuButton");
panel_pos_button->set_accessibility_name(TTRC("Switch Embedded Panel Position"));
panel_pos_button->set_focus_mode(FOCUS_ACCESSIBILITY);
panel_pos_button->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
panel_pos_button->connect(SceneStringName(pressed), callable_mp(this, &TouchActionsPanel::_switch_embedded_panel_side));
box->add_child(panel_pos_button);
embedded_panel_index = EDITOR_DEF("_touch_actions_panel_embed_index", 0);
}
ColorRect *separator = memnew(ColorRect);
separator->set_color(Color(0.5, 0.5, 0.5));
separator->set_custom_minimum_size(Size2(2, 2));
box->add_child(separator);
// Add action buttons.
save_button = _add_new_action_button("editor/save_scene", TTRC("Save"));
delete_button = _add_new_action_button("", TTRC("Delete"), Key::KEY_DELETE);
undo_button = _add_new_action_button("ui_undo", TTRC("Undo"));
redo_button = _add_new_action_button("ui_redo", TTRC("Redo"));
cut_button = _add_new_action_button("ui_cut", TTRC("Cut"));
copy_button = _add_new_action_button("ui_copy", TTRC("Copy"));
paste_button = _add_new_action_button("ui_paste", TTRC("Paste"));
_add_new_modifier_button(MODIFIER_CTRL);
_add_new_modifier_button(MODIFIER_SHIFT);
_add_new_modifier_button(MODIFIER_ALT);
}

View File

@@ -0,0 +1,92 @@
/**************************************************************************/
/* touch_actions_panel.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/panel_container.h"
class BoxContainer;
class Button;
class TextureRect;
class TouchActionsPanel : public PanelContainer {
GDCLASS(TouchActionsPanel, PanelContainer);
private:
BoxContainer *box = nullptr;
Button *save_button = nullptr;
Button *delete_button = nullptr;
Button *undo_button = nullptr;
Button *redo_button = nullptr;
Button *cut_button = nullptr;
Button *copy_button = nullptr;
Button *paste_button = nullptr;
TextureRect *drag_handle = nullptr;
Button *layout_toggle_button = nullptr;
Button *lock_panel_button = nullptr;
Button *panel_pos_button = nullptr;
bool locked_panel = false;
bool dragging = false;
Vector2 drag_offset;
enum Modifier {
MODIFIER_CTRL,
MODIFIER_SHIFT,
MODIFIER_ALT
};
bool ctrl_btn_pressed = false;
bool shift_btn_pressed = false;
bool alt_btn_pressed = false;
bool is_floating = false; // Embedded panel mode is default.
int embedded_panel_index = 0;
void _notification(int p_what);
virtual void input(const Ref<InputEvent> &event) override;
void _simulate_editor_shortcut(const String &p_shortcut_name);
void _simulate_key_press(Key p_keycode);
void _on_drag_handle_gui_input(const Ref<InputEvent> &p_event);
void _switch_layout();
void _lock_panel_toggled(bool p_pressed);
void _switch_embedded_panel_side();
Button *_add_new_action_button(const String &p_shortcut, const String &p_name, Key p_keycode = Key::NONE);
void _add_new_modifier_button(Modifier p_modifier);
void _on_modifier_button_toggled(bool p_pressed, int p_modifier);
void _hardware_keyboard_connected(bool p_connected);
public:
TouchActionsPanel();
};

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_theme_type_variation(SceneStringName(FlatButton));
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();
};