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