summaryrefslogtreecommitdiff
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/editor_log.cpp6
-rw-r--r--editor/editor_log.h2
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/editor_settings.cpp1
-rw-r--r--editor/editor_toaster.cpp511
-rw-r--r--editor/editor_toaster.h116
-rw-r--r--editor/icons/Notification.svg1
-rw-r--r--editor/icons/NotificationDisabled.svg1
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp2
-rw-r--r--editor/rename_dialog.cpp2
-rw-r--r--editor/rename_dialog.h2
12 files changed, 645 insertions, 5 deletions
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 346b93a87c..251e1c2385 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -37,7 +37,7 @@
#include "scene/gui/center_container.h"
#include "scene/resources/font.h"
-void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) {
+void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
EditorLog *self = (EditorLog *)p_self;
if (self->current != Thread::get_caller_id()) {
return;
@@ -50,6 +50,10 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f
err_str = String(p_file) + ":" + itos(p_line) + " - " + String(p_error);
}
+ if (p_editor_notify) {
+ err_str += " (User)";
+ }
+
if (p_type == ERR_HANDLER_WARNING) {
self->add_message(err_str, MSG_TYPE_WARNING);
} else {
diff --git a/editor/editor_log.h b/editor/editor_log.h
index 6cbf4bedee..43cc5680bd 100644
--- a/editor/editor_log.h
+++ b/editor/editor_log.h
@@ -136,7 +136,7 @@ private:
bool is_loading_state = false; // Used to disable saving requests while loading (some signals from buttons will try trigger a save, which happens during loading).
Timer *save_state_timer;
- 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, ErrorHandlerType p_type);
+ static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type);
ErrorHandlerList eh;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 2f92f60d5e..ffcb75ba2c 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -95,6 +95,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_spin_slider.h"
#include "editor/editor_themes.h"
+#include "editor/editor_toaster.h"
#include "editor/editor_translation_parser.h"
#include "editor/export_template_manager.h"
#include "editor/filesystem_dock.h"
@@ -6748,6 +6749,9 @@ EditorNode::EditorNode() {
bottom_panel_hb_editors->set_h_size_flags(Control::SIZE_EXPAND_FILL);
bottom_panel_hb->add_child(bottom_panel_hb_editors);
+ editor_toaster = memnew(EditorToaster);
+ bottom_panel_hb->add_child(editor_toaster);
+
VBoxContainer *version_info_vbc = memnew(VBoxContainer);
bottom_panel_hb->add_child(version_info_vbc);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 73feeecfee..5c89823ad8 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -37,6 +37,7 @@
#include "editor/editor_folding.h"
#include "editor/editor_native_shader_source_visualizer.h"
#include "editor/editor_run.h"
+#include "editor/editor_toaster.h"
#include "editor/inspector_dock.h"
#include "editor/property_editor.h"
#include "editor/scene_tree_dock.h"
@@ -438,6 +439,7 @@ private:
HBoxContainer *bottom_panel_hb;
HBoxContainer *bottom_panel_hb_editors;
VBoxContainer *bottom_panel_vb;
+ EditorToaster *editor_toaster;
LinkButton *version_btn;
Button *bottom_panel_raise;
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 223cc6650c..1da9213b89 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -442,6 +442,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("interface/editor/hide_console_window", false);
_initial_set("interface/editor/mouse_extra_buttons_navigate_history", true);
_initial_set("interface/editor/save_each_scene_on_quit", true); // Regression
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_internal_errors_in_toast_notifications", 0, "Auto,Enabled,Disabled")
// Inspector
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1")
diff --git a/editor/editor_toaster.cpp b/editor/editor_toaster.cpp
new file mode 100644
index 0000000000..0d45a7f1d8
--- /dev/null
+++ b/editor/editor_toaster.cpp
@@ -0,0 +1,511 @@
+/*************************************************************************/
+/* editor_toaster.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+#include "scene/gui/label.h"
+#include "scene/gui/panel_container.h"
+
+#include "editor_toaster.h"
+
+EditorToaster *EditorToaster::singleton;
+
+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->update();
+ }
+ } 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->update();
+ }
+ }
+
+ // Change alpha over time.
+ bool needs_update = false;
+ for (const KeyValue<Control *, Toast> &element : toasts) {
+ Color modulate = element.key->get_modulate();
+
+ // Change alpha over time.
+ if (element.value.popped && modulate.a < 1.0) {
+ modulate.a += delta * 3;
+ element.key->set_modulate(modulate);
+ } else if (!element.value.popped && modulate.a > 0.0) {
+ modulate.a -= delta * 2;
+ element.key->set_modulate(modulate);
+ }
+
+ // Hide element if it is not visible anymore.
+ if (modulate.a <= 0) {
+ element.key->hide();
+ needs_update = true;
+ }
+ }
+
+ if (needs_update) {
+ _update_vbox_position();
+ _update_disable_notifications_button();
+ main_button->update();
+ }
+ } break;
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ if (vbox_container->is_visible()) {
+ main_button->set_icon(get_theme_icon(SNAME("Notification"), SNAME("EditorIcons")));
+ } else {
+ main_button->set_icon(get_theme_icon(SNAME("NotificationDisabled"), SNAME("EditorIcons")));
+ }
+ disable_notifications_button->set_icon(get_theme_icon(SNAME("NotificationDisabled"), SNAME("EditorIcons")));
+
+ // Styleboxes background.
+ info_panel_style_background->set_bg_color(get_theme_color("base_color", "Editor"));
+
+ warning_panel_style_background->set_bg_color(get_theme_color("base_color", "Editor"));
+ warning_panel_style_background->set_border_color(get_theme_color("warning_color", "Editor"));
+
+ error_panel_style_background->set_bg_color(get_theme_color("base_color", "Editor"));
+ error_panel_style_background->set_border_color(get_theme_color("error_color", "Editor"));
+
+ // Styleboxes progress.
+ info_panel_style_progress->set_bg_color(get_theme_color("base_color", "Editor").lightened(0.03));
+
+ warning_panel_style_progress->set_bg_color(get_theme_color("base_color", "Editor").lightened(0.03));
+ warning_panel_style_progress->set_border_color(get_theme_color("warning_color", "Editor"));
+
+ error_panel_style_progress->set_bg_color(get_theme_color("base_color", "Editor").lightened(0.03));
+ error_panel_style_progress->set_border_color(get_theme_color("error_color", "Editor"));
+
+ main_button->update();
+ disable_notifications_button->update();
+ } break;
+ case NOTIFICATION_TRANSFORM_CHANGED: {
+ _update_vbox_position();
+ _update_disable_notifications_button();
+ } break;
+ default:
+ 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) {
+ if (!EditorToaster::get_singleton()) {
+ 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;
+ if (p_errorexp && p_errorexp[0]) {
+ err_str = p_errorexp;
+ } else {
+ err_str = String(p_error);
+ }
+ String tooltip_str = String(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;
+ }
+ }
+
+ if (p_type == ERR_HANDLER_WARNING) {
+ EditorToaster::get_singleton()->popup_str(err_str, SEVERITY_WARNING, tooltip_str);
+ } else {
+ EditorToaster::get_singleton()->popup_str(err_str, SEVERITY_ERROR, 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 (unsigned int i = 0; i < to_delete.size(); i++) {
+ vbox_container->remove_child(to_delete[i]);
+ to_delete[i]->queue_delete();
+ toasts.erase(to_delete[i]);
+ }
+}
+
+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("accent_color", "Editor");
+ break;
+ case SEVERITY_WARNING:
+ color = get_theme_color("warning_color", "Editor");
+ break;
+ case SEVERITY_ERROR:
+ color = get_theme_color("error_color", "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::range_lerp(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_icon(get_theme_icon(SNAME("Notification"), SNAME("EditorIcons")));
+ } else {
+ main_button->set_icon(get_theme_icon(SNAME("NotificationDisabled"), SNAME("EditorIcons")));
+ }
+ _update_disable_notifications_button();
+}
+
+void EditorToaster::_repop_old() {
+ // Repop olds, up to max_temporary_count
+ bool needs_update = false;
+ int visible = 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++;
+ if (visible >= max_temporary_count) {
+ break;
+ }
+ }
+ if (needs_update) {
+ _update_vbox_position();
+ _update_disable_notifications_button();
+ main_button->update();
+ }
+}
+
+Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_time, String p_tooltip) {
+ // Create the panel according to the severity.
+ PanelContainer *panel = memnew(PanelContainer);
+ panel->set_tooltip(p_tooltip);
+ switch (p_severity) {
+ case SEVERITY_INFO:
+ panel->add_theme_style_override("panel", info_panel_style_background);
+ break;
+ case SEVERITY_WARNING:
+ panel->add_theme_style_override("panel", warning_panel_style_background);
+ break;
+ case SEVERITY_ERROR:
+ panel->add_theme_style_override("panel", error_panel_style_background);
+ break;
+ default:
+ break;
+ }
+ panel->set_modulate(Color(1, 1, 1, 0));
+ panel->connect("draw", callable_bind(callable_mp(this, &EditorToaster::_draw_progress), 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);
+
+ // Close button.
+ if (p_time > 0.0) {
+ Button *close_button = memnew(Button);
+ close_button->set_flat(true);
+ close_button->set_icon(get_theme_icon("Close", "EditorIcons"));
+ close_button->connect("pressed", callable_bind(callable_mp(this, &EditorToaster::close), panel));
+ hbox_container->add_child(close_button);
+ }
+
+ toasts[panel].severity = p_severity;
+ if (p_time > 0.0) {
+ toasts[panel].duration = p_time;
+ toasts[panel].remaining_time = p_time;
+ } else {
+ toasts[panel].duration = -1.0;
+ }
+ toasts[panel].popped = true;
+ vbox_container->add_child(panel);
+ _auto_hide_or_free_toasts();
+ _update_vbox_position();
+ _update_disable_notifications_button();
+ main_button->update();
+
+ return panel;
+}
+
+void EditorToaster::popup_str(String p_message, Severity p_severity, String p_tooltip) {
+ // 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) {
+ Label *label = memnew(Label);
+
+ control = popup(label, p_severity, default_message_duration, p_tooltip);
+ toasts[control].message = p_message;
+ toasts[control].tooltip = p_tooltip;
+ toasts[control].count = 1;
+ } else {
+ if (toasts[control].popped) {
+ toasts[control].count += 1;
+ } else {
+ toasts[control].count = 1;
+ }
+ toasts[control].remaining_time = toasts[control].duration;
+ toasts[control].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->update();
+ }
+
+ // Retrieve the label back then update the text.
+ Label *label = Object::cast_to<Label>(control->get_child(0)->get_child(0));
+ ERR_FAIL_COND(!label);
+ if (toasts[control].count == 1) {
+ label->set_text(p_message);
+ } else {
+ label->set_text(vformat("%s (%d)", p_message, toasts[control].count));
+ }
+}
+
+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;
+}
+
+EditorToaster *EditorToaster::get_singleton() {
+ return singleton;
+}
+
+EditorToaster::EditorToaster() {
+ set_notify_transform(true);
+ set_process_internal(true);
+
+ // VBox.
+ vbox_container = memnew(VBoxContainer);
+ vbox_container->set_as_top_level(true);
+ vbox_container->connect("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_default_margin(SIDE_LEFT, int(stylebox_radius * 2.5));
+ boxes[i]->set_default_margin(SIDE_RIGHT, int(stylebox_radius * 2.5));
+ boxes[i]->set_default_margin(SIDE_TOP, 3);
+ boxes[i]->set_default_margin(SIDE_BOTTOM, 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_flat(true);
+ main_button->connect("pressed", callable_mp(this, &EditorToaster::_set_notifications_enabled), varray(true));
+ main_button->connect("pressed", callable_mp(this, &EditorToaster::_repop_old));
+ main_button->connect("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("panel", info_panel_style_background);
+ add_child(disable_notifications_panel);
+
+ disable_notifications_button = memnew(Button);
+ disable_notifications_button->set_flat(true);
+ disable_notifications_button->connect("pressed", callable_mp(this, &EditorToaster::_set_notifications_enabled), varray(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);
+}
diff --git a/editor/editor_toaster.h b/editor/editor_toaster.h
new file mode 100644
index 0000000000..aac80d8fb1
--- /dev/null
+++ b/editor/editor_toaster.h
@@ -0,0 +1,116 @@
+/*************************************************************************/
+/* editor_toaster.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef EDITOR_TOASTER_H
+#define EDITOR_TOASTER_H
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/popup.h"
+
+#include "core/string/ustring.h"
+#include "core/templates/local_vector.h"
+
+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;
+ PanelContainer *disable_notifications_panel;
+ Button *disable_notifications_button;
+
+ VBoxContainer *vbox_container;
+ 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;
+
+ // Messages
+ String message;
+ String tooltip;
+ int count = 0;
+ };
+ Map<Control *, Toast> toasts;
+
+ 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);
+ 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();
+
+protected:
+ 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, String p_tooltip = String());
+ void popup_str(String p_message, Severity p_severity = SEVERITY_INFO, String p_tooltip = String());
+ void close(Control *p_control);
+
+ EditorToaster();
+ ~EditorToaster();
+};
+
+VARIANT_ENUM_CAST(EditorToaster::Severity);
+
+#endif // EDITOR_TOASTER_H
diff --git a/editor/icons/Notification.svg b/editor/icons/Notification.svg
new file mode 100644
index 0000000000..1f1f9c3e15
--- /dev/null
+++ b/editor/icons/Notification.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m12.089506 1.2353795c-.498141-.2384823-1.095292-.027987-1.333775.4701537-.01372.028981-.02341.059557-.03428.089693-.01467.023016-.02777.046925-.04071.071459-.04899-.00527-.09728-.00862-.146087-.011473-1.4730257-.6255101-3.1777024.0153376-3.8738627 1.4563251l-1.7272425 3.6078572s-.3364181.7034345-.8079671 1.1268133c-.00105.0009371-.00239.00174-.00344.00268-.2721193.1337295-.5707545.185826-.8605632.0470816-.4981411-.2384824-1.0952924-.0279876-1.3337749.4701537-.01605.033526-.029907.066894-.041944.1011828-.018769.030371-.036749.06319-.052515.096122-.2384825.4981412-.027988 1.0952923.4701537 1.3337751l9.0196437 4.318106c.498141.238482 1.095292.02799 1.333775-.470154.01577-.03293.0301-.0675.04191-.1012.0192-.03086.0365-.06257.05255-.0961.238483-.498141.02799-1.095292-.470153-1.333775-.901965-.43181-.03834-2.235739-.03834-2.235739l1.727237-3.6078618c.715447-1.4944233.08396-3.2858776-1.410461-4.0013247.238482-.4981411.02799-1.0952926-.470154-1.333775zm-5.5145786 11.3714015c-.322341.673306-.037829 1.480435.6354753 1.802775.6733031.32234 1.4804334.03783 1.8027749-.635476z" fill="#e6e6e6"/></svg>
diff --git a/editor/icons/NotificationDisabled.svg b/editor/icons/NotificationDisabled.svg
new file mode 100644
index 0000000000..0e4465bee8
--- /dev/null
+++ b/editor/icons/NotificationDisabled.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m11.705078 1.1386719c-.389281-.0180576-.770356.1928007-.949219.5664062-.01372.028981-.024286.0597078-.035156.0898438-.01467.023016-.026122.0477316-.039062.0722656-.04899-.00527-.097678-.0088657-.146485-.0117187-1.4730253-.6255102-3.1788394.0160437-3.8749998 1.4570312l-1.7265624 3.6074219s-.3370448.7035743-.8085938 1.1269531l-.0019531.0019531c-.2721193.1337295-.5715196.1856194-.8613281.046875-.4981413-.2384824-1.0955019-.0274382-1.3339844.4707031-.01605.0335262-.0289787.0672737-.0410156.1015626-.0187691.0303709-.0369684.0627711-.0527344.0957031-.2384825.4981412-.0293917 1.0955019.46875 1.3339841l.3398437.16211 10.8984379-6.9394535c-.263272-.3070418-.592225-.5660832-.980469-.7519531.238482-.4981411.027441-1.0935489-.470703-1.3320313-.124536-.0596206-.255006-.091637-.384766-.0976562zm2.435547 2.8652343a.94188849 1 0 0 0 -.566406.1386719l-12.1171878 7.7148439a.94188849 1 0 0 0 -.3222656 1.373047.94188849 1 0 0 0 1.2910156.341797l12.1171878-7.7148441a.94188849 1 0 0 0 .322265-1.3710938.94188849 1 0 0 0 -.724609-.4824219zm-.509766 3.2753907-7.3867184 4.7050781 5.0781254 2.431641c.498141.238482 1.095501.027442 1.333984-.470704.01577-.03293.031159-.067862.042969-.101562.0192-.03086.036684-.062173.052734-.095703.238483-.498141.02744-1.095501-.470703-1.333985-.901965-.431809-.039063-2.236328-.039062-2.236328zm-7.0566402 5.3281251c-.322341.673306-.0365856 1.480394.6367187 1.802734.6733031.32234 1.4803929.036588 1.8027344-.636718z" fill="#e6e6e6"/></svg>
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 106f1750b1..157d28da4f 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -172,7 +172,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
} else if (p_name == "size_in_atlas") {
Vector2i as_vector2i = Vector2i(p_value);
bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, as_vector2i, tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
- ERR_FAIL_COND_V(!has_room_for_tile, false);
+ ERR_FAIL_COND_V_EDMSG(!has_room_for_tile, false, "Invalid size or not enough room in the atlas for the tile.");
tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
emit_signal(SNAME("changed"), "size_in_atlas");
return true;
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index 9063b5c6f8..a5e1b0eab8 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -459,7 +459,7 @@ String RenameDialog::_substitute(const String &subject, const Node *node, int co
return result;
}
-void RenameDialog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) {
+void RenameDialog::_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) {
RenameDialog *self = (RenameDialog *)p_self;
String source_file(p_file);
diff --git a/editor/rename_dialog.h b/editor/rename_dialog.h
index 7990862b37..4db3ef6740 100644
--- a/editor/rename_dialog.h
+++ b/editor/rename_dialog.h
@@ -63,7 +63,7 @@ class RenameDialog : public ConfirmationDialog {
String _postprocess(const String &subject);
void _update_preview(String new_text = "");
void _update_preview_int(int new_value = 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, ErrorHandlerType p_type);
+ 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);
SceneTreeEditor *scene_tree_editor;
UndoRedo *undo_redo;