Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/docks/editor_dock_manager.cpp
9896 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/editor_node.h"
41
#include "editor/editor_string_names.h"
42
#include "editor/gui/editor_bottom_panel.h"
43
#include "editor/gui/window_wrapper.h"
44
#include "editor/settings/editor_settings.h"
45
#include "editor/themes/editor_scale.h"
46
#include "scene/resources/style_box_flat.h"
47
48
enum class TabStyle {
49
TEXT_ONLY,
50
ICON_ONLY,
51
TEXT_AND_ICON,
52
};
53
54
EditorDockManager *EditorDockManager::singleton = nullptr;
55
56
bool EditorDockDragHint::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
57
return can_drop_dock;
58
}
59
60
void EditorDockDragHint::drop_data(const Point2 &p_point, const Variant &p_data) {
61
// Drop dock into last spot if not over tabbar.
62
if (drop_tabbar->get_rect().has_point(p_point)) {
63
drop_tabbar->_handle_drop_data("tab_container_tab", p_point, p_data, callable_mp(this, &EditorDockDragHint::_drag_move_tab), callable_mp(this, &EditorDockDragHint::_drag_move_tab_from));
64
} else {
65
dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], drop_tabbar->get_tab_count());
66
}
67
}
68
69
void EditorDockDragHint::_drag_move_tab(int p_from_index, int p_to_index) {
70
dock_manager->_move_dock_tab_index(dock_manager->_get_dock_tab_dragged(), p_to_index, true);
71
}
72
73
void EditorDockDragHint::_drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
74
dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], p_to_index);
75
}
76
77
void EditorDockDragHint::gui_input(const Ref<InputEvent> &p_event) {
78
ERR_FAIL_COND(p_event.is_null());
79
80
Ref<InputEventMouseMotion> mm = p_event;
81
if (mm.is_valid()) {
82
Point2 pos = mm->get_position();
83
84
// Redraw when inside the tabbar and just exited.
85
if (mouse_inside_tabbar) {
86
queue_redraw();
87
}
88
mouse_inside_tabbar = drop_tabbar->get_rect().has_point(pos);
89
}
90
}
91
92
void EditorDockDragHint::set_slot(EditorDockManager::DockSlot p_slot) {
93
occupied_slot = p_slot;
94
drop_tabbar = dock_manager->dock_slot[occupied_slot]->get_tab_bar();
95
}
96
97
void EditorDockDragHint::_notification(int p_what) {
98
switch (p_what) {
99
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
100
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme")) {
101
dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());
102
if (mouse_inside) {
103
queue_redraw();
104
}
105
}
106
} break;
107
108
case NOTIFICATION_THEME_CHANGED: {
109
valid_drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
110
} break;
111
112
case NOTIFICATION_MOUSE_ENTER:
113
case NOTIFICATION_MOUSE_EXIT: {
114
mouse_inside = p_what == NOTIFICATION_MOUSE_ENTER;
115
queue_redraw();
116
} break;
117
118
case NOTIFICATION_DRAG_BEGIN: {
119
Control *dragged_dock = dock_manager->_get_dock_tab_dragged();
120
if (!dragged_dock) {
121
return;
122
}
123
124
can_drop_dock = true;
125
126
dock_drop_highlight->set_border_color(valid_drop_color);
127
dock_drop_highlight->set_bg_color(valid_drop_color * Color(1, 1, 1, 0.1));
128
} break;
129
case NOTIFICATION_DRAG_END: {
130
dock_manager->_dock_drag_stopped();
131
can_drop_dock = false;
132
mouse_inside = false;
133
hide();
134
} break;
135
136
case NOTIFICATION_DRAW: {
137
if (!mouse_inside) {
138
return;
139
}
140
141
// Draw highlights around docks that can be dropped.
142
Rect2 dock_rect = Rect2(Point2(), get_size()).grow(2 * EDSCALE);
143
draw_style_box(dock_drop_highlight, dock_rect);
144
145
// Only display tabbar hint if the mouse is over the tabbar.
146
if (drop_tabbar->get_global_rect().has_point(get_global_mouse_position())) {
147
drop_tabbar->_draw_tab_drop(get_canvas_item());
148
}
149
} break;
150
}
151
}
152
153
EditorDockDragHint::EditorDockDragHint() {
154
dock_manager = EditorDockManager::get_singleton();
155
156
set_as_top_level(true);
157
dock_drop_highlight.instantiate();
158
dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());
159
dock_drop_highlight->set_border_width_all(Math::round(2 * EDSCALE));
160
}
161
162
////////////////////////////////////////////////
163
////////////////////////////////////////////////
164
165
void DockSplitContainer::_update_visibility() {
166
if (is_updating) {
167
return;
168
}
169
is_updating = true;
170
bool any_visible = false;
171
for (int i = 0; i < get_child_count(false); i++) {
172
Control *c = Object::cast_to<Control>(get_child(i, false));
173
if (!c || !c->is_visible() || c->is_set_as_top_level()) {
174
continue;
175
}
176
any_visible = c;
177
break;
178
}
179
set_visible(any_visible);
180
is_updating = false;
181
}
182
183
void DockSplitContainer::add_child_notify(Node *p_child) {
184
SplitContainer::add_child_notify(p_child);
185
186
Control *child_control = nullptr;
187
for (int i = 0; i < get_child_count(false); i++) {
188
Control *c = Object::cast_to<Control>(get_child(i, false));
189
if (!c || c->is_set_as_top_level()) {
190
continue;
191
}
192
if (p_child == c) {
193
child_control = c;
194
break;
195
}
196
}
197
if (!child_control) {
198
return;
199
}
200
201
child_control->connect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));
202
_update_visibility();
203
}
204
205
void DockSplitContainer::remove_child_notify(Node *p_child) {
206
SplitContainer::remove_child_notify(p_child);
207
208
Control *child_control = nullptr;
209
for (int i = 0; i < get_child_count(false); i++) {
210
Control *c = Object::cast_to<Control>(get_child(i, false));
211
if (!c || c->is_set_as_top_level()) {
212
continue;
213
}
214
if (p_child == c) {
215
child_control = c;
216
break;
217
}
218
}
219
if (!child_control) {
220
return;
221
}
222
223
child_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &DockSplitContainer::_update_visibility));
224
_update_visibility();
225
}
226
227
DockSplitContainer::DockSplitContainer() {
228
if (EDITOR_GET("interface/touchscreen/enable_touch_optimizations")) {
229
callable_mp((SplitContainer *)this, &SplitContainer::set_touch_dragger_enabled).call_deferred(true);
230
}
231
}
232
233
////////////////////////////////////////////////
234
////////////////////////////////////////////////
235
236
Control *EditorDockManager::_get_dock_tab_dragged() {
237
if (dock_tab_dragged) {
238
return dock_tab_dragged;
239
}
240
241
Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data();
242
243
// Check if we are dragging a dock.
244
const String type = dock_drop_data.get("type", "");
245
if (type == "tab_container_tab") {
246
Node *from_node = dock_slot[DOCK_SLOT_LEFT_BL]->get_node(dock_drop_data["from_path"]);
247
if (!from_node) {
248
return nullptr;
249
}
250
251
TabContainer *parent = Object::cast_to<TabContainer>(from_node->get_parent());
252
if (!parent) {
253
return nullptr;
254
}
255
256
// TODO: Update logic when GH-106503 is merged to cast directly to EditorDock instead of the below check.
257
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
258
if (dock_slot[i] == parent) {
259
dock_tab_dragged = parent->get_tab_control(dock_drop_data["tab_index"]);
260
break;
261
}
262
}
263
if (!dock_tab_dragged) {
264
return nullptr;
265
}
266
267
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
268
if (dock_slot[i]->is_visible_in_tree()) {
269
dock_drag_rects[i]->set_rect(dock_slot[i]->get_global_rect());
270
dock_drag_rects[i]->show();
271
}
272
}
273
274
return dock_tab_dragged;
275
}
276
return nullptr;
277
}
278
279
void EditorDockManager::_dock_drag_stopped() {
280
dock_tab_dragged = nullptr;
281
}
282
283
void EditorDockManager::_dock_split_dragged(int p_offset) {
284
EditorNode::get_singleton()->save_editor_layout_delayed();
285
}
286
287
void EditorDockManager::_dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container) {
288
Ref<InputEventMouseButton> mb = p_input;
289
290
if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
291
int tab_id = p_dock_container->get_tab_bar()->get_hovered_tab();
292
if (tab_id < 0) {
293
return;
294
}
295
296
// Right click context menu.
297
dock_context_popup->set_dock(p_dock_container->get_tab_control(tab_id));
298
dock_context_popup->set_position(p_dock_container->get_screen_position() + mb->get_position());
299
dock_context_popup->popup();
300
}
301
}
302
303
void EditorDockManager::_bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button) {
304
Ref<InputEventMouseButton> mb = p_input;
305
306
if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
307
// Right click context menu.
308
dock_context_popup->set_dock(p_dock);
309
dock_context_popup->set_position(p_bottom_button->get_screen_position() + mb->get_position());
310
dock_context_popup->popup();
311
}
312
}
313
314
void EditorDockManager::_dock_container_update_visibility(TabContainer *p_dock_container) {
315
if (!docks_visible) {
316
return;
317
}
318
// Hide the dock container if there are no tabs.
319
p_dock_container->set_visible(p_dock_container->get_tab_count() > 0);
320
}
321
322
void EditorDockManager::_update_layout() {
323
if (!dock_context_popup->is_inside_tree() || EditorNode::get_singleton()->is_exiting()) {
324
return;
325
}
326
dock_context_popup->docks_updated();
327
update_docks_menu();
328
EditorNode::get_singleton()->save_editor_layout_delayed();
329
}
330
331
void EditorDockManager::update_docks_menu() {
332
docks_menu->clear();
333
docks_menu->reset_size();
334
335
const Ref<Texture2D> default_icon = docks_menu->get_editor_theme_icon(SNAME("Window"));
336
const Color closed_icon_color_mod = Color(1, 1, 1, 0.5);
337
338
bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU);
339
bool dark_mode = DisplayServer::get_singleton()->is_dark_mode_supported() && DisplayServer::get_singleton()->is_dark_mode();
340
341
// Add docks.
342
docks_menu_docks.clear();
343
int id = 0;
344
for (const KeyValue<Control *, DockInfo> &dock : all_docks) {
345
if (!dock.value.enabled) {
346
continue;
347
}
348
if (dock.value.shortcut.is_valid()) {
349
docks_menu->add_shortcut(dock.value.shortcut, id);
350
docks_menu->set_item_text(id, dock.value.title);
351
} else {
352
docks_menu->add_item(dock.value.title, id);
353
}
354
const Ref<Texture2D> icon = dock.value.icon_name ? docks_menu->get_editor_theme_native_menu_icon(dock.value.icon_name, global_menu, dark_mode) : dock.value.icon;
355
docks_menu->set_item_icon(id, icon.is_valid() ? icon : default_icon);
356
if (!dock.value.open) {
357
docks_menu->set_item_icon_modulate(id, closed_icon_color_mod);
358
docks_menu->set_item_tooltip(id, vformat(TTR("Open the %s dock."), dock.value.title));
359
} else {
360
docks_menu->set_item_tooltip(id, vformat(TTR("Focus on the %s dock."), dock.value.title));
361
}
362
docks_menu_docks.push_back(dock.key);
363
id++;
364
}
365
}
366
367
void EditorDockManager::_docks_menu_option(int p_id) {
368
Control *dock = docks_menu_docks[p_id];
369
ERR_FAIL_NULL(dock);
370
ERR_FAIL_COND_MSG(!all_docks.has(dock), vformat("Menu option for unknown dock '%s'.", dock->get_name()));
371
if (all_docks[dock].enabled && all_docks[dock].open) {
372
PopupMenu *parent_menu = Object::cast_to<PopupMenu>(docks_menu->get_parent());
373
ERR_FAIL_NULL(parent_menu);
374
parent_menu->hide();
375
}
376
focus_dock(dock);
377
}
378
379
void EditorDockManager::_window_close_request(WindowWrapper *p_wrapper) {
380
// Give the dock back to the original owner.
381
Control *dock = _close_window(p_wrapper);
382
ERR_FAIL_COND(!all_docks.has(dock));
383
384
if (all_docks[dock].previous_at_bottom || all_docks[dock].dock_slot_index != DOCK_SLOT_NONE) {
385
all_docks[dock].open = false;
386
open_dock(dock);
387
focus_dock(dock);
388
} else {
389
close_dock(dock);
390
}
391
}
392
393
Control *EditorDockManager::_close_window(WindowWrapper *p_wrapper) {
394
p_wrapper->set_block_signals(true);
395
Control *dock = p_wrapper->release_wrapped_control();
396
p_wrapper->set_block_signals(false);
397
ERR_FAIL_COND_V(!all_docks.has(dock), nullptr);
398
399
all_docks[dock].dock_window = nullptr;
400
dock_windows.erase(p_wrapper);
401
p_wrapper->queue_free();
402
return dock;
403
}
404
405
void EditorDockManager::_open_dock_in_window(Control *p_dock, bool p_show_window, bool p_reset_size) {
406
ERR_FAIL_NULL(p_dock);
407
408
Size2 borders = Size2(4, 4) * EDSCALE;
409
// Remember size and position before removing it from the main window.
410
Size2 dock_size = p_dock->get_size() + borders * 2;
411
Point2 dock_screen_pos = p_dock->get_screen_position();
412
413
WindowWrapper *wrapper = memnew(WindowWrapper);
414
wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), all_docks[p_dock].title));
415
wrapper->set_margins_enabled(true);
416
417
EditorNode::get_singleton()->get_gui_base()->add_child(wrapper);
418
419
_move_dock(p_dock, nullptr);
420
wrapper->set_wrapped_control(p_dock);
421
422
all_docks[p_dock].dock_window = wrapper;
423
all_docks[p_dock].open = true;
424
p_dock->show();
425
426
wrapper->connect("window_close_requested", callable_mp(this, &EditorDockManager::_window_close_request).bind(wrapper));
427
dock_windows.push_back(wrapper);
428
429
if (p_show_window) {
430
wrapper->restore_window(Rect2i(dock_screen_pos, dock_size), EditorNode::get_singleton()->get_gui_base()->get_window()->get_current_screen());
431
_update_layout();
432
if (p_reset_size) {
433
// Use a default size of one third the current window size.
434
Size2i popup_size = EditorNode::get_singleton()->get_window()->get_size() / 3.0;
435
p_dock->get_window()->set_size(popup_size);
436
p_dock->get_window()->move_to_center();
437
}
438
p_dock->get_window()->grab_focus();
439
}
440
}
441
442
void EditorDockManager::_restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump) {
443
if (!all_docks[p_dock].dock_window) {
444
_open_dock_in_window(p_dock, false);
445
}
446
447
all_docks[p_dock].dock_window->restore_window_from_saved_position(
448
p_window_dump.get("window_rect", Rect2i()),
449
p_window_dump.get("window_screen", -1),
450
p_window_dump.get("window_screen_rect", Rect2i()));
451
}
452
453
void EditorDockManager::_dock_move_to_bottom(Control *p_dock, bool p_visible) {
454
_move_dock(p_dock, nullptr);
455
456
all_docks[p_dock].at_bottom = true;
457
all_docks[p_dock].previous_at_bottom = false;
458
459
p_dock->call("_set_dock_horizontal", true);
460
461
// Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
462
Button *bottom_button = EditorNode::get_bottom_panel()->add_item(all_docks[p_dock].title, p_dock, all_docks[p_dock].shortcut, true);
463
bottom_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock));
464
EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible);
465
}
466
467
void EditorDockManager::_dock_remove_from_bottom(Control *p_dock) {
468
all_docks[p_dock].at_bottom = false;
469
all_docks[p_dock].previous_at_bottom = true;
470
471
EditorNode::get_bottom_panel()->remove_item(p_dock);
472
p_dock->call("_set_dock_horizontal", false);
473
}
474
475
bool EditorDockManager::_is_dock_at_bottom(Control *p_dock) {
476
ERR_FAIL_COND_V(!all_docks.has(p_dock), false);
477
return all_docks[p_dock].at_bottom;
478
}
479
480
void EditorDockManager::_move_dock_tab_index(Control *p_dock, int p_tab_index, bool p_set_current) {
481
TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_dock->get_parent());
482
if (!dock_tab_container) {
483
return;
484
}
485
486
dock_tab_container->set_block_signals(true);
487
int target_index = CLAMP(p_tab_index, 0, dock_tab_container->get_tab_count() - 1);
488
dock_tab_container->move_child(p_dock, dock_tab_container->get_tab_control(target_index)->get_index(false));
489
all_docks[p_dock].previous_tab_index = target_index;
490
491
if (p_set_current) {
492
dock_tab_container->set_current_tab(target_index);
493
}
494
dock_tab_container->set_block_signals(false);
495
}
496
497
void EditorDockManager::_move_dock(Control *p_dock, Control *p_target, int p_tab_index, bool p_set_current) {
498
ERR_FAIL_NULL(p_dock);
499
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot move unknown dock '%s'.", p_dock->get_name()));
500
501
Node *parent = p_dock->get_parent();
502
if (parent == p_target) {
503
if (p_tab_index >= 0 && parent) {
504
// Only change the tab index.
505
_move_dock_tab_index(p_dock, p_tab_index, p_set_current);
506
}
507
return;
508
}
509
510
// Remove dock from its existing parent.
511
if (parent) {
512
if (all_docks[p_dock].dock_window) {
513
_close_window(all_docks[p_dock].dock_window);
514
} else if (all_docks[p_dock].at_bottom) {
515
_dock_remove_from_bottom(p_dock);
516
} else {
517
all_docks[p_dock].previous_at_bottom = false;
518
TabContainer *parent_tabs = Object::cast_to<TabContainer>(parent);
519
if (parent_tabs) {
520
all_docks[p_dock].previous_tab_index = parent_tabs->get_tab_idx_from_control(p_dock);
521
}
522
parent->set_block_signals(true);
523
parent->remove_child(p_dock);
524
parent->set_block_signals(false);
525
if (parent_tabs) {
526
_dock_container_update_visibility(parent_tabs);
527
}
528
}
529
}
530
531
// Add dock to its new parent, at the given tab index.
532
if (!p_target) {
533
return;
534
}
535
p_target->set_block_signals(true);
536
p_target->add_child(p_dock);
537
p_target->set_block_signals(false);
538
TabContainer *dock_tab_container = Object::cast_to<TabContainer>(p_target);
539
if (dock_tab_container) {
540
if (dock_tab_container->is_inside_tree()) {
541
_update_tab_style(p_dock);
542
}
543
if (p_tab_index >= 0) {
544
_move_dock_tab_index(p_dock, p_tab_index, p_set_current);
545
}
546
_dock_container_update_visibility(dock_tab_container);
547
}
548
}
549
550
void EditorDockManager::_update_tab_style(Control *p_dock) {
551
const DockInfo &dock_info = all_docks[p_dock];
552
if (!dock_info.enabled || !dock_info.open) {
553
return; // Disabled by feature profile or manually closed by user.
554
}
555
if (dock_info.dock_window || dock_info.at_bottom) {
556
return; // Floating or sent to bottom.
557
}
558
559
TabContainer *tab_container = get_dock_tab_container(p_dock);
560
ERR_FAIL_NULL(tab_container);
561
int index = tab_container->get_tab_idx_from_control(p_dock);
562
ERR_FAIL_COND(index == -1);
563
564
const TabStyle style = (TabStyle)EDITOR_GET("interface/editor/dock_tab_style").operator int();
565
switch (style) {
566
case TabStyle::TEXT_ONLY: {
567
tab_container->set_tab_title(index, dock_info.title);
568
tab_container->set_tab_icon(index, Ref<Texture2D>());
569
tab_container->set_tab_tooltip(index, String());
570
} break;
571
case TabStyle::ICON_ONLY: {
572
const Ref<Texture2D> icon = dock_info.icon_name ? tab_container->get_editor_theme_icon(dock_info.icon_name) : dock_info.icon;
573
tab_container->set_tab_title(index, icon.is_valid() ? String() : dock_info.title);
574
tab_container->set_tab_icon(index, icon);
575
tab_container->set_tab_tooltip(index, icon.is_valid() ? dock_info.title : String());
576
} break;
577
case TabStyle::TEXT_AND_ICON: {
578
const Ref<Texture2D> icon = dock_info.icon_name ? tab_container->get_editor_theme_icon(dock_info.icon_name) : dock_info.icon;
579
tab_container->set_tab_title(index, dock_info.title);
580
tab_container->set_tab_icon(index, icon);
581
tab_container->set_tab_tooltip(index, String());
582
} break;
583
}
584
}
585
586
void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section) const {
587
// Save docks by dock slot.
588
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
589
String names;
590
for (int j = 0; j < dock_slot[i]->get_tab_count(); j++) {
591
String name = dock_slot[i]->get_tab_control(j)->get_name();
592
if (!names.is_empty()) {
593
names += ",";
594
}
595
names += name;
596
}
597
598
String config_key = "dock_" + itos(i + 1);
599
600
if (p_layout->has_section_key(p_section, config_key)) {
601
p_layout->erase_section_key(p_section, config_key);
602
}
603
604
if (!names.is_empty()) {
605
p_layout->set_value(p_section, config_key, names);
606
}
607
608
int selected_tab_idx = dock_slot[i]->get_current_tab();
609
if (selected_tab_idx >= 0) {
610
p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx);
611
}
612
}
613
if (p_layout->has_section_key(p_section, "dock_0")) {
614
// Clear the keys where the dock has no slot so it is overridden.
615
p_layout->erase_section_key(p_section, "dock_0");
616
}
617
618
// Save docks in windows.
619
Dictionary floating_docks_dump;
620
for (WindowWrapper *wrapper : dock_windows) {
621
Control *dock = wrapper->get_wrapped_control();
622
623
Dictionary window_dump;
624
window_dump["window_rect"] = wrapper->get_window_rect();
625
626
int screen = wrapper->get_window_screen();
627
window_dump["window_screen"] = wrapper->get_window_screen();
628
window_dump["window_screen_rect"] = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
629
630
String name = dock->get_name();
631
floating_docks_dump[name] = window_dump;
632
633
// Append to regular dock section so we know where to restore it to.
634
int dock_slot_id = all_docks[dock].dock_slot_index;
635
String config_key = "dock_" + itos(dock_slot_id + 1);
636
637
String names = p_layout->get_value(p_section, config_key, "");
638
if (names.is_empty()) {
639
names = name;
640
} else {
641
names += "," + name;
642
}
643
p_layout->set_value(p_section, config_key, names);
644
}
645
p_layout->set_value(p_section, "dock_floating", floating_docks_dump);
646
647
// Save closed and bottom docks.
648
Array bottom_docks_dump;
649
Array closed_docks_dump;
650
for (const KeyValue<Control *, DockInfo> &d : all_docks) {
651
d.key->call(SNAME("_save_layout_to_config"), p_layout, p_section);
652
653
if (!d.value.at_bottom && d.value.open && (!d.value.previous_at_bottom || !d.value.dock_window)) {
654
continue;
655
}
656
// Use the name of the Control since it isn't translated.
657
String name = d.key->get_name();
658
if (d.value.at_bottom || (d.value.previous_at_bottom && d.value.dock_window)) {
659
bottom_docks_dump.push_back(name);
660
}
661
if (!d.value.open) {
662
closed_docks_dump.push_back(name);
663
}
664
665
int dock_slot_id = all_docks[d.key].dock_slot_index;
666
String config_key = "dock_" + itos(dock_slot_id + 1);
667
668
String names = p_layout->get_value(p_section, config_key, "");
669
if (names.is_empty()) {
670
names = name;
671
} else {
672
names += "," + name;
673
}
674
p_layout->set_value(p_section, config_key, names);
675
}
676
p_layout->set_value(p_section, "dock_bottom", bottom_docks_dump);
677
p_layout->set_value(p_section, "dock_closed", closed_docks_dump);
678
679
// Save SplitContainer offsets.
680
for (int i = 0; i < vsplits.size(); i++) {
681
if (vsplits[i]->is_visible_in_tree()) {
682
p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
683
}
684
}
685
686
for (int i = 0; i < hsplits.size(); i++) {
687
p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE));
688
}
689
}
690
691
void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section, bool p_first_load) {
692
Dictionary floating_docks_dump = p_layout->get_value(p_section, "dock_floating", Dictionary());
693
Array dock_bottom = p_layout->get_value(p_section, "dock_bottom", Array());
694
Array closed_docks = p_layout->get_value(p_section, "dock_closed", Array());
695
696
bool allow_floating_docks = EditorNode::get_singleton()->is_multi_window_enabled() && (!p_first_load || EDITOR_GET("interface/multi_window/restore_windows_on_load"));
697
698
// Store the docks by name for easy lookup.
699
HashMap<String, Control *> dock_map;
700
for (const KeyValue<Control *, DockInfo> &dock : all_docks) {
701
dock_map[dock.key->get_name()] = dock.key;
702
}
703
704
// Load docks by slot. Index -1 is for docks that have no slot.
705
for (int i = -1; i < DOCK_SLOT_MAX; i++) {
706
if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1))) {
707
continue;
708
}
709
710
Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(",");
711
712
for (int j = names.size() - 1; j >= 0; j--) {
713
String name = names[j];
714
715
if (!dock_map.has(name)) {
716
continue;
717
}
718
Control *dock = dock_map[name];
719
720
if (!all_docks[dock].enabled) {
721
// Don't open disabled docks.
722
dock->call(SNAME("_load_layout_from_config"), p_layout, p_section);
723
continue;
724
}
725
bool at_bottom = false;
726
if (allow_floating_docks && floating_docks_dump.has(name)) {
727
all_docks[dock].previous_at_bottom = dock_bottom.has(name);
728
_restore_dock_to_saved_window(dock, floating_docks_dump[name]);
729
} else if (dock_bottom.has(name)) {
730
_dock_move_to_bottom(dock, false);
731
at_bottom = true;
732
} else if (i >= 0) {
733
_move_dock(dock, dock_slot[i], 0);
734
}
735
dock->call(SNAME("_load_layout_from_config"), p_layout, p_section);
736
737
if (closed_docks.has(name)) {
738
_move_dock(dock, closed_dock_parent);
739
all_docks[dock].open = false;
740
dock->hide();
741
} else {
742
// Make sure it is open.
743
all_docks[dock].open = true;
744
// It's important to not update the visibility of bottom panels.
745
// Visibility of bottom panels are managed in EditorBottomPanel.
746
if (!at_bottom) {
747
dock->show();
748
}
749
}
750
751
all_docks[dock].dock_slot_index = i;
752
all_docks[dock].previous_tab_index = i >= 0 ? j : 0;
753
}
754
}
755
756
// Set the selected tabs.
757
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
758
if (dock_slot[i]->get_tab_count() == 0 || !p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) {
759
continue;
760
}
761
int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx");
762
if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) {
763
dock_slot[i]->set_block_signals(true);
764
dock_slot[i]->set_current_tab(selected_tab_idx);
765
dock_slot[i]->set_block_signals(false);
766
}
767
}
768
769
// Load SplitContainer offsets.
770
for (int i = 0; i < vsplits.size(); i++) {
771
if (!p_layout->has_section_key(p_section, "dock_split_" + itos(i + 1))) {
772
continue;
773
}
774
int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
775
vsplits[i]->set_split_offset(ofs);
776
}
777
778
for (int i = 0; i < hsplits.size(); i++) {
779
if (!p_layout->has_section_key(p_section, "dock_hsplit_" + itos(i + 1))) {
780
continue;
781
}
782
int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
783
hsplits[i]->set_split_offset(ofs * EDSCALE);
784
}
785
update_docks_menu();
786
}
787
788
void EditorDockManager::bottom_dock_show_placement_popup(const Rect2i &p_position, Control *p_dock) {
789
ERR_FAIL_COND(!all_docks.has(p_dock));
790
791
dock_context_popup->set_dock(p_dock);
792
793
Vector2 popup_pos = p_position.position;
794
popup_pos.y += p_position.size.height;
795
796
if (!EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
797
popup_pos.x -= dock_context_popup->get_size().width;
798
popup_pos.x += p_position.size.width;
799
}
800
dock_context_popup->set_position(popup_pos);
801
dock_context_popup->popup();
802
}
803
804
void EditorDockManager::set_dock_enabled(Control *p_dock, bool p_enabled) {
805
ERR_FAIL_NULL(p_dock);
806
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set enabled unknown dock '%s'.", p_dock->get_name()));
807
808
if (all_docks[p_dock].enabled == p_enabled) {
809
return;
810
}
811
812
all_docks[p_dock].enabled = p_enabled;
813
if (p_enabled) {
814
open_dock(p_dock, false);
815
} else {
816
close_dock(p_dock);
817
}
818
}
819
820
void EditorDockManager::close_dock(Control *p_dock) {
821
ERR_FAIL_NULL(p_dock);
822
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot close unknown dock '%s'.", p_dock->get_name()));
823
824
if (!all_docks[p_dock].open) {
825
return;
826
}
827
828
_move_dock(p_dock, closed_dock_parent);
829
830
all_docks[p_dock].open = false;
831
p_dock->hide();
832
833
_update_layout();
834
}
835
836
void EditorDockManager::open_dock(Control *p_dock, bool p_set_current) {
837
ERR_FAIL_NULL(p_dock);
838
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot open unknown dock '%s'.", p_dock->get_name()));
839
840
if (all_docks[p_dock].open) {
841
return;
842
}
843
844
all_docks[p_dock].open = true;
845
p_dock->show();
846
847
// Open dock to its previous location.
848
if (all_docks[p_dock].previous_at_bottom) {
849
_dock_move_to_bottom(p_dock, true);
850
} else if (all_docks[p_dock].dock_slot_index != DOCK_SLOT_NONE) {
851
TabContainer *slot = dock_slot[all_docks[p_dock].dock_slot_index];
852
int tab_index = all_docks[p_dock].previous_tab_index;
853
if (tab_index < 0) {
854
tab_index = slot->get_tab_count();
855
}
856
_move_dock(p_dock, slot, tab_index, p_set_current);
857
} else {
858
_open_dock_in_window(p_dock, true, true);
859
return;
860
}
861
862
_update_layout();
863
}
864
865
TabContainer *EditorDockManager::get_dock_tab_container(Control *p_dock) const {
866
return Object::cast_to<TabContainer>(p_dock->get_parent());
867
}
868
869
void EditorDockManager::focus_dock(Control *p_dock) {
870
ERR_FAIL_NULL(p_dock);
871
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot focus unknown dock '%s'.", p_dock->get_name()));
872
873
if (!all_docks[p_dock].enabled) {
874
return;
875
}
876
877
if (!all_docks[p_dock].open) {
878
open_dock(p_dock);
879
}
880
881
if (all_docks[p_dock].dock_window) {
882
p_dock->get_window()->grab_focus();
883
return;
884
}
885
886
if (all_docks[p_dock].at_bottom) {
887
EditorNode::get_bottom_panel()->make_item_visible(p_dock, true, true);
888
return;
889
}
890
891
if (!docks_visible) {
892
return;
893
}
894
895
TabContainer *tab_container = get_dock_tab_container(p_dock);
896
if (!tab_container) {
897
return;
898
}
899
int tab_index = tab_container->get_tab_idx_from_control(p_dock);
900
tab_container->get_tab_bar()->grab_focus();
901
tab_container->set_current_tab(tab_index);
902
}
903
904
void EditorDockManager::add_dock(Control *p_dock, const String &p_title, DockSlot p_slot, const Ref<Shortcut> &p_shortcut, const StringName &p_icon_name) {
905
ERR_FAIL_NULL(p_dock);
906
ERR_FAIL_COND_MSG(all_docks.has(p_dock), vformat("Cannot add dock '%s', already added.", p_dock->get_name()));
907
908
DockInfo dock_info;
909
dock_info.title = p_title.is_empty() ? String(p_dock->get_name()) : p_title;
910
dock_info.dock_slot_index = p_slot;
911
dock_info.shortcut = p_shortcut;
912
dock_info.icon_name = p_icon_name;
913
all_docks[p_dock] = dock_info;
914
915
if (p_slot != DOCK_SLOT_NONE) {
916
ERR_FAIL_INDEX(p_slot, DOCK_SLOT_MAX);
917
open_dock(p_dock, false);
918
} else {
919
closed_dock_parent->add_child(p_dock);
920
p_dock->hide();
921
_update_layout();
922
}
923
}
924
925
void EditorDockManager::remove_dock(Control *p_dock) {
926
ERR_FAIL_NULL(p_dock);
927
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot remove unknown dock '%s'.", p_dock->get_name()));
928
929
_move_dock(p_dock, nullptr);
930
931
all_docks.erase(p_dock);
932
_update_layout();
933
}
934
935
void EditorDockManager::set_dock_tab_icon(Control *p_dock, const Ref<Texture2D> &p_icon) {
936
ERR_FAIL_NULL(p_dock);
937
ERR_FAIL_COND_MSG(!all_docks.has(p_dock), vformat("Cannot set tab icon for unknown dock '%s'.", p_dock->get_name()));
938
939
all_docks[p_dock].icon = p_icon;
940
_update_tab_style(p_dock);
941
}
942
943
void EditorDockManager::set_docks_visible(bool p_show) {
944
if (docks_visible == p_show) {
945
return;
946
}
947
docks_visible = p_show;
948
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
949
dock_slot[i]->set_visible(docks_visible && dock_slot[i]->get_tab_count() > 0);
950
}
951
_update_layout();
952
}
953
954
bool EditorDockManager::are_docks_visible() const {
955
return docks_visible;
956
}
957
958
void EditorDockManager::update_tab_styles() {
959
for (const KeyValue<Control *, DockInfo> &dock : all_docks) {
960
_update_tab_style(dock.key);
961
}
962
}
963
964
void EditorDockManager::set_tab_icon_max_width(int p_max_width) {
965
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
966
TabContainer *tab_container = dock_slot[i];
967
tab_container->add_theme_constant_override(SNAME("icon_max_width"), p_max_width);
968
}
969
}
970
971
void EditorDockManager::add_vsplit(DockSplitContainer *p_split) {
972
vsplits.push_back(p_split);
973
p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
974
}
975
976
void EditorDockManager::add_hsplit(DockSplitContainer *p_split) {
977
hsplits.push_back(p_split);
978
p_split->connect("dragged", callable_mp(this, &EditorDockManager::_dock_split_dragged));
979
}
980
981
void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p_tab_container) {
982
ERR_FAIL_NULL(p_tab_container);
983
ERR_FAIL_INDEX(p_dock_slot, DOCK_SLOT_MAX);
984
985
dock_slot[p_dock_slot] = p_tab_container;
986
987
p_tab_container->set_custom_minimum_size(Size2(170, 0) * EDSCALE);
988
p_tab_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
989
p_tab_container->set_popup(dock_context_popup);
990
p_tab_container->connect("pre_popup_pressed", callable_mp(dock_context_popup, &DockContextPopup::select_current_dock_in_dock_slot).bind(p_dock_slot));
991
p_tab_container->set_drag_to_rearrange_enabled(true);
992
p_tab_container->set_tabs_rearrange_group(1);
993
p_tab_container->connect("tab_changed", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
994
p_tab_container->connect("active_tab_rearranged", callable_mp(this, &EditorDockManager::_update_layout).unbind(1));
995
p_tab_container->connect("child_order_changed", callable_mp(this, &EditorDockManager::_dock_container_update_visibility).bind(p_tab_container));
996
p_tab_container->set_use_hidden_tabs_for_min_size(true);
997
p_tab_container->get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_dock_container_gui_input).bind(p_tab_container));
998
p_tab_container->hide();
999
1000
// Create dock dragging hint.
1001
dock_drag_rects[p_dock_slot] = memnew(EditorDockDragHint);
1002
dock_drag_rects[p_dock_slot]->set_slot(p_dock_slot);
1003
dock_drag_rects[p_dock_slot]->hide();
1004
EditorNode::get_singleton()->get_gui_base()->add_child(dock_drag_rects[p_dock_slot]);
1005
}
1006
1007
int EditorDockManager::get_vsplit_count() const {
1008
return vsplits.size();
1009
}
1010
1011
PopupMenu *EditorDockManager::get_docks_menu() {
1012
return docks_menu;
1013
}
1014
1015
EditorDockManager::EditorDockManager() {
1016
singleton = this;
1017
1018
closed_dock_parent = EditorNode::get_singleton()->get_gui_base();
1019
1020
dock_context_popup = memnew(DockContextPopup);
1021
EditorNode::get_singleton()->get_gui_base()->add_child(dock_context_popup);
1022
1023
docks_menu = memnew(PopupMenu);
1024
docks_menu->set_hide_on_item_selection(false);
1025
docks_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorDockManager::_docks_menu_option));
1026
EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu));
1027
}
1028
1029
////////////////////////////////////////////////
1030
////////////////////////////////////////////////
1031
1032
void DockContextPopup::_notification(int p_what) {
1033
switch (p_what) {
1034
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
1035
case NOTIFICATION_TRANSLATION_CHANGED:
1036
case NOTIFICATION_THEME_CHANGED: {
1037
if (make_float_button) {
1038
make_float_button->set_button_icon(get_editor_theme_icon(SNAME("MakeFloating")));
1039
}
1040
if (is_layout_rtl()) {
1041
tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
1042
tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
1043
tab_move_left_button->set_tooltip_text(TTR("Move this dock right one tab."));
1044
tab_move_right_button->set_tooltip_text(TTR("Move this dock left one tab."));
1045
} else {
1046
tab_move_left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
1047
tab_move_right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
1048
tab_move_left_button->set_tooltip_text(TTR("Move this dock left one tab."));
1049
tab_move_right_button->set_tooltip_text(TTR("Move this dock right one tab."));
1050
}
1051
dock_to_bottom_button->set_button_icon(get_editor_theme_icon(SNAME("ControlAlignBottomWide")));
1052
close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
1053
} break;
1054
}
1055
}
1056
1057
void DockContextPopup::_tab_move_left() {
1058
TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);
1059
if (!tab_container) {
1060
return;
1061
}
1062
int new_index = tab_container->get_tab_idx_from_control(context_dock) - 1;
1063
dock_manager->_move_dock(context_dock, tab_container, new_index);
1064
dock_manager->_update_layout();
1065
dock_select->queue_redraw();
1066
}
1067
1068
void DockContextPopup::_tab_move_right() {
1069
TabContainer *tab_container = dock_manager->get_dock_tab_container(context_dock);
1070
if (!tab_container) {
1071
return;
1072
}
1073
int new_index = tab_container->get_tab_idx_from_control(context_dock) + 1;
1074
dock_manager->_move_dock(context_dock, tab_container, new_index);
1075
dock_manager->_update_layout();
1076
dock_select->queue_redraw();
1077
}
1078
1079
void DockContextPopup::_close_dock() {
1080
hide();
1081
dock_manager->close_dock(context_dock);
1082
}
1083
1084
void DockContextPopup::_float_dock() {
1085
hide();
1086
dock_manager->_open_dock_in_window(context_dock);
1087
}
1088
1089
void DockContextPopup::_move_dock_to_bottom() {
1090
hide();
1091
dock_manager->_dock_move_to_bottom(context_dock, true);
1092
dock_manager->_update_layout();
1093
}
1094
1095
void DockContextPopup::_dock_select_input(const Ref<InputEvent> &p_input) {
1096
Ref<InputEventMouse> me = p_input;
1097
1098
if (me.is_valid()) {
1099
Vector2 point = me->get_position();
1100
1101
int over_dock_slot = -1;
1102
for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {
1103
if (dock_select_rects[i].has_point(point)) {
1104
over_dock_slot = i;
1105
break;
1106
}
1107
}
1108
1109
if (over_dock_slot != dock_select_rect_over_idx) {
1110
dock_select->queue_redraw();
1111
dock_select_rect_over_idx = over_dock_slot;
1112
}
1113
1114
if (over_dock_slot == -1) {
1115
return;
1116
}
1117
1118
Ref<InputEventMouseButton> mb = me;
1119
TabContainer *target_tab_container = dock_manager->dock_slot[over_dock_slot];
1120
1121
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
1122
if (dock_manager->get_dock_tab_container(context_dock) != target_tab_container) {
1123
dock_manager->_move_dock(context_dock, target_tab_container, target_tab_container->get_tab_count());
1124
dock_manager->all_docks[context_dock].dock_slot_index = over_dock_slot;
1125
dock_manager->_update_layout();
1126
hide();
1127
}
1128
}
1129
}
1130
}
1131
1132
void DockContextPopup::_dock_select_mouse_exited() {
1133
dock_select_rect_over_idx = -1;
1134
dock_select->queue_redraw();
1135
}
1136
1137
void DockContextPopup::_dock_select_draw() {
1138
Color used_dock_color = Color(0.6, 0.6, 0.6, 0.8);
1139
Color hovered_dock_color = Color(0.8, 0.8, 0.8, 0.8);
1140
Color tab_selected_color = dock_select->get_theme_color(SNAME("mono_color"), EditorStringName(Editor));
1141
Color tab_unselected_color = used_dock_color;
1142
Color unused_dock_color = used_dock_color;
1143
unused_dock_color.a = 0.4;
1144
Color unusable_dock_color = unused_dock_color;
1145
unusable_dock_color.a = 0.1;
1146
1147
// Update sizes.
1148
Size2 dock_size = dock_select->get_size();
1149
dock_size.x /= 6.0;
1150
dock_size.y /= 2.0;
1151
1152
Size2 center_panel_size = dock_size * 2.0;
1153
Rect2 center_panel_rect(center_panel_size.x, 0, center_panel_size.x, center_panel_size.y);
1154
1155
if (dock_select->is_layout_rtl()) {
1156
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(), dock_size);
1157
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(0, dock_size.y), dock_size);
1158
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x, 0), dock_size);
1159
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(dock_size, dock_size);
1160
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
1161
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
1162
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
1163
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
1164
} else {
1165
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UL] = Rect2(Point2(), dock_size);
1166
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BL] = Rect2(Point2(0, dock_size.y), dock_size);
1167
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_UR] = Rect2(Point2(dock_size.x, 0), dock_size);
1168
dock_select_rects[EditorDockManager::DOCK_SLOT_LEFT_BR] = Rect2(dock_size, dock_size);
1169
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UL] = Rect2(Point2(dock_size.x * 4, 0), dock_size);
1170
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BL] = Rect2(Point2(dock_size.x * 4, dock_size.y), dock_size);
1171
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_UR] = Rect2(Point2(dock_size.x * 5, 0), dock_size);
1172
dock_select_rects[EditorDockManager::DOCK_SLOT_RIGHT_BR] = Rect2(Point2(dock_size.x * 5, dock_size.y), dock_size);
1173
}
1174
1175
int max_tabs = 3;
1176
int rtl_dir = dock_select->is_layout_rtl() ? -1 : 1;
1177
real_t tab_height = 3.0 * EDSCALE;
1178
real_t tab_spacing = 1.0 * EDSCALE;
1179
real_t dock_spacing = 2.0 * EDSCALE;
1180
real_t dock_top_spacing = tab_height + dock_spacing;
1181
1182
TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);
1183
int context_tab_index = -1;
1184
if (context_tab_container && context_tab_container->get_tab_count() > 0) {
1185
context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
1186
}
1187
1188
// Draw center panel.
1189
Rect2 center_panel_draw_rect = center_panel_rect.grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
1190
dock_select->draw_rect(center_panel_draw_rect, unusable_dock_color);
1191
1192
// Draw all dock slots.
1193
for (int i = 0; i < EditorDockManager::DOCK_SLOT_MAX; i++) {
1194
Rect2 dock_slot_draw_rect = dock_select_rects[i].grow_individual(-dock_spacing, -dock_top_spacing, -dock_spacing, -dock_spacing);
1195
real_t tab_width = Math::round(dock_slot_draw_rect.size.width / max_tabs);
1196
Rect2 tab_draw_rect = Rect2(dock_slot_draw_rect.position.x, dock_select_rects[i].position.y, tab_width - tab_spacing, tab_height);
1197
if (dock_select->is_layout_rtl()) {
1198
tab_draw_rect.position.x += dock_slot_draw_rect.size.x - tab_draw_rect.size.x;
1199
}
1200
bool is_context_dock = context_tab_container == dock_manager->dock_slot[i];
1201
int tabs_to_draw = MIN(max_tabs, dock_manager->dock_slot[i]->get_tab_count());
1202
1203
if (i == dock_select_rect_over_idx) {
1204
dock_select->draw_rect(dock_slot_draw_rect, hovered_dock_color);
1205
} else if (tabs_to_draw == 0) {
1206
dock_select->draw_rect(dock_slot_draw_rect, unused_dock_color);
1207
} else {
1208
dock_select->draw_rect(dock_slot_draw_rect, used_dock_color);
1209
}
1210
1211
// Draw tabs above each used dock slot.
1212
for (int j = 0; j < tabs_to_draw; j++) {
1213
Color tab_color = tab_unselected_color;
1214
if (is_context_dock && context_tab_index == j) {
1215
tab_color = tab_selected_color;
1216
}
1217
Rect2 tabj_draw_rect = tab_draw_rect;
1218
tabj_draw_rect.position.x += tab_width * j * rtl_dir;
1219
dock_select->draw_rect(tabj_draw_rect, tab_color);
1220
}
1221
}
1222
}
1223
1224
void DockContextPopup::_update_buttons() {
1225
TabContainer *context_tab_container = dock_manager->get_dock_tab_container(context_dock);
1226
bool dock_at_bottom = dock_manager->_is_dock_at_bottom(context_dock);
1227
1228
// Update tab move buttons.
1229
tab_move_left_button->set_disabled(true);
1230
tab_move_right_button->set_disabled(true);
1231
if (!dock_at_bottom && context_tab_container && context_tab_container->get_tab_count() > 0) {
1232
int context_tab_index = context_tab_container->get_tab_idx_from_control(context_dock);
1233
tab_move_left_button->set_disabled(context_tab_index == 0);
1234
tab_move_right_button->set_disabled(context_tab_index >= context_tab_container->get_tab_count() - 1);
1235
}
1236
1237
dock_to_bottom_button->set_visible(!dock_at_bottom && bool(context_dock->call("_can_dock_horizontal")));
1238
reset_size();
1239
}
1240
1241
void DockContextPopup::select_current_dock_in_dock_slot(int p_dock_slot) {
1242
context_dock = dock_manager->dock_slot[p_dock_slot]->get_current_tab_control();
1243
_update_buttons();
1244
}
1245
1246
void DockContextPopup::set_dock(Control *p_dock) {
1247
context_dock = p_dock;
1248
_update_buttons();
1249
}
1250
1251
Control *DockContextPopup::get_dock() const {
1252
return context_dock;
1253
}
1254
1255
void DockContextPopup::docks_updated() {
1256
if (!is_visible()) {
1257
return;
1258
}
1259
_update_buttons();
1260
}
1261
1262
DockContextPopup::DockContextPopup() {
1263
dock_manager = EditorDockManager::get_singleton();
1264
1265
dock_select_popup_vb = memnew(VBoxContainer);
1266
add_child(dock_select_popup_vb);
1267
1268
HBoxContainer *header_hb = memnew(HBoxContainer);
1269
tab_move_left_button = memnew(Button);
1270
tab_move_left_button->set_accessibility_name(TTRC("Move Tab Left"));
1271
tab_move_left_button->set_flat(true);
1272
tab_move_left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1273
tab_move_left_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_left));
1274
header_hb->add_child(tab_move_left_button);
1275
1276
Label *position_label = memnew(Label);
1277
position_label->set_text(TTR("Dock Position"));
1278
position_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1279
position_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1280
header_hb->add_child(position_label);
1281
1282
tab_move_right_button = memnew(Button);
1283
tab_move_right_button->set_accessibility_name(TTRC("Move Tab Right"));
1284
tab_move_right_button->set_flat(true);
1285
tab_move_right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1286
tab_move_right_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_tab_move_right));
1287
1288
header_hb->add_child(tab_move_right_button);
1289
dock_select_popup_vb->add_child(header_hb);
1290
1291
dock_select = memnew(Control);
1292
dock_select->set_custom_minimum_size(Size2(128, 64) * EDSCALE);
1293
dock_select->connect(SceneStringName(gui_input), callable_mp(this, &DockContextPopup::_dock_select_input));
1294
dock_select->connect(SceneStringName(draw), callable_mp(this, &DockContextPopup::_dock_select_draw));
1295
dock_select->connect(SceneStringName(mouse_exited), callable_mp(this, &DockContextPopup::_dock_select_mouse_exited));
1296
dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1297
dock_select_popup_vb->add_child(dock_select);
1298
1299
make_float_button = memnew(Button);
1300
make_float_button->set_text(TTR("Make Floating"));
1301
if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
1302
make_float_button->set_disabled(true);
1303
make_float_button->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
1304
} else {
1305
make_float_button->set_tooltip_text(TTR("Make this dock floating."));
1306
}
1307
make_float_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1308
make_float_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1309
make_float_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_float_dock));
1310
dock_select_popup_vb->add_child(make_float_button);
1311
1312
dock_to_bottom_button = memnew(Button);
1313
dock_to_bottom_button->set_text(TTR("Move to Bottom"));
1314
dock_to_bottom_button->set_tooltip_text(TTR("Move this dock to the bottom panel."));
1315
dock_to_bottom_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1316
dock_to_bottom_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1317
dock_to_bottom_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_move_dock_to_bottom));
1318
dock_to_bottom_button->hide();
1319
dock_select_popup_vb->add_child(dock_to_bottom_button);
1320
1321
close_button = memnew(Button);
1322
close_button->set_text(TTR("Close"));
1323
close_button->set_tooltip_text(TTR("Close this dock."));
1324
close_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1325
close_button->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1326
close_button->connect(SceneStringName(pressed), callable_mp(this, &DockContextPopup::_close_dock));
1327
dock_select_popup_vb->add_child(close_button);
1328
}
1329
1330