Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/progress_dialog.cpp
20843 views
1
/**************************************************************************/
2
/* progress_dialog.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 "progress_dialog.h"
32
33
#include "core/os/os.h"
34
#include "editor/editor_interface.h"
35
#include "editor/editor_node.h"
36
#include "editor/themes/editor_scale.h"
37
#include "main/main.h"
38
#include "scene/gui/panel_container.h"
39
#include "scene/main/window.h"
40
#include "servers/display/display_server.h"
41
42
void BackgroundProgress::_add_task(const String &p_task, const String &p_label, int p_steps) {
43
_THREAD_SAFE_METHOD_
44
ERR_FAIL_COND_MSG(tasks.has(p_task), "Task '" + p_task + "' already exists.");
45
BackgroundProgress::Task t;
46
t.hb = memnew(HBoxContainer);
47
Label *l = memnew(Label);
48
l->set_text(p_label + " ");
49
t.hb->add_child(l);
50
t.progress = memnew(ProgressBar);
51
t.progress->set_theme_type_variation("PopupProgressBar");
52
t.progress->set_max(p_steps);
53
t.progress->set_value(p_steps);
54
Control *ec = memnew(Control);
55
ec->set_h_size_flags(SIZE_EXPAND_FILL);
56
ec->set_v_size_flags(SIZE_EXPAND_FILL);
57
t.progress->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
58
ec->add_child(t.progress);
59
ec->set_custom_minimum_size(Size2(80, 5) * EDSCALE);
60
t.hb->add_child(ec);
61
62
add_child(t.hb);
63
64
tasks[p_task] = t;
65
}
66
67
void BackgroundProgress::_update() {
68
_THREAD_SAFE_METHOD_
69
70
for (const KeyValue<String, int> &E : updates) {
71
if (tasks.has(E.key)) {
72
_task_step(E.key, E.value);
73
}
74
}
75
76
updates.clear();
77
}
78
79
void BackgroundProgress::_task_step(const String &p_task, int p_step) {
80
_THREAD_SAFE_METHOD_
81
82
ERR_FAIL_COND(!tasks.has(p_task));
83
84
Task &t = tasks[p_task];
85
if (p_step < 0) {
86
t.progress->set_value(t.progress->get_value() + 1);
87
} else {
88
t.progress->set_value(p_step);
89
}
90
}
91
92
void BackgroundProgress::_end_task(const String &p_task) {
93
_THREAD_SAFE_METHOD_
94
95
ERR_FAIL_COND(!tasks.has(p_task));
96
Task &t = tasks[p_task];
97
98
memdelete(t.hb);
99
tasks.erase(p_task);
100
}
101
102
void BackgroundProgress::add_task(const String &p_task, const String &p_label, int p_steps) {
103
callable_mp(this, &BackgroundProgress::_add_task).call_deferred(p_task, p_label, p_steps);
104
}
105
106
void BackgroundProgress::task_step(const String &p_task, int p_step) {
107
//this code is weird, but it prevents deadlock.
108
bool no_updates = true;
109
{
110
_THREAD_SAFE_METHOD_
111
no_updates = updates.is_empty();
112
}
113
114
if (no_updates) {
115
callable_mp(this, &BackgroundProgress::_update).call_deferred();
116
}
117
118
{
119
_THREAD_SAFE_METHOD_
120
updates[p_task] = p_step;
121
}
122
}
123
124
void BackgroundProgress::end_task(const String &p_task) {
125
callable_mp(this, &BackgroundProgress::_end_task).call_deferred(p_task);
126
}
127
128
////////////////////////////////////////////////
129
130
ProgressDialog *ProgressDialog::singleton = nullptr;
131
132
void ProgressDialog::_notification(int p_what) {
133
switch (p_what) {
134
case NOTIFICATION_THEME_CHANGED: {
135
Ref<StyleBox> style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu"));
136
main_border_size = style->get_minimum_size();
137
main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT));
138
main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT));
139
main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP));
140
main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM));
141
142
center_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), "PopupPanel"));
143
} break;
144
}
145
}
146
147
void ProgressDialog::_update_ui() {
148
// Run main loop for two frames.
149
if (is_inside_tree()) {
150
DisplayServer::get_singleton()->process_events();
151
Main::iteration();
152
}
153
}
154
155
void ProgressDialog::_popup() {
156
// Activate processing of all inputs in EditorNode, and the EditorNode::input method
157
// will discard every key input.
158
EditorNode::get_singleton()->set_process_input(true);
159
// Disable all other windows to prevent interaction with them.
160
for (ObjectID wid : host_windows) {
161
Window *w = ObjectDB::get_instance<Window>(wid);
162
if (w) {
163
w->set_process_mode(PROCESS_MODE_DISABLED);
164
}
165
}
166
167
Size2 ms = main->get_combined_minimum_size();
168
ms.width = MAX(500 * EDSCALE, ms.width);
169
ms += main_border_size;
170
171
center_panel->set_custom_minimum_size(ms);
172
173
if (is_ready()) {
174
_reparent_and_show();
175
} else {
176
callable_mp(this, &ProgressDialog::_reparent_and_show).call_deferred();
177
}
178
}
179
180
void ProgressDialog::_reparent_and_show() {
181
Window *current_window = SceneTree::get_singleton()->get_root()->get_last_exclusive_window();
182
ERR_FAIL_NULL(current_window);
183
reparent(current_window);
184
185
// Ensures that events are properly released before the dialog blocks input.
186
bool window_is_input_disabled = current_window->is_input_disabled();
187
current_window->set_disable_input(!window_is_input_disabled);
188
current_window->set_disable_input(window_is_input_disabled);
189
190
show();
191
}
192
193
void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
194
if (MessageQueue::get_singleton()->is_flushing()) {
195
ERR_PRINT("Do not use progress dialog (task) while flushing the message queue or using call_deferred()!");
196
return;
197
}
198
199
ERR_FAIL_COND_MSG(tasks.has(p_task), "Task '" + p_task + "' already exists.");
200
ProgressDialog::Task t;
201
t.vb = memnew(VBoxContainer);
202
VBoxContainer *vb2 = memnew(VBoxContainer);
203
t.vb->add_margin_child(p_label, vb2);
204
t.progress = memnew(ProgressBar);
205
t.progress->set_theme_type_variation("PopupProgressBar");
206
t.progress->set_max(p_steps);
207
t.progress->set_value(p_steps);
208
vb2->add_child(t.progress);
209
t.state = memnew(Label);
210
t.state->set_clip_text(true);
211
vb2->add_child(t.state);
212
main->add_child(t.vb);
213
214
tasks[p_task] = t;
215
if (p_can_cancel) {
216
cancel_hb->show();
217
} else {
218
cancel_hb->hide();
219
}
220
cancel_hb->move_to_front();
221
canceled = false;
222
_popup();
223
if (p_can_cancel) {
224
cancel->grab_focus();
225
}
226
_update_ui();
227
}
228
229
bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
230
ERR_FAIL_COND_V(!tasks.has(p_task), canceled);
231
232
Task &t = tasks[p_task];
233
if (!p_force_redraw) {
234
uint64_t tus = OS::get_singleton()->get_ticks_usec();
235
if (tus - t.last_progress_tick < 200000) { //200ms
236
return canceled;
237
}
238
}
239
if (p_step < 0) {
240
t.progress->set_value(t.progress->get_value() + 1);
241
} else {
242
t.progress->set_value(p_step);
243
}
244
245
t.state->set_text(p_state);
246
t.last_progress_tick = OS::get_singleton()->get_ticks_usec();
247
_update_ui();
248
249
return canceled;
250
}
251
252
void ProgressDialog::end_task(const String &p_task) {
253
ERR_FAIL_COND(!tasks.has(p_task));
254
Task &t = tasks[p_task];
255
256
memdelete(t.vb);
257
tasks.erase(p_task);
258
259
if (tasks.is_empty()) {
260
hide();
261
EditorNode::get_singleton()->set_process_input(false);
262
for (ObjectID wid : host_windows) {
263
Window *w = ObjectDB::get_instance<Window>(wid);
264
if (w) {
265
w->set_process_mode(PROCESS_MODE_INHERIT);
266
}
267
}
268
} else {
269
_popup();
270
}
271
}
272
273
void ProgressDialog::add_host_window(ObjectID p_window) {
274
host_windows.push_back(p_window);
275
}
276
277
void ProgressDialog::remove_host_window(ObjectID p_window) {
278
host_windows.erase(p_window);
279
}
280
281
void ProgressDialog::_cancel_pressed() {
282
canceled = true;
283
}
284
285
ProgressDialog::ProgressDialog() {
286
// We want to cover the entire screen to prevent the user from interacting with the Editor.
287
set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
288
// Be sure it's the top most component.
289
set_z_index(RS::CANVAS_ITEM_Z_MAX);
290
singleton = this;
291
hide();
292
293
center_panel = memnew(PanelContainer);
294
add_child(center_panel);
295
center_panel->set_h_size_flags(SIZE_SHRINK_BEGIN);
296
center_panel->set_v_size_flags(SIZE_SHRINK_BEGIN);
297
298
main = memnew(VBoxContainer);
299
center_panel->add_child(main);
300
301
cancel_hb = memnew(HBoxContainer);
302
main->add_child(cancel_hb);
303
cancel_hb->hide();
304
cancel = memnew(Button);
305
cancel_hb->add_spacer();
306
cancel_hb->add_child(cancel);
307
cancel->set_text(TTR("Cancel"));
308
cancel_hb->add_spacer();
309
cancel->connect(SceneStringName(pressed), callable_mp(this, &ProgressDialog::_cancel_pressed));
310
}
311
312
ProgressDialog::~ProgressDialog() {
313
singleton = nullptr;
314
}
315
316