From 0587e5e018f832eba8771a1cb1b645364b45f354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Wed, 22 Sep 2021 17:36:40 +0200 Subject: [PATCH] Implement toast notifications in the editor --- core/debugger/engine_debugger.h | 2 +- core/debugger/local_debugger.cpp | 2 +- core/debugger/local_debugger.h | 2 +- core/debugger/remote_debugger.cpp | 6 +- core/debugger/remote_debugger.h | 4 +- core/debugger/script_debugger.cpp | 4 +- core/debugger/script_debugger.h | 2 +- core/error/error_macros.cpp | 36 +- core/error/error_macros.h | 255 +++++++-- core/extension/gdnative_interface.cpp | 6 +- core/io/logger.cpp | 10 +- core/io/logger.h | 4 +- core/os/os.cpp | 4 +- core/os/os.h | 2 +- drivers/unix/os_unix.cpp | 2 +- drivers/unix/os_unix.h | 2 +- editor/editor_log.cpp | 6 +- editor/editor_log.h | 2 +- editor/editor_node.cpp | 4 + editor/editor_node.h | 2 + editor/editor_settings.cpp | 1 + editor/editor_toaster.cpp | 511 ++++++++++++++++++ editor/editor_toaster.h | 116 ++++ editor/icons/Notification.svg | 1 + editor/icons/NotificationDisabled.svg | 1 + .../tiles/tile_set_atlas_source_editor.cpp | 2 +- editor/rename_dialog.cpp | 2 +- editor/rename_dialog.h | 2 +- modules/gdnative/gdnative/gdnative.cpp | 6 +- modules/gdscript/gdscript.cpp | 8 +- modules/gdscript/gdscript_vm.cpp | 2 +- .../gdscript/tests/gdscript_test_runner.cpp | 2 +- modules/gdscript/tests/gdscript_test_runner.h | 2 +- modules/gltf/gltf_document.cpp | 4 +- modules/visual_script/visual_script.cpp | 2 +- platform/osx/os_osx.mm | 2 +- .../renderer_rd/shader_compiler_rd.cpp | 2 +- tests/test_tools.h | 2 +- 38 files changed, 930 insertions(+), 95 deletions(-) create mode 100644 editor/editor_toaster.cpp create mode 100644 editor/editor_toaster.h create mode 100644 editor/icons/Notification.svg create mode 100644 editor/icons/NotificationDisabled.svg diff --git a/core/debugger/engine_debugger.h b/core/debugger/engine_debugger.h index 22c6ef943e..41142bf305 100644 --- a/core/debugger/engine_debugger.h +++ b/core/debugger/engine_debugger.h @@ -128,7 +128,7 @@ public: virtual void poll_events(bool p_is_idle) {} virtual void send_message(const String &p_msg, const Array &p_data) = 0; - virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) = 0; + virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) = 0; virtual void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0; virtual ~EngineDebugger(); diff --git a/core/debugger/local_debugger.cpp b/core/debugger/local_debugger.cpp index f7e56351b0..60aa3e6be7 100644 --- a/core/debugger/local_debugger.cpp +++ b/core/debugger/local_debugger.cpp @@ -358,7 +358,7 @@ void LocalDebugger::send_message(const String &p_message, const Array &p_args) { // print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args))); } -void LocalDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) { +void LocalDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) { print_line("ERROR: '" + (p_descr.is_empty() ? p_err : p_descr) + "'"); } diff --git a/core/debugger/local_debugger.h b/core/debugger/local_debugger.h index e793b2a859..cb59eb82e9 100644 --- a/core/debugger/local_debugger.h +++ b/core/debugger/local_debugger.h @@ -50,7 +50,7 @@ private: public: void debug(bool p_can_continue, bool p_is_error_breakpoint); void send_message(const String &p_message, const Array &p_args); - void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type); + void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type); LocalDebugger(); ~LocalDebugger(); diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index 032c7d55c0..9967d1e361 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -455,7 +455,7 @@ Error RemoteDebugger::_put_msg(String p_message, Array p_data) { return err; } -void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type) { +void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type) { if (p_type == ERR_HANDLER_SCRIPT) { return; //ignore script errors, those go through debugger } @@ -475,7 +475,7 @@ void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char * } // send_error will lock internally. - rd->script_debugger->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si); + rd->script_debugger->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type, si); } void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error) { @@ -605,7 +605,7 @@ void RemoteDebugger::send_message(const String &p_message, const Array &p_args) } } -void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) { +void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) { ErrorMessage oe; oe.error = p_err; oe.error_descr = p_descr; diff --git a/core/debugger/remote_debugger.h b/core/debugger/remote_debugger.h index 28e670747e..73799e3f81 100644 --- a/core/debugger/remote_debugger.h +++ b/core/debugger/remote_debugger.h @@ -89,7 +89,7 @@ private: PrintHandlerList phl; static void _print_handler(void *p_this, const String &p_string, bool p_error); ErrorHandlerList eh; - static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type); + static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type); ErrorMessage _create_overflow_error(const String &p_what, const String &p_descr); Error _put_msg(String p_message, Array p_data); @@ -111,7 +111,7 @@ public: // Overrides void poll_events(bool p_is_idle); void send_message(const String &p_message, const Array &p_args); - void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type); + void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type); void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false); RemoteDebugger(Ref p_peer); diff --git a/core/debugger/script_debugger.cpp b/core/debugger/script_debugger.cpp index 6d1e4ed101..70ec101a03 100644 --- a/core/debugger/script_debugger.cpp +++ b/core/debugger/script_debugger.cpp @@ -100,10 +100,10 @@ void ScriptDebugger::debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_i break_lang = prev; } -void ScriptDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector &p_stack_info) { +void ScriptDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type, const Vector &p_stack_info) { // Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way. error_stack_info.append_array(p_stack_info); - EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_type); + EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type); error_stack_info.resize(0); } diff --git a/core/debugger/script_debugger.h b/core/debugger/script_debugger.h index 9f034a5e5d..c1d0170334 100644 --- a/core/debugger/script_debugger.h +++ b/core/debugger/script_debugger.h @@ -71,7 +71,7 @@ public: void debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false); ScriptLanguage *get_break_language() const; - void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector &p_stack_info); + void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type, const Vector &p_stack_info); Vector get_error_stack_info() const; ScriptDebugger() {} }; diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 272dda97d8..719ea8afb5 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -65,45 +65,49 @@ void remove_error_handler(ErrorHandlerList *p_handler) { _global_unlock(); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, ErrorHandlerType p_type) { - _err_print_error(p_function, p_file, p_line, p_error, "", p_type); +// Errors without messages. +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error, "", p_editor_notify, p_type); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, ErrorHandlerType p_type) { - _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), "", p_type); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), "", p_editor_notify, p_type); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, ErrorHandlerType p_type) { +// Main error printing function. +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) { OS::get_singleton()->print_error(p_function, p_file, p_line, p_error, p_message, (Logger::ErrorType)p_type); _global_lock(); ErrorHandlerList *l = error_handler_list; while (l) { - l->errfunc(l->userdata, p_function, p_file, p_line, p_error, p_message, p_type); + l->errfunc(l->userdata, p_function, p_file, p_line, p_error, p_message, p_editor_notify, p_type); l = l->next; } _global_unlock(); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, ErrorHandlerType p_type) { - _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_type); +// Errors with message. (All combinations of p_error and p_message as String or char*.) +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_editor_notify, p_type); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, ErrorHandlerType p_type) { - _err_print_error(p_function, p_file, p_line, p_error, p_message.utf8().get_data(), p_type); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error, p_message.utf8().get_data(), p_editor_notify, p_type); } -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, ErrorHandlerType p_type) { - _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message.utf8().get_data(), p_type); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message.utf8().get_data(), p_editor_notify, p_type); } -void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message, bool fatal) { - String fstr(fatal ? "FATAL: " : ""); +// Index errors. (All combinations of p_message as String or char*.) +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message, bool p_editor_notify, bool p_fatal) { + String fstr(p_fatal ? "FATAL: " : ""); String err(fstr + "Index " + p_index_str + " = " + itos(p_index) + " is out of bounds (" + p_size_str + " = " + itos(p_size) + ")."); _err_print_error(p_function, p_file, p_line, err.utf8().get_data(), p_message); } -void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool fatal) { - _err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), fatal); +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify, bool p_fatal) { + _err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), p_fatal); } diff --git a/core/error/error_macros.h b/core/error/error_macros.h index 1bed8d366b..4eb862dce2 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -46,7 +46,7 @@ enum ErrorHandlerType { // Pointer to the error handler printing function. Reassign to any function to have errors printed. // Parameters: userdata, function, file, line, error, explanation, type. -typedef void (*ErrorHandlerFunc)(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type); +typedef void (*ErrorHandlerFunc)(void *, const char *, const char *, int p_line, const char *, const char *, bool p_editor_notify, ErrorHandlerType p_type); struct ErrorHandlerList { ErrorHandlerFunc errfunc = nullptr; @@ -61,14 +61,14 @@ void add_error_handler(ErrorHandlerList *p_handler); void remove_error_handler(ErrorHandlerList *p_handler); // Functions used by the error macros. -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR); -void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool fatal = false); -void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool fatal = false); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false); +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); #ifdef __GNUC__ //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying @@ -135,6 +135,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, prints `m_msg`, notifies in the editor, and the current function returns. + */ +#define ERR_FAIL_INDEX_EDMSG(m_index, m_size, m_msg) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \ + return; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_INDEX_V_MSG`. * Only use this macro if there is no sensible error message. @@ -160,6 +171,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures an integer index `m_index` is less than `m_size` and greater than or equal to 0. + * If not, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + */ +#define ERR_FAIL_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \ + return m_retval; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_INDEX_MSG` or `ERR_FAIL_INDEX_V_MSG`. * Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and @@ -215,6 +237,16 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li return; \ } else \ ((void)0) +/** + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, prints `m_msg`, notifies in the editor, and the current function returns. + */ +#define ERR_FAIL_UNSIGNED_INDEX_EDMSG(m_index, m_size, m_msg) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \ + return; \ + } else \ + ((void)0) /** * Try using `ERR_FAIL_UNSIGNED_INDEX_V_MSG`. @@ -241,6 +273,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures an unsigned integer index `m_index` is less than `m_size`. + * If not, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + */ +#define ERR_FAIL_UNSIGNED_INDEX_V_EDMSG(m_index, m_size, m_retval, m_msg) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \ + return m_retval; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_UNSIGNED_INDEX_MSG` or `ERR_FAIL_UNSIGNED_INDEX_V_MSG`. * Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and @@ -297,6 +340,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures a pointer `m_param` is not null. + * If it is null, prints `m_msg`, notifies in the editor, and the current function returns. + */ +#define ERR_FAIL_NULL_EDMSG(m_param, m_msg) \ + if (unlikely(m_param == nullptr)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg, true); \ + return; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_NULL_V_MSG`. * Only use this macro if there is no sensible error message. @@ -322,6 +376,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures a pointer `m_param` is not null. + * If it is null, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + */ +#define ERR_FAIL_NULL_V_EDMSG(m_param, m_retval, m_msg) \ + if (unlikely(m_param == nullptr)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg, true); \ + return m_retval; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_COND_MSG`. * Only use this macro if there is no sensible error message. @@ -352,6 +417,20 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current function returns. + * + * If checking for null use ERR_FAIL_NULL_MSG instead. + * If checking index bounds use ERR_FAIL_INDEX_MSG instead. + */ +#define ERR_FAIL_COND_EDMSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true.", m_msg, true); \ + return; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_COND_V_MSG`. * Only use this macro if there is no sensible error message. @@ -382,6 +461,20 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + * + * If checking for null use ERR_FAIL_NULL_V_MSG instead. + * If checking index bounds use ERR_FAIL_INDEX_V_MSG instead. + */ +#define ERR_FAIL_COND_V_EDMSG(m_cond, m_retval, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returning: " _STR(m_retval), m_msg, true); \ + return m_retval; \ + } else \ + ((void)0) + /** * Try using `ERR_CONTINUE_MSG`. * Only use this macro if there is no sensible error message. @@ -407,6 +500,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current loop continues. + */ +#define ERR_CONTINUE_EDMSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing.", m_msg, true); \ + continue; \ + } else \ + ((void)0) + /** * Try using `ERR_BREAK_MSG`. * Only use this macro if there is no sensible error message. @@ -432,6 +536,17 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Ensures `m_cond` is false. + * If `m_cond` is true, prints `m_msg`, notifies in the editor, and the current loop breaks. + */ +#define ERR_BREAK_EDMSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking.", m_msg, true); \ + break; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_COND_MSG` or `ERR_FAIL_COND_V_MSG`. * Only use this macro if there is no sensible fallback i.e. the error is unrecoverable, and @@ -490,6 +605,19 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Try using `ERR_FAIL_COND_MSG`. + * Only use this macro if more complex error detection or recovery is required. + * + * Prints `m_msg`, notifies in the editor, and the current function returns. + */ +#define ERR_FAIL_EDMSG(m_msg) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed.", m_msg, true); \ + return; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_COND_V_MSG` or `ERR_FAIL_V_MSG`. * Only use this macro if more complex error detection or recovery is required, and @@ -517,6 +645,19 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Try using `ERR_FAIL_COND_V_MSG`. + * Only use this macro if more complex error detection or recovery is required. + * + * Prints `m_msg`, notifies in the editor, and the current function returns `m_retval`. + */ +#define ERR_FAIL_V_EDMSG(m_retval, m_msg) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method/function failed. Returning: " _STR(m_retval), m_msg, true); \ + return m_retval; \ + } else \ + ((void)0) + /** * Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or ERR_BREAK_MSG. * Only use this macro at the start of a function that has not been implemented yet, or @@ -527,6 +668,16 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li #define ERR_PRINT(m_msg) \ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg) +/** + * Try using `ERR_FAIL_COND_MSG`, `ERR_FAIL_COND_V_MSG`, `ERR_CONTINUE_MSG` or ERR_BREAK_MSG. + * Only use this macro at the start of a function that has not been implemented yet, or + * if more complex error detection or recovery is required. + * + * Prints `m_msg` and notifies the editor. + */ +#define ERR_PRINT_ED(m_msg) \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, ) + /** * Prints `m_msg` once during the application lifetime. */ @@ -540,6 +691,19 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li } else \ ((void)0) +/** + * Prints `m_msg` and notifies the editor once during the application lifetime. + */ +#define ERR_PRINT_ONCE_ED(m_msg) \ + if (true) { \ + static bool first_print = true; \ + if (first_print) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true); \ + first_print = false; \ + } \ + } else \ + ((void)0) + // Print warning message macros. /** @@ -548,21 +712,44 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. */ #define WARN_PRINT(m_msg) \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, ERR_HANDLER_WARNING) + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, false, ERR_HANDLER_WARNING) + +/** + * Prints `m_msg` and notifies the editor. + * + * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. + */ +#define WARN_PRINT_ED(m_msg) \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true, ERR_HANDLER_WARNING) /** * Prints `m_msg` once during the application lifetime. * * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. */ -#define WARN_PRINT_ONCE(m_msg) \ - if (true) { \ - static bool first_print = true; \ - if (first_print) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, ERR_HANDLER_WARNING); \ - first_print = false; \ - } \ - } else \ +#define WARN_PRINT_ONCE(m_msg) \ + if (true) { \ + static bool first_print = true; \ + if (first_print) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, false, ERR_HANDLER_WARNING); \ + first_print = false; \ + } \ + } else \ + ((void)0) + +/** + * Prints `m_msg` and notifies the editor once during the application lifetime. + * + * If warning about deprecated usage, use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. + */ +#define WARN_PRINT_ONCE_ED(m_msg) \ + if (true) { \ + static bool first_print = true; \ + if (first_print) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_msg, true, ERR_HANDLER_WARNING); \ + first_print = false; \ + } \ + } else \ ((void)0) // Print deprecated warning message macros. @@ -570,27 +757,27 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li /** * Warns that the current function is deprecated. */ -#define WARN_DEPRECATED \ - if (true) { \ - static SafeFlag warning_shown; \ - if (!warning_shown.is_set()) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", ERR_HANDLER_WARNING); \ - warning_shown.set(); \ - } \ - } else \ +#define WARN_DEPRECATED \ + if (true) { \ + static SafeFlag warning_shown; \ + if (!warning_shown.is_set()) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", false, ERR_HANDLER_WARNING); \ + warning_shown.set(); \ + } \ + } else \ ((void)0) /** * Warns that the current function is deprecated and prints `m_msg`. */ -#define WARN_DEPRECATED_MSG(m_msg) \ - if (true) { \ - static SafeFlag warning_shown; \ - if (!warning_shown.is_set()) { \ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", m_msg, ERR_HANDLER_WARNING); \ - warning_shown.set(); \ - } \ - } else \ +#define WARN_DEPRECATED_MSG(m_msg) \ + if (true) { \ + static SafeFlag warning_shown; \ + if (!warning_shown.is_set()) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", m_msg, false, ERR_HANDLER_WARNING); \ + warning_shown.set(); \ + } \ + } else \ ((void)0) /** diff --git a/core/extension/gdnative_interface.cpp b/core/extension/gdnative_interface.cpp index ff09b0b86c..4770c9c65f 100644 --- a/core/extension/gdnative_interface.cpp +++ b/core/extension/gdnative_interface.cpp @@ -51,13 +51,13 @@ static void gdnative_free(void *p_mem) { // Helper print functions. static void gdnative_print_error(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) { - _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_ERROR); + _err_print_error(p_function, p_file, p_line, p_description, false, ERR_HANDLER_ERROR); } static void gdnative_print_warning(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) { - _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_WARNING); + _err_print_error(p_function, p_file, p_line, p_description, false, ERR_HANDLER_WARNING); } static void gdnative_print_script_error(const char *p_description, const char *p_function, const char *p_file, int32_t p_line) { - _err_print_error(p_function, p_file, p_line, p_description, ERR_HANDLER_SCRIPT); + _err_print_error(p_function, p_file, p_line, p_description, false, ERR_HANDLER_SCRIPT); } // Variant functions diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 09539f716c..b68a8b20a5 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -50,7 +50,7 @@ void Logger::set_flush_stdout_on_print(bool value) { _flush_stdout_on_print = value; } -void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { +void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { if (!should_log(true)) { return; } @@ -81,7 +81,11 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c err_details = p_code; } - logf_error("%s: %s\n", err_type, err_details); + if (p_editor_notify) { + logf_error("%s: %s\n", err_type, err_details); + } else { + logf_error("USER %s: %s\n", err_type, err_details); + } logf_error(" at: %s (%s:%i) - %s\n", p_function, p_file, p_line, p_code); } @@ -256,7 +260,7 @@ void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) { } } -void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { +void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { if (!should_log(true)) { return; } diff --git a/core/io/logger.h b/core/io/logger.h index ccf68562d6..f244f70e7e 100644 --- a/core/io/logger.h +++ b/core/io/logger.h @@ -54,7 +54,7 @@ public: static void set_flush_stdout_on_print(bool value); virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0 = 0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR); void logf(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void logf_error(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -103,7 +103,7 @@ public: CompositeLogger(Vector p_loggers); virtual void logv(const char *p_format, va_list p_list, bool p_err) _PRINTF_FORMAT_ATTRIBUTE_2_0; - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR); void add_logger(Logger *p_logger); diff --git a/core/os/os.cpp b/core/os/os.cpp index 12f85858c3..7404ffdcd5 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -75,12 +75,12 @@ void OS::add_logger(Logger *p_logger) { } } -void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, Logger::ErrorType p_type) { +void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type) { if (!_stderr_enabled) { return; } - _logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_type); + _logger->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type); } void OS::print(const char *p_format, ...) { diff --git a/core/os/os.h b/core/os/os.h index 29d33ce4f0..6d7bc47407 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -110,7 +110,7 @@ public: static OS *get_singleton(); - void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, Logger::ErrorType p_type = Logger::ERR_ERROR); + void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR); void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 3032c31629..193825a73f 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -528,7 +528,7 @@ String OS_Unix::get_executable_path() const { #endif } -void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { +void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { if (!should_log(true)) { return; } diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index bf82019d38..eb36bf4255 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -102,7 +102,7 @@ public: class UnixTerminalLogger : public StdLogger { public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type = ERR_ERROR); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type = ERR_ERROR); virtual ~UnixTerminalLogger(); }; 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 9b7dc966e6..0f6d43b0be 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" @@ -6747,6 +6748,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 43d458c58e..e2d50622dc 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -437,6 +437,7 @@ void EditorSettings::_load_defaults(Ref 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 &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 &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 &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 &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 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 to_delete; + for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) { + Control *control = Object::cast_to(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 &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 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(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 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