1 | /**************************************************************************/ |
2 | /* editor_toaster.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "editor_toaster.h" |
32 | |
33 | #include "editor/editor_scale.h" |
34 | #include "editor/editor_settings.h" |
35 | #include "editor/editor_string_names.h" |
36 | #include "scene/gui/button.h" |
37 | #include "scene/gui/label.h" |
38 | #include "scene/gui/panel_container.h" |
39 | #include "scene/resources/style_box_flat.h" |
40 | |
41 | EditorToaster *EditorToaster::singleton = nullptr; |
42 | |
43 | void EditorToaster::_notification(int p_what) { |
44 | switch (p_what) { |
45 | case NOTIFICATION_INTERNAL_PROCESS: { |
46 | double delta = get_process_delta_time(); |
47 | |
48 | // Check if one element is hovered, if so, don't elapse time. |
49 | bool hovered = false; |
50 | for (const KeyValue<Control *, Toast> &element : toasts) { |
51 | if (Rect2(Vector2(), element.key->get_size()).has_point(element.key->get_local_mouse_position())) { |
52 | hovered = true; |
53 | break; |
54 | } |
55 | } |
56 | |
57 | // Elapses the time and remove toasts if needed. |
58 | if (!hovered) { |
59 | for (const KeyValue<Control *, Toast> &element : toasts) { |
60 | if (!element.value.popped || element.value.duration <= 0) { |
61 | continue; |
62 | } |
63 | toasts[element.key].remaining_time -= delta; |
64 | if (toasts[element.key].remaining_time < 0) { |
65 | close(element.key); |
66 | } |
67 | element.key->queue_redraw(); |
68 | } |
69 | } else { |
70 | // Reset the timers when hovered. |
71 | for (const KeyValue<Control *, Toast> &element : toasts) { |
72 | if (!element.value.popped || element.value.duration <= 0) { |
73 | continue; |
74 | } |
75 | toasts[element.key].remaining_time = element.value.duration; |
76 | element.key->queue_redraw(); |
77 | } |
78 | } |
79 | |
80 | // Change alpha over time. |
81 | bool needs_update = false; |
82 | for (const KeyValue<Control *, Toast> &element : toasts) { |
83 | Color modulate_fade = element.key->get_modulate(); |
84 | |
85 | // Change alpha over time. |
86 | if (element.value.popped && modulate_fade.a < 1.0) { |
87 | modulate_fade.a += delta * 3; |
88 | element.key->set_modulate(modulate_fade); |
89 | } else if (!element.value.popped && modulate_fade.a > 0.0) { |
90 | modulate_fade.a -= delta * 2; |
91 | element.key->set_modulate(modulate_fade); |
92 | } |
93 | |
94 | // Hide element if it is not visible anymore. |
95 | if (modulate_fade.a <= 0.0 && element.key->is_visible()) { |
96 | element.key->hide(); |
97 | needs_update = true; |
98 | } else if (modulate_fade.a > 0.0 && !element.key->is_visible()) { |
99 | element.key->show(); |
100 | needs_update = true; |
101 | } |
102 | } |
103 | |
104 | if (needs_update) { |
105 | _update_vbox_position(); |
106 | _update_disable_notifications_button(); |
107 | main_button->queue_redraw(); |
108 | } |
109 | } break; |
110 | |
111 | case NOTIFICATION_ENTER_TREE: |
112 | case NOTIFICATION_THEME_CHANGED: { |
113 | if (vbox_container->is_visible()) { |
114 | main_button->set_icon(get_editor_theme_icon(SNAME("Notification" ))); |
115 | } else { |
116 | main_button->set_icon(get_editor_theme_icon(SNAME("NotificationDisabled" ))); |
117 | } |
118 | disable_notifications_button->set_icon(get_editor_theme_icon(SNAME("NotificationDisabled" ))); |
119 | |
120 | // Styleboxes background. |
121 | info_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor))); |
122 | |
123 | warning_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor))); |
124 | warning_panel_style_background->set_border_color(get_theme_color(SNAME("warning_color" ), EditorStringName(Editor))); |
125 | |
126 | error_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor))); |
127 | error_panel_style_background->set_border_color(get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
128 | |
129 | // Styleboxes progress. |
130 | info_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor)).lightened(0.03)); |
131 | |
132 | warning_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor)).lightened(0.03)); |
133 | warning_panel_style_progress->set_border_color(get_theme_color(SNAME("warning_color" ), EditorStringName(Editor))); |
134 | |
135 | error_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color" ), EditorStringName(Editor)).lightened(0.03)); |
136 | error_panel_style_progress->set_border_color(get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
137 | |
138 | main_button->queue_redraw(); |
139 | disable_notifications_button->queue_redraw(); |
140 | } break; |
141 | |
142 | case NOTIFICATION_TRANSFORM_CHANGED: { |
143 | _update_vbox_position(); |
144 | _update_disable_notifications_button(); |
145 | } break; |
146 | } |
147 | } |
148 | |
149 | 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) { |
150 | // This may be called from a thread. Since we will deal with non-thread-safe elements, |
151 | // we have to put it in the queue for safety. |
152 | callable_mp_static(&EditorToaster::_error_handler_impl).bind(p_file, p_line, p_error, p_errorexp, p_editor_notify, p_type).call_deferred(); |
153 | } |
154 | |
155 | 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) { |
156 | if (!EditorToaster::get_singleton() || !EditorToaster::get_singleton()->is_inside_tree()) { |
157 | return; |
158 | } |
159 | |
160 | #ifdef DEV_ENABLED |
161 | bool in_dev = true; |
162 | #else |
163 | bool in_dev = false; |
164 | #endif |
165 | |
166 | int show_all_setting = EDITOR_GET("interface/editor/show_internal_errors_in_toast_notifications" ); |
167 | |
168 | if (p_editor_notify || (show_all_setting == 0 && in_dev) || show_all_setting == 1) { |
169 | String err_str = !p_errorexp.is_empty() ? p_errorexp : p_error; |
170 | String tooltip_str = p_file + ":" + itos(p_line); |
171 | |
172 | if (!p_editor_notify) { |
173 | if (p_type == ERR_HANDLER_WARNING) { |
174 | err_str = "INTERNAL WARNING: " + err_str; |
175 | } else { |
176 | err_str = "INTERNAL ERROR: " + err_str; |
177 | } |
178 | } |
179 | |
180 | Severity severity = ((ErrorHandlerType)p_type == ERR_HANDLER_WARNING) ? SEVERITY_WARNING : SEVERITY_ERROR; |
181 | EditorToaster::get_singleton()->popup_str(err_str, severity, tooltip_str); |
182 | } |
183 | } |
184 | |
185 | void EditorToaster::_update_vbox_position() { |
186 | // This is kind of a workaround because it's hard to keep the VBox anchroed to the bottom. |
187 | vbox_container->set_size(Vector2()); |
188 | vbox_container->set_position(get_global_position() - vbox_container->get_size() + Vector2(get_size().x, -5 * EDSCALE)); |
189 | } |
190 | |
191 | void EditorToaster::_update_disable_notifications_button() { |
192 | bool any_visible = false; |
193 | for (KeyValue<Control *, Toast> element : toasts) { |
194 | if (element.key->is_visible()) { |
195 | any_visible = true; |
196 | break; |
197 | } |
198 | } |
199 | |
200 | if (!any_visible || !vbox_container->is_visible()) { |
201 | disable_notifications_panel->hide(); |
202 | } else { |
203 | disable_notifications_panel->show(); |
204 | disable_notifications_panel->set_position(get_global_position() + Vector2(5 * EDSCALE, -disable_notifications_panel->get_minimum_size().y) + Vector2(get_size().x, -5 * EDSCALE)); |
205 | } |
206 | } |
207 | |
208 | void EditorToaster::_auto_hide_or_free_toasts() { |
209 | // Hide or free old temporary items. |
210 | int visible_temporary = 0; |
211 | int temporary = 0; |
212 | LocalVector<Control *> to_delete; |
213 | for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) { |
214 | Control *control = Object::cast_to<Control>(vbox_container->get_child(i)); |
215 | if (toasts[control].duration <= 0) { |
216 | continue; // Ignore non-temporary toasts. |
217 | } |
218 | |
219 | temporary++; |
220 | if (control->is_visible()) { |
221 | visible_temporary++; |
222 | } |
223 | |
224 | // Hide |
225 | if (visible_temporary > max_temporary_count) { |
226 | close(control); |
227 | } |
228 | |
229 | // Free |
230 | if (temporary > max_temporary_count * 2) { |
231 | to_delete.push_back(control); |
232 | } |
233 | } |
234 | |
235 | // Delete the control right away (removed as child) as it might cause issues otherwise when iterative over the vbox_container children. |
236 | for (Control *c : to_delete) { |
237 | vbox_container->remove_child(c); |
238 | c->queue_free(); |
239 | toasts.erase(c); |
240 | } |
241 | |
242 | if (toasts.is_empty()) { |
243 | main_button->set_tooltip_text(TTR("No notifications." )); |
244 | main_button->set_modulate(Color(0.5, 0.5, 0.5)); |
245 | main_button->set_disabled(true); |
246 | } else { |
247 | main_button->set_tooltip_text(TTR("Show notifications." )); |
248 | main_button->set_modulate(Color(1, 1, 1)); |
249 | main_button->set_disabled(false); |
250 | } |
251 | } |
252 | |
253 | void EditorToaster::_draw_button() { |
254 | bool has_one = false; |
255 | Severity highest_severity = SEVERITY_INFO; |
256 | for (const KeyValue<Control *, Toast> &element : toasts) { |
257 | if (!element.key->is_visible()) { |
258 | continue; |
259 | } |
260 | has_one = true; |
261 | if (element.value.severity > highest_severity) { |
262 | highest_severity = element.value.severity; |
263 | } |
264 | } |
265 | |
266 | if (!has_one) { |
267 | return; |
268 | } |
269 | |
270 | Color color; |
271 | real_t button_radius = main_button->get_size().x / 8; |
272 | switch (highest_severity) { |
273 | case SEVERITY_INFO: |
274 | color = get_theme_color(SNAME("accent_color" ), EditorStringName(Editor)); |
275 | break; |
276 | case SEVERITY_WARNING: |
277 | color = get_theme_color(SNAME("warning_color" ), EditorStringName(Editor)); |
278 | break; |
279 | case SEVERITY_ERROR: |
280 | color = get_theme_color(SNAME("error_color" ), EditorStringName(Editor)); |
281 | break; |
282 | default: |
283 | break; |
284 | } |
285 | main_button->draw_circle(Vector2(button_radius * 2, button_radius * 2), button_radius, color); |
286 | } |
287 | |
288 | void EditorToaster::_draw_progress(Control *panel) { |
289 | if (toasts.has(panel) && toasts[panel].remaining_time > 0 && toasts[panel].duration > 0) { |
290 | Size2 size = panel->get_size(); |
291 | size.x *= MIN(1, Math::remap(toasts[panel].remaining_time, 0, toasts[panel].duration, 0, 2)); |
292 | |
293 | Ref<StyleBoxFlat> stylebox; |
294 | switch (toasts[panel].severity) { |
295 | case SEVERITY_INFO: |
296 | stylebox = info_panel_style_progress; |
297 | break; |
298 | case SEVERITY_WARNING: |
299 | stylebox = warning_panel_style_progress; |
300 | break; |
301 | case SEVERITY_ERROR: |
302 | stylebox = error_panel_style_progress; |
303 | break; |
304 | default: |
305 | break; |
306 | } |
307 | panel->draw_style_box(stylebox, Rect2(Vector2(), size)); |
308 | } |
309 | } |
310 | |
311 | void EditorToaster::_set_notifications_enabled(bool p_enabled) { |
312 | vbox_container->set_visible(p_enabled); |
313 | if (p_enabled) { |
314 | main_button->set_icon(get_editor_theme_icon(SNAME("Notification" ))); |
315 | } else { |
316 | main_button->set_icon(get_editor_theme_icon(SNAME("NotificationDisabled" ))); |
317 | } |
318 | _update_disable_notifications_button(); |
319 | } |
320 | |
321 | void EditorToaster::_repop_old() { |
322 | // Repop olds, up to max_temporary_count |
323 | bool needs_update = false; |
324 | int visible_count = 0; |
325 | for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) { |
326 | Control *control = Object::cast_to<Control>(vbox_container->get_child(i)); |
327 | if (!control->is_visible()) { |
328 | control->show(); |
329 | toasts[control].remaining_time = toasts[control].duration; |
330 | toasts[control].popped = true; |
331 | needs_update = true; |
332 | } |
333 | visible_count++; |
334 | if (visible_count >= max_temporary_count) { |
335 | break; |
336 | } |
337 | } |
338 | if (needs_update) { |
339 | _update_vbox_position(); |
340 | _update_disable_notifications_button(); |
341 | main_button->queue_redraw(); |
342 | } |
343 | } |
344 | |
345 | Control *EditorToaster::(Control *p_control, Severity p_severity, double p_time, String p_tooltip) { |
346 | // Create the panel according to the severity. |
347 | PanelContainer *panel = memnew(PanelContainer); |
348 | panel->set_tooltip_text(p_tooltip); |
349 | switch (p_severity) { |
350 | case SEVERITY_INFO: |
351 | panel->add_theme_style_override("panel" , info_panel_style_background); |
352 | break; |
353 | case SEVERITY_WARNING: |
354 | panel->add_theme_style_override("panel" , warning_panel_style_background); |
355 | break; |
356 | case SEVERITY_ERROR: |
357 | panel->add_theme_style_override("panel" , error_panel_style_background); |
358 | break; |
359 | default: |
360 | break; |
361 | } |
362 | panel->set_modulate(Color(1, 1, 1, 0)); |
363 | panel->connect("draw" , callable_mp(this, &EditorToaster::_draw_progress).bind(panel)); |
364 | |
365 | // Horizontal container. |
366 | HBoxContainer *hbox_container = memnew(HBoxContainer); |
367 | hbox_container->set_h_size_flags(SIZE_EXPAND_FILL); |
368 | panel->add_child(hbox_container); |
369 | |
370 | // Content control. |
371 | p_control->set_h_size_flags(SIZE_EXPAND_FILL); |
372 | hbox_container->add_child(p_control); |
373 | |
374 | // Close button. |
375 | if (p_time > 0.0) { |
376 | Button *close_button = memnew(Button); |
377 | close_button->set_flat(true); |
378 | close_button->set_icon(get_editor_theme_icon(SNAME("Close" ))); |
379 | close_button->connect("pressed" , callable_mp(this, &EditorToaster::close).bind(panel)); |
380 | close_button->connect("theme_changed" , callable_mp(this, &EditorToaster::_close_button_theme_changed).bind(close_button)); |
381 | hbox_container->add_child(close_button); |
382 | } |
383 | |
384 | toasts[panel].severity = p_severity; |
385 | if (p_time > 0.0) { |
386 | toasts[panel].duration = p_time; |
387 | toasts[panel].remaining_time = p_time; |
388 | } else { |
389 | toasts[panel].duration = -1.0; |
390 | } |
391 | toasts[panel].popped = true; |
392 | vbox_container->add_child(panel); |
393 | _auto_hide_or_free_toasts(); |
394 | _update_vbox_position(); |
395 | _update_disable_notifications_button(); |
396 | main_button->queue_redraw(); |
397 | |
398 | return panel; |
399 | } |
400 | |
401 | void EditorToaster::(String p_message, Severity p_severity, String p_tooltip) { |
402 | if (is_processing_error) { |
403 | return; |
404 | } |
405 | |
406 | // Since "_popup_str" adds nodes to the tree, and since the "add_child" method is not |
407 | // thread-safe, it's better to defer the call to the next cycle to be thread-safe. |
408 | is_processing_error = true; |
409 | call_deferred(SNAME("_popup_str" ), p_message, p_severity, p_tooltip); |
410 | is_processing_error = false; |
411 | } |
412 | |
413 | void EditorToaster::(String p_message, Severity p_severity, String p_tooltip) { |
414 | is_processing_error = true; |
415 | // Check if we already have a popup with the given message. |
416 | Control *control = nullptr; |
417 | for (KeyValue<Control *, Toast> element : toasts) { |
418 | if (element.value.message == p_message && element.value.severity == p_severity && element.value.tooltip == p_tooltip) { |
419 | control = element.key; |
420 | break; |
421 | } |
422 | } |
423 | |
424 | // Create a new message if needed. |
425 | if (control == nullptr) { |
426 | HBoxContainer *hb = memnew(HBoxContainer); |
427 | hb->add_theme_constant_override("separation" , 0); |
428 | |
429 | Label *label = memnew(Label); |
430 | hb->add_child(label); |
431 | |
432 | Label *count_label = memnew(Label); |
433 | hb->add_child(count_label); |
434 | |
435 | control = popup(hb, p_severity, default_message_duration, p_tooltip); |
436 | toasts[control].message = p_message; |
437 | toasts[control].tooltip = p_tooltip; |
438 | toasts[control].count = 1; |
439 | toasts[control].message_label = label; |
440 | toasts[control].message_count_label = count_label; |
441 | } else { |
442 | if (toasts[control].popped) { |
443 | toasts[control].count += 1; |
444 | } else { |
445 | toasts[control].count = 1; |
446 | } |
447 | toasts[control].remaining_time = toasts[control].duration; |
448 | toasts[control].popped = true; |
449 | control->show(); |
450 | vbox_container->move_child(control, vbox_container->get_child_count()); |
451 | _auto_hide_or_free_toasts(); |
452 | _update_vbox_position(); |
453 | _update_disable_notifications_button(); |
454 | main_button->queue_redraw(); |
455 | } |
456 | |
457 | // Retrieve the label back, then update the text. |
458 | Label *message_label = toasts[control].message_label; |
459 | ERR_FAIL_NULL(message_label); |
460 | message_label->set_text(p_message); |
461 | message_label->set_text_overrun_behavior(TextServer::OVERRUN_NO_TRIMMING); |
462 | message_label->set_custom_minimum_size(Size2()); |
463 | |
464 | Size2i size = message_label->get_combined_minimum_size(); |
465 | int limit_width = get_viewport_rect().size.x / 2; // Limit label size to half the viewport size. |
466 | if (size.x > limit_width) { |
467 | message_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); |
468 | message_label->set_custom_minimum_size(Size2(limit_width, 0)); |
469 | } |
470 | |
471 | // Retrieve the count label back, then update the text. |
472 | Label *message_count_label = toasts[control].message_count_label; |
473 | if (toasts[control].count == 1) { |
474 | message_count_label->hide(); |
475 | } else { |
476 | message_count_label->set_text(vformat("(%d)" , toasts[control].count)); |
477 | message_count_label->show(); |
478 | } |
479 | |
480 | vbox_container->reset_size(); |
481 | |
482 | is_processing_error = false; |
483 | } |
484 | |
485 | void EditorToaster::close(Control *p_control) { |
486 | ERR_FAIL_COND(!toasts.has(p_control)); |
487 | toasts[p_control].remaining_time = -1.0; |
488 | toasts[p_control].popped = false; |
489 | } |
490 | |
491 | void EditorToaster::_close_button_theme_changed(Control *p_close_button) { |
492 | Button *close_button = Object::cast_to<Button>(p_close_button); |
493 | if (close_button) { |
494 | close_button->set_icon(get_editor_theme_icon(SNAME("Close" ))); |
495 | } |
496 | } |
497 | |
498 | EditorToaster *EditorToaster::get_singleton() { |
499 | return singleton; |
500 | } |
501 | |
502 | void EditorToaster::_bind_methods() { |
503 | // Binding method to make it defer-able. |
504 | ClassDB::bind_method(D_METHOD("_popup_str" , "message" , "severity" , "tooltip" ), &EditorToaster::_popup_str); |
505 | } |
506 | |
507 | EditorToaster::EditorToaster() { |
508 | set_notify_transform(true); |
509 | set_process_internal(true); |
510 | |
511 | // VBox. |
512 | vbox_container = memnew(VBoxContainer); |
513 | vbox_container->set_as_top_level(true); |
514 | vbox_container->connect("resized" , callable_mp(this, &EditorToaster::_update_vbox_position)); |
515 | add_child(vbox_container); |
516 | |
517 | // Theming (background). |
518 | info_panel_style_background.instantiate(); |
519 | info_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE); |
520 | |
521 | warning_panel_style_background.instantiate(); |
522 | warning_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE); |
523 | warning_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE); |
524 | |
525 | error_panel_style_background.instantiate(); |
526 | error_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE); |
527 | error_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE); |
528 | |
529 | Ref<StyleBoxFlat> boxes[] = { info_panel_style_background, warning_panel_style_background, error_panel_style_background }; |
530 | for (int i = 0; i < 3; i++) { |
531 | boxes[i]->set_content_margin_individual(int(stylebox_radius * 2.5), 3, int(stylebox_radius * 2.5), 3); |
532 | } |
533 | |
534 | // Theming (progress). |
535 | info_panel_style_progress.instantiate(); |
536 | info_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE); |
537 | |
538 | warning_panel_style_progress.instantiate(); |
539 | warning_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE); |
540 | warning_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE); |
541 | |
542 | error_panel_style_progress.instantiate(); |
543 | error_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE); |
544 | error_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE); |
545 | |
546 | // Main button. |
547 | main_button = memnew(Button); |
548 | main_button->set_tooltip_text(TTR("No notifications." )); |
549 | main_button->set_modulate(Color(0.5, 0.5, 0.5)); |
550 | main_button->set_disabled(true); |
551 | main_button->set_flat(true); |
552 | main_button->connect("pressed" , callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(true)); |
553 | main_button->connect("pressed" , callable_mp(this, &EditorToaster::_repop_old)); |
554 | main_button->connect("draw" , callable_mp(this, &EditorToaster::_draw_button)); |
555 | add_child(main_button); |
556 | |
557 | // Disable notification button. |
558 | disable_notifications_panel = memnew(PanelContainer); |
559 | disable_notifications_panel->set_as_top_level(true); |
560 | disable_notifications_panel->add_theme_style_override("panel" , info_panel_style_background); |
561 | add_child(disable_notifications_panel); |
562 | |
563 | disable_notifications_button = memnew(Button); |
564 | disable_notifications_button->set_tooltip_text(TTR("Silence the notifications." )); |
565 | disable_notifications_button->set_flat(true); |
566 | disable_notifications_button->connect("pressed" , callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(false)); |
567 | disable_notifications_panel->add_child(disable_notifications_button); |
568 | |
569 | // Other |
570 | singleton = this; |
571 | |
572 | eh.errfunc = _error_handler; |
573 | add_error_handler(&eh); |
574 | }; |
575 | |
576 | EditorToaster::~EditorToaster() { |
577 | singleton = nullptr; |
578 | remove_error_handler(&eh); |
579 | } |
580 | |