Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/docks/editor_dock_manager.cpp
20898 views
1
/**************************************************************************/
2
/* editor_dock_manager.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_dock_manager.h"
32
33
#include "scene/gui/box_container.h"
34
#include "scene/gui/button.h"
35
#include "scene/gui/label.h"
36
#include "scene/gui/split_container.h"
37
#include "scene/gui/tab_container.h"
38
#include "scene/main/window.h"
39
40
#include "editor/docks/dock_tab_container.h"
41
#include "editor/docks/editor_dock.h"
42
#include "editor/editor_node.h"
43
#include "editor/editor_string_names.h"
44
#include "editor/gui/window_wrapper.h"
45
#include "editor/settings/editor_settings.h"
46
#include "editor/themes/editor_scale.h"
47
48
////////////////////////////////////////////////
49
////////////////////////////////////////////////
50
51
void DockSplitContainer::_update_visibility() {
52
if (is_updating) {
53
return;
54
}
55
is_updating = true;
56
bool any_visible = false;
57
for (int i = 0; i < get_child_count(false); i++) {
58
Control *c = Object::cast_to<Control>(get_child(i, false));
59
if (!c || !c->is_visible() || c->is_set_as_top_level()) {
60
continue;
61
}
62
any_visible = c;
63
break;
64
}
65
set_visible(any_visible);
66
is_updating = false;
67
}
68
69
void DockSplitContainer::add_child_notify(Node *p_child) {
70
SplitContainer::add_child_notify(p_child);
71
72
Control *child_control = nullptr;
73
for (int i = 0; i < get_child_count(false); i++) {
74
Control *c = Object::cast_to<Control>(get_child(i, false));
75
if (!c || c->is_set_as_top_level()) {
76
continue;
77
}
78
if (p_child == c) {
79
child_control = c;
80
break;
81
}
82
}
83
if (!child_control) {
84
return;
85
}
86
87
child_control->connect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));
88
_update_visibility();
89
}
90
91
void DockSplitContainer::remove_child_notify(Node *p_child) {
92
SplitContainer::remove_child_notify(p_child);
93
94
Control *child_control = nullptr;
95
for (int i = 0; i < get_child_count(false); i++) {
96
Control *c = Object::cast_to<Control>(get_child(i, false));
97
if (!c || c->is_set_as_top_level()) {
98
continue;
99
}
100
if (p_child == c) {
101
child_control = c;
102
break;
103
}
104
}
105
if (!child_control) {
106
return;
107
}
108
109
child_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));
110
_update_visibility();
111
}
112
113
DockSplitContainer::DockSplitContainer() {
114
if (EDITOR_GET("interface/touchscreen/enable_touch_optimizations")) {
115
callable_mp((SplitContainer *)this, &SplitContainer::set_touch_dragger_enabled).call_deferred(true);
116
}
117
}
118
119
////////////////////////////////////////////////
120
////////////////////////////////////////////////
121
122
EditorDock *EditorDockManager::_get_dock_tab_dragged() {
123
if (dock_tab_dragged) {
124
return dock_tab_dragged;
125
}
126
127
Dictionary dock_drop_data = EditorNode::get_singleton()->get_viewport()->gui_get_drag_data();
128
129
// Check if we are dragging a dock.
130
if (dock_drop_data.get("type", "").operator String() != "tab") {
131
return nullptr;
132
}
133
134
const String tab_type = dock_drop_data.get("tab_type", "");
135
if (tab_type == "tab_container_tab") {
136
Node *source_tab_bar = EditorNode::get_singleton()->get_node(dock_drop_data["from_path"]);
137
if (!source_tab_bar) {
138
return nullptr;
139
}
140
141
DockTabContainer *source_tab_container = Object::cast_to<DockTabContainer>(source_tab_bar->get_parent());
142
if (!source_tab_container) {
143
return nullptr;
144
}
145
146
dock_tab_dragged = source_tab_container->get_dock(dock_drop_data["tab_index"]);
147
if (!dock_tab_dragged) {
148
return nullptr;
149
}
150
151
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
152
dock_slots[i]->show_drag_hint();
153
}
154
155
return dock_tab_dragged;
156
}
157
return nullptr;
158
}
159
160
void EditorDockManager::_dock_drag_stopped() {
161
dock_tab_dragged = nullptr;
162
}
163
164
void EditorDockManager::_dock_split_dragged(int p_offset) {
165
EditorNode::get_singleton()->save_editor_layout_delayed();
166
}
167
168
void EditorDockManager::_update_layout() {
169
if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {
170
return;
171
}
172
dock_context_popup->docks_updated();
173
update_docks_menu();
174
EditorNode::get_singleton()->save_editor_layout_delayed();
175
}
176
177
void EditorDockManager::update_docks_menu() {
178
docks_menu->clear();
179
docks_menu->reset_size();
180
181
const Ref<Texture2D> default_icon = docks_menu->get_editor_theme_icon(SNAME("Window"));
182
const Color closed_icon_color_mod = Color(1, 1, 1, 0.5);
183
184
bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);
185
bool dark_mode = DisplayServer::get_singleton()->is_dark_mode_supported() && DisplayServer::get_singleton()->is_dark_mode();
186
int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));
187
188
// Add docks.
189
docks_menu_docks.clear();
190
int id = 0;
191
const Callable icon_fetch = callable_mp(EditorNode::get_singleton(), &EditorNode::get_editor_theme_native_menu_icon).bind(global_menu, dark_mode);
192
for (EditorDock *dock : all_docks) {
193
if (!dock->enabled || !dock->global) {
194
continue;
195
}
196
if (dock->shortcut.is_valid()) {
197
docks_menu->add_shortcut(dock->shortcut, id);
198
docks_menu->set_item_text(id, dock->get_display_title());
199
} else {
200
docks_menu->add_item(dock->get_display_title(), id);
201
}
202
docks_menu->set_item_icon_max_width(id, icon_max_width);
203
204
const Ref<Texture2D> icon = dock->get_effective_icon(icon_fetch);
205
docks_menu->set_item_icon(id, icon.is_valid() ? icon : default_icon);
206
if (!dock->is_open) {
207
docks_menu->set_item_icon_modulate(id, closed_icon_color_mod);
208
docks_menu->set_item_tooltip(id, vformat(TTR("Open the %s dock."), TTR(dock->get_display_title())));
209
} else {
210
docks_menu->set_item_tooltip(id, vformat(TTR("Focus on the %s dock."), TTR(dock->get_display_title())));
211
}
212
docks_menu_docks.push_back(dock);
213
id++;
214
}
215
}
216
217
void EditorDockManager::_docks_menu_option(int p_id) {
218
EditorDock *dock = docks_menu_docks[p_id];
219
ERR_FAIL_NULL(dock);
220
ERR_FAIL_COND_MSG(!all_docks.has(dock), vformat("Menu option for unknown dock '%s'.", dock->get_display_title()));
221
if (dock->enabled && dock->is_open) {
222
PopupMenu *parent_menu = Object::cast_to<PopupMenu>(docks_menu->get_parent());
223
ERR_FAIL_NULL(parent_menu);
224
parent_menu->hide();
225
}
226
focus_dock(dock);
227
}
228
229
void EditorDockManager::_window_close_request(WindowWrapper *p_wrapper) {
230
// Give the dock back to the original owner.
231
EditorDock *dock = _close_window(p_wrapper);
232
ERR_FAIL_COND(!all_docks.has(dock));
233
234
if (dock->dock_slot_index != EditorDock::DOCK_SLOT_NONE) {
235
dock->is_open = false;
236
focus_dock(dock);
237
} else {
238
close_dock(dock);
239
}
240
}
241
242
EditorDock *EditorDockManager::_close_window(WindowWrapper *p_wrapper) {
243
p_wrapper->set_block_signals(true);
244
EditorDock *dock = Object::cast_to<EditorDock>(p_wrapper->release_wrapped_control());
245
p_wrapper->set_block_signals(false);
246
ERR_FAIL_COND_V(!all_docks.has(dock), nullptr);
247
248
dock->dock_window = nullptr;
249
dock_windows.erase(p_wrapper);
250
p_wrapper->queue_free();
251
return dock;
252
}
253
254
void EditorDockManager::_open_dock_in_window(EditorDock *p_dock, bool p_show_window, bool p_reset_size) {
255
ERR_FAIL_NULL(p_dock);
256
257
Size2 borders = Size2(4, 4) * EDSCALE;
258
// Remember size and position before removing it from the main window.
259
Size2 dock_size = p_dock->get_size() + borders * 2;
260
Point2 dock_screen_pos = p_dock->get_screen_position();
261
262
WindowWrapper *wrapper = memnew(WindowWrapper);
263
wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR(p_dock->get_display_title())));
264
wrapper->set_margins_enabled(true);
265
266
EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);
267
268
_move_dock(p_dock, nullptr);
269
p_dock->update_layout(EditorDock::DOCK_LAYOUT_FLOATING);
270
p_dock->current_layout = EditorDock::DOCK_LAYOUT_FLOATING;
271
wrapper->set_wrapped_control(p_dock);
272
273
p_dock->dock_window = wrapper;
274
p_dock->is_open = true;
275
p_dock->show();
276
277
wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_window_close_request).bind(wrapper));
278
dock_windows.push_back(wrapper);
279
280
if (p_show_window) {
281
wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());
282
_update_layout();
283
if (p_reset_size) {
284
// Use a default size of one third the current window size.
285
Size2i popup_size = EditorNode::get_singleton()->get_window()->get_size() / 3.0;
286
p_dock->get_window()->set_size(popup_size);
287
p_dock->get_window()->move_to_center();
288
}
289
p_dock->get_window()->grab_focus();
290
}
291
}
292
293
void EditorDockManager::_restore_dock_to_saved_window(EditorDock *p_dock, const Dictionary &p_window_dump) {
294
if (!p_dock->dock_window) {
295
_open_dock_in_window(p_dock, false);
296
}
297
298
p_dock->dock_window->restore_window_from_saved_position(
299
p_window_dump.get("window_rect", Rect2i()),
300
p_window_dump.get("window_screen", -1),
301
p_window_dump.get("window_screen_rect", Rect2i()));
302
}
303
304
void EditorDockManager::_move_dock(EditorDock *p_dock, Control *p_target, int p_tab_index, bool p_set_current) {
305
ERR_FAIL_NULL(p_dock);
306
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot move unknown dock '%s'.", p_dock->get_display_title()));
307
308
Node *parent = p_dock->get_parent();
309
if (parent == p_target) {
310
if (parent && p_tab_index >= 0) {
311
// Only change the tab index.
312
p_dock->set_tab_index(p_tab_index, p_set_current);
313
}
314
return;
315
}
316
317
// Remove dock from its existing parent.
318
if (parent) {
319
if (p_dock->dock_window) {
320
_close_window(p_dock->dock_window);
321
} else {
322
DockTabContainer *parent_tabs = Object::cast_to<DockTabContainer>(parent);
323
if (parent_tabs) {
324
p_dock->previous_tab_index = parent_tabs->get_tab_idx_from_control(p_dock);
325
}
326
parent->set_block_signals(true);
327
parent->remove_child(p_dock);
328
parent->set_block_signals(false);
329
if (parent_tabs) {
330
parent_tabs->update_visibility();
331
}
332
}
333
}
334
335
if (!p_target) {
336
p_dock->is_open = false;
337
return;
338
}
339
340
DockTabContainer *dock_tab_container = Object::cast_to<DockTabContainer>(p_target);
341
if (p_target != closed_dock_parent) {
342
if (dock_tab_container->layout != p_dock->current_layout) {
343
p_dock->update_layout(dock_tab_container->layout);
344
p_dock->current_layout = dock_tab_container->layout;
345
}
346
p_dock->dock_slot_index = dock_tab_container->dock_slot;
347
}
348
349
// Add dock to its new parent, at the given tab index.
350
p_target->set_block_signals(true);
351
p_target->add_child(p_dock);
352
p_target->set_block_signals(false);
353
354
if (dock_tab_container) {
355
if (dock_tab_container->is_inside_tree()) {
356
p_dock->update_tab_style();
357
}
358
if (p_tab_index >= 0) {
359
p_dock->set_tab_index(p_tab_index, p_set_current);
360
}
361
dock_tab_container->update_visibility();
362
}
363
}
364
365
void EditorDockManager::_queue_update_tab_style(EditorDock *p_dock) {
366
if (dirty_docks.is_empty()) {
367
callable_mp(this, &EditorDockManager::_update_dirty_dock_tabs).call_deferred();
368
}
369
dirty_docks.insert(p_dock);
370
}
371
372
void EditorDockManager::_update_dirty_dock_tabs() {
373
bool update_menu = false;
374
for (EditorDock *dock : dirty_docks) {
375
update_menu = update_menu || dock->global;
376
dock->update_tab_style();
377
}
378
dirty_docks.clear();
379
380
if (update_menu) {
381
update_docks_menu();
382
}
383
}
384
385
void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {
386
// Save docks by dock slot.
387
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
388
dock_slots[i]->save_docks_to_config(p_layout, p_section);
389
}
390
391
// Clear the special dock slot for docks without default slots (index -1 = dock_0).
392
// This prevents closed docks from being infinitely appended to the config on each save.
393
const String no_slot_config_key = "dock_0";
394
if (p_layout->has_section_key(p_section, no_slot_config_key)) {
395
p_layout->erase_section_key(p_section, no_slot_config_key);
396
}
397
398
// Save docks in windows.
399
Dictionary floating_docks_dump;
400
for (WindowWrapper *wrapper : dock_windows) {
401
EditorDock *dock = Object::cast_to<EditorDock>(wrapper->get_wrapped_control());
402
403
Dictionary window_dump;
404
window_dump["window_rect"] = wrapper->get_window_rect();
405
406
int screen = wrapper->get_window_screen();
407
window_dump["window_screen"] = wrapper->get_window_screen();
408
window_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
409
410
String name = dock->get_effective_layout_key();
411
if (!dock->transient) {
412
floating_docks_dump[name] = window_dump;
413
}
414
415
// Append to regular dock section so we know where to restore it to.
416
int dock_slot_id = dock->dock_slot_index;
417
String config_key = DockTabContainer::get_config_key(dock_slot_id);
418
419
String names = p_layout->get_value(p_section, config_key, "");
420
if (names.is_empty()) {
421
names = name;
422
} else {
423
names += "," + name;
424
}
425
p_layout->set_value(p_section, config_key, names);
426
}
427
p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
428
429
Array closed_docks_dump;
430
for (const EditorDock *dock : all_docks) {
431
const String section_name = p_section + "/" + dock->get_effective_layout_key();
432
dock->save_layout_to_config(p_layout, section_name);
433
434
if (dock->is_open) {
435
continue;
436
}
437
438
// Save closed docks.
439
const String name = dock->get_effective_layout_key();
440
if (!dock->transient) {
441
closed_docks_dump.push_back(name);
442
}
443
444
int dock_slot_id = dock->dock_slot_index;
445
String config_key = DockTabContainer::get_config_key(dock_slot_id);
446
447
String names = p_layout->get_value(p_section, config_key, "");
448
if (names.is_empty()) {
449
names = name;
450
} else {
451
names += "," + name;
452
}
453
p_layout->set_value(p_section, config_key, names);
454
}
455
p_layout->set_value(p_section, "dock_closed", closed_docks_dump);
456
457
// Save SplitContainer offsets.
458
for (int i = 0; i < vsplits.size(); i++) {
459
if (vsplits[i]->is_visible_in_tree()) {
460
p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
461
}
462
}
463
464
PackedInt32Array split_offsets = main_hsplit->get_split_offsets();
465
int index = 0;
466
for (int i = 0; i < vsplits.size(); i++) {
467
int value = 0;
468
if (vsplits[i]->is_visible() && index < split_offsets.size()) {
469
value = split_offsets[index] / EDSCALE;
470
index++;
471
}
472
p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), value);
473
}
474
}
475
476
void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section, bool p_first_load) {
477
Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
478
Array closed_docks = p_layout->get_value(p_section, "dock_closed", Array());
479
480
bool allow_floating_docks = EditorNode::get_singleton()->is_multi_window_enabled() && (!p_first_load || EDITOR_GET("interface/multi_window/restore_windows_on_load"));
481
482
// Store the docks by name for easy lookup.
483
HashMap<String, EditorDock *> dock_map;
484
for (EditorDock *dock : all_docks) {
485
dock_map[dock->get_effective_layout_key()] = dock;
486
}
487
488
// Load docks by slot. Index -1 is for docks that have no slot.
489
for (int i = -1; i < EditorDock::DOCK_SLOT_MAX; i++) {
490
const String key = DockTabContainer::get_config_key(i);
491
if (!p_layout->has_section_key(p_section, key)) {
492
continue;
493
}
494
495
Vector<String> names = String(p_layout->get_value(p_section, key)).split(",");
496
for (int j = names.size() - 1; j >= 0; j--) {
497
const String &name = names[j];
498
const String section_name = p_section + "/" + name;
499
500
if (!dock_map.has(name)) {
501
continue;
502
}
503
EditorDock *dock = dock_map[name];
504
505
if (!dock->enabled) {
506
// Don't open disabled docks.
507
dock->load_layout_from_config(p_layout, section_name);
508
continue;
509
}
510
511
if (allow_floating_docks && floating_docks_dump.has(name)) {
512
_restore_dock_to_saved_window(dock, floating_docks_dump[name]);
513
} else if (i >= 0 && !(dock->transient && !dock->is_open)) {
514
// Safe to include transient open docks here because they won't be in the closed dock dump.
515
if (closed_docks.has(name)) {
516
dock->is_open = false;
517
dock->hide();
518
_move_dock(dock, closed_dock_parent);
519
} else {
520
dock->is_open = true;
521
_move_dock(dock, dock_slots[i], 0);
522
}
523
}
524
dock->load_layout_from_config(p_layout, section_name);
525
526
dock->dock_slot_index = i;
527
dock->previous_tab_index = i >= 0 ? j : 0;
528
}
529
}
530
531
// Set the selected tabs.
532
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
533
int selected_tab_idx = p_layout->get_value(p_section, DockTabContainer::get_config_key(i) + "_selected_tab_idx", -1);
534
dock_slots[i]->load_selected_tab(selected_tab_idx);
535
}
536
537
// Load SplitContainer offsets.
538
PackedInt32Array offsets;
539
for (int i = 0; i < vsplits.size(); i++) {
540
if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
541
continue;
542
}
543
int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
544
vsplits[i]->set_split_offset(ofs);
545
546
// Only visible ones need a split offset for the main hsplit, even though they all have a value saved.
547
if (vsplits[i]->is_visible() && p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {
548
int offset = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
549
offsets.push_back(offset * EDSCALE);
550
}
551
}
552
main_hsplit->set_split_offsets(offsets);
553
554
update_docks_menu();
555
}
556
557
void EditorDockManager::set_dock_enabled(EditorDock *p_dock, bool p_enabled) {
558
ERR_FAIL_NULL(p_dock);
559
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set enabled unknown dock '%s'.", p_dock->get_display_title()));
560
561
if (p_dock->enabled == p_enabled) {
562
return;
563
}
564
565
p_dock->enabled = p_enabled;
566
if (p_enabled) {
567
open_dock(p_dock, false);
568
} else {
569
close_dock(p_dock);
570
}
571
}
572
573
void EditorDockManager::close_dock(EditorDock *p_dock) {
574
ERR_FAIL_NULL(p_dock);
575
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot close unknown dock '%s'.", p_dock->get_display_title()));
576
577
if (!p_dock->is_open) {
578
return;
579
}
580
581
p_dock->is_open = false;
582
DockTabContainer *parent_container = p_dock->get_parent_container();
583
if (parent_container) {
584
parent_container->dock_closed(p_dock);
585
}
586
587
// Hide before moving to remove inconsistent signals.
588
p_dock->hide();
589
_move_dock(p_dock, closed_dock_parent);
590
591
_update_layout();
592
}
593
594
void EditorDockManager::open_dock(EditorDock *p_dock, bool p_set_current) {
595
ERR_FAIL_NULL(p_dock);
596
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot open unknown dock '%s'.", p_dock->get_display_title()));
597
598
if (p_dock->is_open) {
599
// Show the dock if it is already open.
600
if (p_set_current) {
601
_make_dock_visible(p_dock, false);
602
}
603
return;
604
}
605
606
p_dock->is_open = true;
607
608
// Open dock to its previous location.
609
if (p_dock->dock_slot_index != EditorDock::DOCK_SLOT_NONE) {
610
DockTabContainer *slot = dock_slots[p_dock->dock_slot_index];
611
int tab_index = p_dock->previous_tab_index;
612
if (tab_index < 0) {
613
tab_index = slot->get_tab_count();
614
}
615
616
_move_dock(p_dock, slot, tab_index, p_set_current && slot->can_switch_dock());
617
} else {
618
_open_dock_in_window(p_dock, true, true);
619
return;
620
}
621
622
_update_layout();
623
}
624
625
void EditorDockManager::make_dock_floating(EditorDock *p_dock) {
626
ERR_FAIL_NULL(p_dock);
627
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot make unknown dock '%s' floating.", p_dock->get_display_title()));
628
629
if (!p_dock->dock_window) {
630
_open_dock_in_window(p_dock);
631
}
632
}
633
634
void EditorDockManager::_make_dock_visible(EditorDock *p_dock, bool p_grab_focus) {
635
if (p_dock->dock_window) {
636
if (p_grab_focus) {
637
p_dock->get_window()->grab_focus();
638
}
639
return;
640
}
641
642
DockTabContainer *tab_container = p_dock->get_parent_container();
643
if (!tab_container || !tab_container->can_switch_dock()) {
644
return;
645
}
646
647
if (p_grab_focus) {
648
tab_container->get_tab_bar()->grab_focus();
649
}
650
651
int tab_index = tab_container->get_tab_idx_from_control(p_dock);
652
tab_container->set_current_tab(tab_index);
653
}
654
655
void EditorDockManager::focus_dock(EditorDock *p_dock) {
656
ERR_FAIL_NULL(p_dock);
657
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot focus unknown dock '%s'.", p_dock->get_display_title()));
658
659
if (!p_dock->enabled) {
660
return;
661
}
662
663
if (!p_dock->is_open) {
664
p_dock->emit_signal("opened");
665
open_dock(p_dock, false);
666
}
667
668
_make_dock_visible(p_dock, true);
669
}
670
671
void EditorDockManager::add_dock(EditorDock *p_dock) {
672
ERR_FAIL_NULL(p_dock);
673
ERR_FAIL_COND_MSG(all_docks.has(p_dock), vformat("Cannot add dock '%s', already added.", p_dock->get_display_title()));
674
675
p_dock->dock_slot_index = p_dock->default_slot;
676
all_docks.push_back(p_dock);
677
p_dock->connect("_tab_style_changed", callable_mp(this, &EditorDockManager::_queue_update_tab_style).bind(p_dock));
678
p_dock->connect("renamed", callable_mp(this, &EditorDockManager::_queue_update_tab_style).bind(p_dock));
679
680
if (p_dock->default_slot != EditorDock::DOCK_SLOT_NONE) {
681
open_dock(p_dock, false);
682
} else {
683
closed_dock_parent->add_child(p_dock);
684
p_dock->hide();
685
_update_layout();
686
}
687
}
688
689
void EditorDockManager::remove_dock(EditorDock *p_dock) {
690
ERR_FAIL_NULL(p_dock);
691
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot remove unknown dock '%s'.", p_dock->get_display_title()));
692
693
_move_dock(p_dock, nullptr);
694
695
all_docks.erase(p_dock);
696
p_dock->disconnect("_tab_style_changed", callable_mp(this, &EditorDockManager::_queue_update_tab_style));
697
p_dock->disconnect("renamed", callable_mp(this, &EditorDockManager::_queue_update_tab_style));
698
_update_layout();
699
}
700
701
void EditorDockManager::set_docks_visible(bool p_show) {
702
if (docks_visible == p_show) {
703
return;
704
}
705
docks_visible = p_show;
706
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
707
// Show and hide in reverse order due to the SplitContainer prioritizing the last split offset.
708
dock_slots[docks_visible ? i : EditorDock::DOCK_SLOT_MAX - i - 1]->update_visibility();
709
}
710
_update_layout();
711
}
712
713
bool EditorDockManager::are_docks_visible() const {
714
return docks_visible;
715
}
716
717
void EditorDockManager::update_tab_styles() {
718
for (EditorDock *dock : all_docks) {
719
dock->update_tab_style();
720
}
721
}
722
723
void EditorDockManager::set_tab_icon_max_width(int p_max_width) {
724
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
725
dock_slots[i]->add_theme_constant_override(SNAME("icon_max_width"), p_max_width);
726
}
727
}
728
729
void EditorDockManager::add_vsplit(DockSplitContainer *p_split) {
730
vsplits.push_back(p_split);
731
p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
732
}
733
734
void EditorDockManager::set_hsplit(DockSplitContainer *p_split) {
735
main_hsplit = p_split;
736
p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
737
}
738
739
void EditorDockManager::register_dock_slot(DockTabContainer *p_tab_container) {
740
ERR_FAIL_NULL(p_tab_container);
741
dock_slots[p_tab_container->dock_slot] = p_tab_container;
742
743
p_tab_container->set_dock_context_popup(dock_context_popup);
744
p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
745
p_tab_container->connect("active_tab_rearranged", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
746
}
747
748
int EditorDockManager::get_vsplit_count() const {
749
return vsplits.size();
750
}
751
752
PopupMenu *EditorDockManager::get_docks_menu() {
753
return docks_menu;
754
}
755
756
EditorDockManager::EditorDockManager() {
757
singleton = this;
758
759
closed_dock_parent = memnew(Control);
760
closed_dock_parent->hide();
761
EditorNode::get_singleton()->get_gui_base()->add_child(closed_dock_parent);
762
763
dock_context_popup = memnew(DockContextPopup);
764
EditorNode::get_singleton()->get_gui_base()->add_child(dock_context_popup);
765
EditorNode::get_singleton()->add_child(memnew(DockShortcutHandler));
766
767
docks_menu = memnew(PopupMenu);
768
docks_menu->set_hide_on_item_selection(false);
769
docks_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDockManager::_docks_menu_option));
770
EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu));
771
}
772
773
////////////////////////////////////////////////
774
////////////////////////////////////////////////
775
776
void DockContextPopup::_notification(int p_what) {
777
switch (p_what) {
778
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
779
case NOTIFICATION_TRANSLATION_CHANGED:
780
case NOTIFICATION_THEME_CHANGED: {
781
if (make_float_button) {
782
make_float_button->set_button_icon(get_editor_theme_icon(SNAME("MakeFloating")));
783
}
784
if (is_layout_rtl()) {
785
tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
786
tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
787
tab_move_left_button->set_tooltip_text(TTR("Move this dock right one tab."));
788
tab_move_right_button->set_tooltip_text(TTR("Move this dock left one tab."));
789
} else {
790
tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
791
tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
792
tab_move_left_button->set_tooltip_text(TTR("Move this dock left one tab."));
793
tab_move_right_button->set_tooltip_text(TTR("Move this dock right one tab."));
794
}
795
close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
796
} break;
797
}
798
}
799
800
void DockContextPopup::_tab_move_left() {
801
TabContainer *tab_container = context_dock->get_parent_container();
802
if (!tab_container) {
803
return;
804
}
805
int new_index = tab_container->get_tab_idx_from_control(context_dock) - 1;
806
context_dock->set_tab_index(new_index, true);
807
dock_manager->_update_layout();
808
dock_select->queue_redraw();
809
}
810
811
void DockContextPopup::_tab_move_right() {
812
TabContainer *tab_container = context_dock->get_parent_container();
813
if (!tab_container) {
814
return;
815
}
816
int new_index = tab_container->get_tab_idx_from_control(context_dock) + 1;
817
context_dock->set_tab_index(new_index, true);
818
dock_manager->_update_layout();
819
dock_select->queue_redraw();
820
}
821
822
void DockContextPopup::_close_dock() {
823
hide();
824
context_dock->emit_signal("closed");
825
dock_manager->close_dock(context_dock);
826
}
827
828
void DockContextPopup::_float_dock() {
829
hide();
830
dock_manager->_open_dock_in_window(context_dock);
831
}
832
833
void DockContextPopup::_dock_select_input(const Ref<InputEvent> &p_input) {
834
Ref<InputEventMouse> me = p_input;
835
836
if (me.is_valid()) {
837
Vector2 point = me->get_position();
838
839
int over_dock_slot = -1;
840
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
841
if (dock_select_rects[i].has_point(point)) {
842
over_dock_slot = i;
843
break;
844
}
845
}
846
847
if (over_dock_slot != dock_select_rect_over_idx) {
848
dock_select->queue_redraw();
849
dock_select_rect_over_idx = over_dock_slot;
850
}
851
852
if (over_dock_slot == -1) {
853
return;
854
}
855
856
Ref<InputEventMouseButton> mb = me;
857
DockTabContainer *target_tab_container = dock_manager->dock_slots[over_dock_slot];
858
if (context_dock->get_parent_container() == target_tab_container) {
859
return;
860
}
861
862
if (!(context_dock->available_layouts & target_tab_container->layout)) {
863
return;
864
}
865
866
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
867
dock_manager->_move_dock(context_dock, target_tab_container, target_tab_container->get_tab_count());
868
dock_manager->_update_layout();
869
hide();
870
}
871
}
872
}
873
874
void DockContextPopup::_dock_select_mouse_exited() {
875
dock_select_rect_over_idx = -1;
876
dock_select->queue_redraw();
877
}
878
879
void DockContextPopup::_dock_select_draw() {
880
Color used_dock_color = Color(0.6, 0.6, 0.6, 0.8);
881
Color hovered_dock_color = Color(0.8, 0.8, 0.8, 0.8);
882
Color tab_selected_color = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
883
Color tab_unselected_color = used_dock_color;
884
Color unused_dock_color = used_dock_color;
885
unused_dock_color.a = 0.4;
886
Color unusable_dock_color = unused_dock_color;
887
unusable_dock_color.a = 0.1;
888
889
// Update sizes.
890
Size2 dock_size = dock_select->get_size();
891
dock_size.x /= 6.0;
892
dock_size.y /= 2.0;
893
894
real_t center_panel_width = dock_size.x * 2.0;
895
Rect2 center_panel_rect(center_panel_width, 0, center_panel_width, dock_size.y);
896
897
if (dock_select->is_layout_rtl()) {
898
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(), dock_size);
899
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(0, dock_size.y), dock_size);
900
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x, 0), dock_size);
901
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BL] = Rect2(dock_size, dock_size);
902
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
903
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BR] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
904
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UL] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
905
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BL] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
906
} else {
907
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UL] = Rect2(Point2(), dock_size);
908
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BL] = Rect2(Point2(0, dock_size.y), dock_size);
909
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x, 0), dock_size);
910
dock_select_rects[EditorDock::DOCK_SLOT_LEFT_BR] = Rect2(dock_size, dock_size);
911
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
912
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BL] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
913
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
914
dock_select_rects[EditorDock::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
915
}
916
dock_select_rects[EditorDock::DOCK_SLOT_BOTTOM] = Rect2(center_panel_width, dock_size.y, center_panel_width, dock_size.y);
917
918
int rtl_dir = dock_select->is_layout_rtl() ? -1 : 1;
919
real_t tab_height = 3.0 * EDSCALE;
920
real_t tab_spacing = 1.0 * EDSCALE;
921
real_t dock_spacing = 2.0 * EDSCALE;
922
real_t dock_top_spacing = tab_height + dock_spacing;
923
924
TabContainer *context_tab_container = context_dock->get_parent_container();
925
int context_tab_index = -1;
926
if (context_tab_container && context_tab_container->get_tab_count() > 0) {
927
context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
928
}
929
930
// Draw center panel.
931
Rect2 center_panel_draw_rect = center_panel_rect.grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
932
dock_select->draw_rect(center_panel_draw_rect, unusable_dock_color);
933
934
// Draw all dock slots.
935
for (int i = 0; i < EditorDock::DOCK_SLOT_MAX; i++) {
936
int max_tabs = (i == EditorDock::DOCK_SLOT_BOTTOM) ? 6 : 3;
937
const DockTabContainer *dock_slot = dock_manager->dock_slots[i];
938
939
Rect2 dock_slot_draw_rect = dock_select_rects[i].grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
940
real_t tab_width = Math::round(dock_slot_draw_rect.size.width / max_tabs);
941
Rect2 tab_draw_rect = Rect2(dock_slot_draw_rect.position.x, dock_select_rects[i].position.y, tab_width - tab_spacing, tab_height);
942
943
real_t max_width = tab_width * max_tabs;
944
// Tabs may not fit perfectly, so they need to be re-centered.
945
if (max_width > dock_slot_draw_rect.size.x) {
946
tab_draw_rect.position.x -= int(max_width - dock_slot_draw_rect.size.x) / 2 * rtl_dir;
947
}
948
if (dock_select->is_layout_rtl()) {
949
tab_draw_rect.position.x += dock_slot_draw_rect.size.x - tab_draw_rect.size.x;
950
}
951
952
int tabs_to_draw = MIN(max_tabs, dock_slot->get_tab_count());
953
bool is_context_dock = context_tab_container == dock_slot;
954
if (i == context_dock->dock_slot_index) {
955
dock_select->draw_rect(dock_slot_draw_rect, tab_selected_color);
956
} else if (!(context_dock->available_layouts & dock_slot->layout)) {
957
dock_select->draw_rect(dock_slot_draw_rect, unusable_dock_color);
958
} else if (i == dock_select_rect_over_idx) {
959
dock_select->draw_rect(dock_slot_draw_rect, hovered_dock_color);
960
} else if (tabs_to_draw == 0) {
961
dock_select->draw_rect(dock_slot_draw_rect, unused_dock_color);
962
} else {
963
dock_select->draw_rect(dock_slot_draw_rect, used_dock_color);
964
}
965
966
// Draw tabs above each used dock slot.
967
for (int j = 0; j < tabs_to_draw; j++) {
968
Color tab_color = tab_unselected_color;
969
if (is_context_dock && context_tab_index == j) {
970
tab_color = tab_selected_color;
971
}
972
Rect2 tabj_draw_rect = tab_draw_rect;
973
tabj_draw_rect.position.x += tab_width * j * rtl_dir;
974
dock_select->draw_rect(tabj_draw_rect, tab_color);
975
}
976
}
977
}
978
979
void DockContextPopup::_update_buttons() {
980
if (context_dock->global || context_dock->closable) {
981
close_button->set_tooltip_text(TTRC("Close this dock."));
982
close_button->set_disabled(false);
983
} else {
984
close_button->set_tooltip_text(TTRC("This dock can't be closed."));
985
close_button->set_disabled(true);
986
}
987
if (EditorNode::get_singleton()->is_multi_window_enabled()) {
988
if (!(context_dock->available_layouts & EditorDock::DOCK_LAYOUT_FLOATING)) {
989
make_float_button->set_tooltip_text(TTRC("This dock does not support floating."));
990
make_float_button->set_disabled(true);
991
} else {
992
make_float_button->set_tooltip_text(TTRC("Make this dock floating."));
993
make_float_button->set_disabled(false);
994
}
995
}
996
997
// Update tab move buttons.
998
tab_move_left_button->set_disabled(true);
999
tab_move_right_button->set_disabled(true);
1000
TabContainer *context_tab_container = context_dock->get_parent_container();
1001
if (context_tab_container && context_tab_container->get_tab_count() > 0) {
1002
int context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
1003
tab_move_left_button->set_disabled(context_tab_index == 0);
1004
tab_move_right_button->set_disabled(context_tab_index >= context_tab_container->get_tab_count() - 1);
1005
}
1006
reset_size();
1007
}
1008
1009
void DockContextPopup::set_dock(EditorDock *p_dock) {
1010
context_dock = p_dock;
1011
_update_buttons();
1012
}
1013
1014
void DockContextPopup::docks_updated() {
1015
if (!is_visible()) {
1016
return;
1017
}
1018
_update_buttons();
1019
}
1020
1021
DockContextPopup::DockContextPopup() {
1022
dock_manager = EditorDockManager::get_singleton();
1023
1024
dock_select_popup_vb = memnew(VBoxContainer);
1025
add_child(dock_select_popup_vb);
1026
1027
HBoxContainer *header_hb = memnew(HBoxContainer);
1028
tab_move_left_button = memnew(Button);
1029
tab_move_left_button->set_accessibility_name(TTRC("Move Tab Left"));
1030
tab_move_left_button->set_flat(true);
1031
tab_move_left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1032
tab_move_left_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_left));
1033
header_hb->add_child(tab_move_left_button);
1034
1035
Label *position_label = memnew(Label);
1036
position_label->set_text(TTRC("Dock Position"));
1037
position_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1038
position_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1039
header_hb->add_child(position_label);
1040
1041
tab_move_right_button = memnew(Button);
1042
tab_move_right_button->set_accessibility_name(TTRC("Move Tab Right"));
1043
tab_move_right_button->set_flat(true);
1044
tab_move_right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1045
tab_move_right_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_right));
1046
1047
header_hb->add_child(tab_move_right_button);
1048
dock_select_popup_vb->add_child(header_hb);
1049
1050
dock_select = memnew(Control);
1051
dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
1052
dock_select->connect(SceneStringName(gui_input), callable_mp(this, &DockContextPopup::_dock_select_input));
1053
dock_select->connect(SceneStringName(draw), callable_mp(this, &DockContextPopup::_dock_select_draw));
1054
dock_select->connect(SceneStringName(mouse_exited), callable_mp(this, &DockContextPopup::_dock_select_mouse_exited));
1055
dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1056
dock_select_popup_vb->add_child(dock_select);
1057
1058
make_float_button = memnew(Button);
1059
make_float_button->set_text(TTRC("Make Floating"));
1060
if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
1061
make_float_button->set_disabled(true);
1062
make_float_button->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
1063
}
1064
make_float_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1065
make_float_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1066
make_float_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_float_dock));
1067
dock_select_popup_vb->add_child(make_float_button);
1068
1069
close_button = memnew(Button);
1070
close_button->set_text(TTRC("Close"));
1071
close_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1072
close_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1073
close_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_close_dock));
1074
dock_select_popup_vb->add_child(close_button);
1075
}
1076
1077
void DockShortcutHandler::shortcut_input(const Ref<InputEvent> &p_event) {
1078
if (p_event.is_null() || !p_event->is_pressed() || p_event->is_echo()) {
1079
return;
1080
}
1081
1082
for (EditorDock *dock : EditorDockManager::get_singleton()->all_docks) {
1083
const Ref<Shortcut> &dock_shortcut = dock->get_dock_shortcut();
1084
if (dock_shortcut.is_valid() && dock_shortcut->matches_event(p_event)) {
1085
bool was_visible = dock->is_visible();
1086
if (!dock->transient || dock->is_open) {
1087
EditorDockManager::get_singleton()->focus_dock(dock);
1088
}
1089
DockTabContainer *dock_container = dock->get_parent_container();
1090
if (dock_container) {
1091
dock_container->dock_focused(dock, was_visible);
1092
}
1093
get_viewport()->set_input_as_handled();
1094
break;
1095
}
1096
}
1097
}
1098
1099