Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/editor_toaster.cpp
9902 views
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_string_names.h"
34
#include "editor/settings/editor_settings.h"
35
#include "editor/themes/editor_scale.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_THEME_CHANGED: {
112
if (vbox_container->is_visible()) {
113
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
114
} else {
115
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
116
}
117
disable_notifications_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
118
119
// Styleboxes background.
120
info_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
121
122
warning_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
123
warning_panel_style_background->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
124
125
error_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
126
error_panel_style_background->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
127
128
// Styleboxes progress.
129
info_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
130
131
warning_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
132
warning_panel_style_progress->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
133
134
error_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
135
error_panel_style_progress->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
136
} break;
137
138
case NOTIFICATION_TRANSFORM_CHANGED: {
139
_update_vbox_position();
140
_update_disable_notifications_button();
141
} break;
142
}
143
}
144
145
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) {
146
// This may be called from a thread. Since we will deal with non-thread-safe elements,
147
// we have to put it in the queue for safety.
148
callable_mp_static(&EditorToaster::_error_handler_impl).call_deferred(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type);
149
}
150
151
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) {
152
if (!EditorToaster::get_singleton() || !EditorToaster::get_singleton()->is_inside_tree()) {
153
return;
154
}
155
156
#ifdef DEV_ENABLED
157
bool in_dev = true;
158
#else
159
bool in_dev = false;
160
#endif
161
162
int show_all_setting = EDITOR_GET("interface/editor/show_internal_errors_in_toast_notifications");
163
164
if (p_editor_notify || (show_all_setting == 0 && in_dev) || show_all_setting == 1) {
165
String err_str = !p_errorexp.is_empty() ? p_errorexp : p_error;
166
String tooltip_str = p_file + ":" + itos(p_line);
167
168
if (!p_editor_notify) {
169
if (p_type == ERR_HANDLER_WARNING) {
170
err_str = "INTERNAL WARNING: " + err_str;
171
} else {
172
err_str = "INTERNAL ERROR: " + err_str;
173
}
174
}
175
176
Severity severity = ((ErrorHandlerType)p_type == ERR_HANDLER_WARNING) ? SEVERITY_WARNING : SEVERITY_ERROR;
177
EditorToaster::get_singleton()->popup_str(err_str, severity, tooltip_str);
178
}
179
}
180
181
void EditorToaster::_update_vbox_position() {
182
// This is kind of a workaround because it's hard to keep the VBox anchroed to the bottom.
183
vbox_container->set_size(Vector2());
184
vbox_container->set_position(get_global_position() - vbox_container->get_size() + Vector2(get_size().x, -5 * EDSCALE));
185
}
186
187
void EditorToaster::_update_disable_notifications_button() {
188
bool any_visible = false;
189
for (KeyValue<Control *, Toast> element : toasts) {
190
if (element.key->is_visible()) {
191
any_visible = true;
192
break;
193
}
194
}
195
196
if (!any_visible || !vbox_container->is_visible()) {
197
disable_notifications_panel->hide();
198
} else {
199
disable_notifications_panel->show();
200
disable_notifications_panel->set_position(get_global_position() + Vector2(5 * EDSCALE, -disable_notifications_panel->get_minimum_size().y) + Vector2(get_size().x, -5 * EDSCALE));
201
}
202
}
203
204
void EditorToaster::_auto_hide_or_free_toasts() {
205
// Hide or free old temporary items.
206
int visible_temporary = 0;
207
int temporary = 0;
208
LocalVector<Control *> to_delete;
209
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
210
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
211
if (toasts[control].duration <= 0) {
212
continue; // Ignore non-temporary toasts.
213
}
214
215
temporary++;
216
if (control->is_visible()) {
217
visible_temporary++;
218
}
219
220
// Hide
221
if (visible_temporary > max_temporary_count) {
222
close(control);
223
}
224
225
// Free
226
if (temporary > max_temporary_count * 2) {
227
to_delete.push_back(control);
228
}
229
}
230
231
// Delete the control right away (removed as child) as it might cause issues otherwise when iterative over the vbox_container children.
232
for (Control *c : to_delete) {
233
vbox_container->remove_child(c);
234
c->queue_free();
235
toasts.erase(c);
236
}
237
238
if (toasts.is_empty()) {
239
main_button->set_tooltip_text(TTRC("No notifications."));
240
main_button->set_modulate(Color(0.5, 0.5, 0.5));
241
main_button->set_disabled(true);
242
set_process_internal(false);
243
} else {
244
main_button->set_tooltip_text(TTRC("Show notifications."));
245
main_button->set_modulate(Color(1, 1, 1));
246
main_button->set_disabled(false);
247
}
248
}
249
250
void EditorToaster::_draw_button() {
251
bool has_one = false;
252
Severity highest_severity = SEVERITY_INFO;
253
for (const KeyValue<Control *, Toast> &element : toasts) {
254
if (!element.key->is_visible()) {
255
continue;
256
}
257
has_one = true;
258
if (element.value.severity > highest_severity) {
259
highest_severity = element.value.severity;
260
}
261
}
262
263
if (!has_one) {
264
return;
265
}
266
267
Color color;
268
real_t button_radius = main_button->get_size().x / 8;
269
switch (highest_severity) {
270
case SEVERITY_INFO:
271
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
272
break;
273
case SEVERITY_WARNING:
274
color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
275
break;
276
case SEVERITY_ERROR:
277
color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
278
break;
279
default:
280
break;
281
}
282
main_button->draw_circle(Vector2(button_radius * 2, button_radius * 2), button_radius, color);
283
}
284
285
void EditorToaster::_draw_progress(Control *panel) {
286
if (toasts.has(panel) && toasts[panel].remaining_time > 0 && toasts[panel].duration > 0) {
287
Size2 size = panel->get_size();
288
size.x *= MIN(1, Math::remap(toasts[panel].remaining_time, 0, toasts[panel].duration, 0, 2));
289
290
Ref<StyleBoxFlat> stylebox;
291
switch (toasts[panel].severity) {
292
case SEVERITY_INFO:
293
stylebox = info_panel_style_progress;
294
break;
295
case SEVERITY_WARNING:
296
stylebox = warning_panel_style_progress;
297
break;
298
case SEVERITY_ERROR:
299
stylebox = error_panel_style_progress;
300
break;
301
default:
302
break;
303
}
304
panel->draw_style_box(stylebox, Rect2(Vector2(), size));
305
}
306
}
307
308
void EditorToaster::_set_notifications_enabled(bool p_enabled) {
309
vbox_container->set_visible(p_enabled);
310
if (p_enabled) {
311
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
312
} else {
313
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
314
}
315
_update_disable_notifications_button();
316
}
317
318
void EditorToaster::_repop_old() {
319
// Repop olds, up to max_temporary_count
320
bool needs_update = false;
321
int visible_count = 0;
322
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
323
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
324
if (!control->is_visible()) {
325
control->show();
326
toasts[control].remaining_time = toasts[control].duration;
327
toasts[control].popped = true;
328
needs_update = true;
329
}
330
visible_count++;
331
if (visible_count >= max_temporary_count) {
332
break;
333
}
334
}
335
if (needs_update) {
336
_update_vbox_position();
337
_update_disable_notifications_button();
338
main_button->queue_redraw();
339
}
340
}
341
342
Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_time, const String &p_tooltip) {
343
// Create the panel according to the severity.
344
PanelContainer *panel = memnew(PanelContainer);
345
panel->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
346
panel->set_tooltip_text(p_tooltip);
347
switch (p_severity) {
348
case SEVERITY_INFO:
349
panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
350
break;
351
case SEVERITY_WARNING:
352
panel->add_theme_style_override(SceneStringName(panel), warning_panel_style_background);
353
break;
354
case SEVERITY_ERROR:
355
panel->add_theme_style_override(SceneStringName(panel), error_panel_style_background);
356
break;
357
default:
358
break;
359
}
360
panel->set_modulate(Color(1, 1, 1, 0));
361
panel->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_progress).bind(panel));
362
panel->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_toast_theme_changed).bind(panel));
363
364
Toast &toast = toasts[panel];
365
366
// Horizontal container.
367
HBoxContainer *hbox_container = memnew(HBoxContainer);
368
hbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
369
panel->add_child(hbox_container);
370
371
// Content control.
372
p_control->set_h_size_flags(SIZE_EXPAND_FILL);
373
hbox_container->add_child(p_control);
374
375
// Add buttons.
376
if (p_time > 0.0) {
377
Button *copy_button = memnew(Button);
378
copy_button->set_accessibility_name(TTRC("Copy"));
379
copy_button->set_flat(true);
380
copy_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::copy).bind(panel));
381
hbox_container->add_child(copy_button);
382
383
Button *close_button = memnew(Button);
384
close_button->set_accessibility_name(TTRC("Close"));
385
close_button->set_flat(true);
386
close_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::instant_close).bind(panel));
387
hbox_container->add_child(close_button);
388
389
toast.copy_button = copy_button;
390
toast.close_button = close_button;
391
}
392
393
toast.severity = p_severity;
394
if (p_time > 0.0) {
395
toast.duration = p_time;
396
toast.remaining_time = p_time;
397
} else {
398
toast.duration = -1.0;
399
}
400
toast.popped = true;
401
vbox_container->add_child(panel);
402
_auto_hide_or_free_toasts();
403
_update_vbox_position();
404
_update_disable_notifications_button();
405
main_button->queue_redraw();
406
407
return panel;
408
}
409
410
void EditorToaster::popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
411
if (is_processing_error) {
412
return;
413
}
414
415
// Since "_popup_str" adds nodes to the tree, and since the "add_child" method is not
416
// thread-safe, it's better to defer the call to the next cycle to be thread-safe.
417
is_processing_error = true;
418
MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &EditorToaster::_popup_str), p_message, p_severity, p_tooltip);
419
is_processing_error = false;
420
}
421
422
void EditorToaster::_popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
423
is_processing_error = true;
424
// Check if we already have a popup with the given message.
425
Control *control = nullptr;
426
for (KeyValue<Control *, Toast> element : toasts) {
427
if (element.value.message == p_message && element.value.severity == p_severity && element.value.tooltip == p_tooltip) {
428
control = element.key;
429
break;
430
}
431
}
432
433
// Create a new message if needed.
434
if (control == nullptr) {
435
HBoxContainer *hb = memnew(HBoxContainer);
436
hb->add_theme_constant_override("separation", 0);
437
438
Label *label = memnew(Label);
439
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
440
label->set_focus_mode(FOCUS_ACCESSIBILITY);
441
hb->add_child(label);
442
443
Label *count_label = memnew(Label);
444
count_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
445
hb->add_child(count_label);
446
447
control = popup(hb, p_severity, default_message_duration, p_tooltip);
448
449
Toast &toast = toasts[control];
450
toast.message = p_message;
451
toast.tooltip = p_tooltip;
452
toast.count = 1;
453
toast.message_label = label;
454
toast.message_count_label = count_label;
455
} else {
456
Toast &toast = toasts[control];
457
if (toast.popped) {
458
toast.count += 1;
459
} else {
460
toast.count = 1;
461
}
462
toast.remaining_time = toast.duration;
463
toast.popped = true;
464
control->show();
465
vbox_container->move_child(control, vbox_container->get_child_count());
466
_auto_hide_or_free_toasts();
467
_update_vbox_position();
468
_update_disable_notifications_button();
469
main_button->queue_redraw();
470
}
471
472
// Retrieve the label back, then update the text.
473
Label *message_label = toasts[control].message_label;
474
ERR_FAIL_NULL(message_label);
475
message_label->set_text(p_message);
476
message_label->set_text_overrun_behavior(TextServer::OVERRUN_NO_TRIMMING);
477
message_label->set_custom_minimum_size(Size2());
478
479
Size2i size = message_label->get_combined_minimum_size();
480
int limit_width = get_viewport_rect().size.x / 2; // Limit label size to half the viewport size.
481
if (size.x > limit_width) {
482
message_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
483
message_label->set_custom_minimum_size(Size2(limit_width, 0));
484
}
485
486
// Retrieve the count label back, then update the text.
487
Label *message_count_label = toasts[control].message_count_label;
488
if (toasts[control].count == 1) {
489
message_count_label->hide();
490
} else {
491
message_count_label->set_text(vformat("(%d)", toasts[control].count));
492
message_count_label->show();
493
}
494
495
vbox_container->reset_size();
496
497
is_processing_error = false;
498
set_process_internal(true);
499
}
500
501
void EditorToaster::_toast_theme_changed(Control *p_control) {
502
ERR_FAIL_COND(!toasts.has(p_control));
503
504
Toast &toast = toasts[p_control];
505
if (toast.close_button) {
506
toast.close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
507
}
508
if (toast.copy_button) {
509
toast.copy_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
510
}
511
}
512
513
void EditorToaster::close(Control *p_control) {
514
ERR_FAIL_COND(!toasts.has(p_control));
515
toasts[p_control].remaining_time = -1.0;
516
toasts[p_control].popped = false;
517
}
518
519
void EditorToaster::instant_close(Control *p_control) {
520
close(p_control);
521
p_control->set_modulate(Color(1, 1, 1, 0));
522
}
523
524
void EditorToaster::copy(Control *p_control) {
525
ERR_FAIL_COND(!toasts.has(p_control));
526
DisplayServer::get_singleton()->clipboard_set(toasts[p_control].message);
527
}
528
529
void EditorToaster::_bind_methods() {
530
ClassDB::bind_method(D_METHOD("push_toast", "message", "severity", "tooltip"), &EditorToaster::_popup_str, DEFVAL(EditorToaster::SEVERITY_INFO), DEFVAL(String()));
531
532
BIND_ENUM_CONSTANT(SEVERITY_INFO);
533
BIND_ENUM_CONSTANT(SEVERITY_WARNING);
534
BIND_ENUM_CONSTANT(SEVERITY_ERROR);
535
}
536
537
EditorToaster *EditorToaster::get_singleton() {
538
return singleton;
539
}
540
541
EditorToaster::EditorToaster() {
542
set_notify_transform(true);
543
544
// VBox.
545
vbox_container = memnew(VBoxContainer);
546
vbox_container->set_as_top_level(true);
547
vbox_container->connect(SceneStringName(resized), callable_mp(this, &EditorToaster::_update_vbox_position));
548
add_child(vbox_container);
549
550
// Theming (background).
551
info_panel_style_background.instantiate();
552
info_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
553
554
warning_panel_style_background.instantiate();
555
warning_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
556
warning_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
557
558
error_panel_style_background.instantiate();
559
error_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
560
error_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
561
562
Ref<StyleBoxFlat> boxes[] = { info_panel_style_background, warning_panel_style_background, error_panel_style_background };
563
for (int i = 0; i < 3; i++) {
564
boxes[i]->set_content_margin_individual(int(stylebox_radius * 2.5), 3, int(stylebox_radius * 2.5), 3);
565
}
566
567
// Theming (progress).
568
info_panel_style_progress.instantiate();
569
info_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
570
571
warning_panel_style_progress.instantiate();
572
warning_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
573
warning_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
574
575
error_panel_style_progress.instantiate();
576
error_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
577
error_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
578
579
// Main button.
580
main_button = memnew(Button);
581
main_button->set_accessibility_name(TTRC("Notifications:"));
582
main_button->set_tooltip_text(TTRC("No notifications."));
583
main_button->set_modulate(Color(0.5, 0.5, 0.5));
584
main_button->set_disabled(true);
585
main_button->set_theme_type_variation("FlatMenuButton");
586
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(true));
587
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_repop_old));
588
main_button->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_button));
589
add_child(main_button);
590
591
// Disable notification button.
592
disable_notifications_panel = memnew(PanelContainer);
593
disable_notifications_panel->set_as_top_level(true);
594
disable_notifications_panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
595
add_child(disable_notifications_panel);
596
597
disable_notifications_button = memnew(Button);
598
disable_notifications_button->set_tooltip_text(TTRC("Silence the notifications."));
599
disable_notifications_button->set_flat(true);
600
disable_notifications_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(false));
601
disable_notifications_panel->add_child(disable_notifications_button);
602
603
// Other
604
singleton = this;
605
606
eh.errfunc = _error_handler;
607
add_error_handler(&eh);
608
}
609
610
EditorToaster::~EditorToaster() {
611
singleton = nullptr;
612
remove_error_handler(&eh);
613
}
614
615