Skip to content

Commit 1091da8

Browse files
committed
Add dock dragging area and highlight
1 parent 6c9765d commit 1091da8

File tree

4 files changed

+257
-36
lines changed

4 files changed

+257
-36
lines changed

editor/editor_dock_manager.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "editor/gui/editor_bottom_panel.h"
4444
#include "editor/themes/editor_scale.h"
4545
#include "editor/window_wrapper.h"
46+
#include "scene/resources/style_box_flat.h"
4647

4748
enum class TabStyle {
4849
TEXT_ONLY,
@@ -52,6 +53,119 @@ enum class TabStyle {
5253

5354
EditorDockManager *EditorDockManager::singleton = nullptr;
5455

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+
if (can_drop_dock) {
62+
// Drop dock into last spot if not over tabbar.
63+
if (drop_tabbar->get_rect().has_point(p_point)) {
64+
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));
65+
} else {
66+
dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], drop_tabbar->get_tab_count());
67+
}
68+
}
69+
}
70+
71+
void EditorDockDragHint::_drag_move_tab(int p_from_index, int p_to_index) {
72+
dock_manager->_move_dock_tab_index(dock_manager->_get_dock_tab_dragged(), p_to_index, true);
73+
}
74+
75+
void EditorDockDragHint::_drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
76+
dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], p_to_index);
77+
}
78+
79+
void EditorDockDragHint::gui_input(const Ref<InputEvent> &p_event) {
80+
ERR_FAIL_COND(p_event.is_null());
81+
82+
Ref<InputEventMouseMotion> mm = p_event;
83+
if (mm.is_valid()) {
84+
Point2 pos = mm->get_position();
85+
86+
if (mouse_inside_tabbar) {
87+
queue_redraw();
88+
}
89+
mouse_inside_tabbar = drop_tabbar->get_rect().has_point(pos);
90+
}
91+
}
92+
93+
void EditorDockDragHint::set_slot(EditorDockManager::DockSlot p_slot) {
94+
occupied_slot = p_slot;
95+
drop_tabbar = dock_manager->dock_slot[occupied_slot]->get_tab_bar();
96+
}
97+
98+
void EditorDockDragHint::_notification(int p_what) {
99+
switch (p_what) {
100+
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
101+
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme")) {
102+
dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());
103+
if (is_visible_in_tree()) {
104+
queue_redraw();
105+
}
106+
}
107+
} break;
108+
109+
case NOTIFICATION_THEME_CHANGED: {
110+
valid_drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
111+
invalid_drop_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
112+
dock_drop_highlight->set_bg_color(valid_drop_color * Color(1, 1, 1, 0.1));
113+
} break;
114+
115+
case NOTIFICATION_MOUSE_ENTER:
116+
case NOTIFICATION_MOUSE_EXIT: {
117+
bool new_mouse_inside = p_what == NOTIFICATION_MOUSE_ENTER;
118+
if (new_mouse_inside != mouse_inside) {
119+
mouse_inside = new_mouse_inside;
120+
queue_redraw();
121+
}
122+
} break;
123+
124+
case NOTIFICATION_DRAG_BEGIN: {
125+
Control *dragged_dock = dock_manager->_get_dock_tab_dragged();
126+
if (!dragged_dock) {
127+
return;
128+
}
129+
130+
// TODO: Update logic when GH-106503 is merged to use flags.
131+
can_drop_dock = is_layout_horizontal ? bool(dragged_dock->call("_can_dock_horizontal")) : true;
132+
dock_drop_highlight->set_border_color(can_drop_dock ? valid_drop_color : invalid_drop_color);
133+
} break;
134+
case NOTIFICATION_DRAG_END: {
135+
dock_manager->_dock_drag_stopped();
136+
can_drop_dock = false;
137+
hide();
138+
} break;
139+
140+
case NOTIFICATION_DRAW: {
141+
// Draw highlights around docks that can be dropped.
142+
if (mouse_inside) {
143+
Rect2 dock_rect = Rect2(Point2(), get_size()).grow(2 * EDSCALE);
144+
draw_style_box(dock_drop_highlight, dock_rect);
145+
146+
// Only display hint if the mouse is over the tabbar.
147+
if (!drop_tabbar->get_global_rect().has_point(get_global_mouse_position())) {
148+
return;
149+
}
150+
151+
drop_tabbar->_draw_tab_drop(get_canvas_item());
152+
}
153+
} break;
154+
}
155+
}
156+
157+
EditorDockDragHint::EditorDockDragHint() {
158+
dock_manager = EditorDockManager::get_singleton();
159+
160+
set_as_top_level(true);
161+
dock_drop_highlight.instantiate();
162+
dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int());
163+
dock_drop_highlight->set_border_width_all(Math::round(2 * EDSCALE));
164+
}
165+
166+
////////////////////////////////////////////////
167+
////////////////////////////////////////////////
168+
55169
void DockSplitContainer::_update_visibility() {
56170
if (is_updating) {
57171
return;
@@ -114,6 +228,56 @@ void DockSplitContainer::remove_child_notify(Node *p_child) {
114228
_update_visibility();
115229
}
116230

231+
////////////////////////////////////////////////
232+
////////////////////////////////////////////////
233+
234+
Control *EditorDockManager::_get_dock_tab_dragged() {
235+
if (dock_tab_dragged) {
236+
return dock_tab_dragged;
237+
}
238+
239+
Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data();
240+
241+
// Check if we are dragging a dock.
242+
const String type = dock_drop_data.get("type", "");
243+
if (type == "tab_container_tab") {
244+
Node *from_node = dock_slot[DOCK_SLOT_LEFT_BL]->get_node(dock_drop_data["from_path"]);
245+
if (!from_node) {
246+
return nullptr;
247+
}
248+
249+
TabContainer *parent = Object::cast_to<TabContainer>(from_node->get_parent());
250+
if (!parent) {
251+
return nullptr;
252+
}
253+
254+
// TODO: Update logic when GH-106503 is merged to cast to EditorDock.
255+
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
256+
if (dock_slot[i] == parent) {
257+
dock_tab_dragged = parent->get_tab_control(dock_drop_data["tab_index"]);
258+
break;
259+
}
260+
}
261+
if (!dock_tab_dragged) {
262+
return nullptr;
263+
}
264+
265+
for (int i = 0; i < DOCK_SLOT_MAX; i++) {
266+
if (dock_slot[i]->is_visible_in_tree()) {
267+
dock_drag_rects[i]->set_rect(dock_slot[i]->get_global_rect());
268+
dock_drag_rects[i]->show();
269+
}
270+
}
271+
272+
return dock_tab_dragged;
273+
}
274+
return nullptr;
275+
}
276+
277+
void EditorDockManager::_dock_drag_stopped() {
278+
dock_tab_dragged = nullptr;
279+
}
280+
117281
void EditorDockManager::_dock_split_dragged(int p_offset) {
118282
EditorNode::get_singleton()->save_editor_layout_delayed();
119283
}
@@ -830,6 +994,12 @@ void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p
830994
p_tab_container->set_use_hidden_tabs_for_min_size(true);
831995
p_tab_container->get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_dock_container_gui_input).bind(p_tab_container));
832996
p_tab_container->hide();
997+
998+
// Create dock dragging hint.
999+
dock_drag_rects[p_dock_slot] = memnew(EditorDockDragHint);
1000+
dock_drag_rects[p_dock_slot]->set_slot(p_dock_slot);
1001+
dock_drag_rects[p_dock_slot]->hide();
1002+
EditorNode::get_singleton()->get_gui_base()->add_child(dock_drag_rects[p_dock_slot]);
8331003
}
8341004

8351005
int EditorDockManager::get_vsplit_count() const {
@@ -854,6 +1024,9 @@ EditorDockManager::EditorDockManager() {
8541024
EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu));
8551025
}
8561026

1027+
////////////////////////////////////////////////
1028+
////////////////////////////////////////////////
1029+
8571030
void DockContextPopup::_notification(int p_what) {
8581031
switch (p_what) {
8591032
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:

editor/editor_dock_manager.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ class Button;
3737
class ConfigFile;
3838
class Control;
3939
class PopupMenu;
40+
class TabBar;
4041
class TabContainer;
4142
class VBoxContainer;
4243
class WindowWrapper;
44+
class StyleBoxFlat;
4345

4446
class DockSplitContainer : public SplitContainer {
4547
GDCLASS(DockSplitContainer, SplitContainer);
@@ -55,6 +57,7 @@ class DockSplitContainer : public SplitContainer {
5557
};
5658

5759
class DockContextPopup;
60+
class EditorDockDragHint;
5861

5962
class EditorDockManager : public Object {
6063
GDCLASS(EditorDockManager, Object);
@@ -75,6 +78,7 @@ class EditorDockManager : public Object {
7578

7679
private:
7780
friend class DockContextPopup;
81+
friend class EditorDockDragHint;
7882

7983
struct DockInfo {
8084
String title;
@@ -98,14 +102,18 @@ class EditorDockManager : public Object {
98102

99103
Vector<WindowWrapper *> dock_windows;
100104
TabContainer *dock_slot[DOCK_SLOT_MAX];
105+
EditorDockDragHint *dock_drag_rects[DOCK_SLOT_MAX];
101106
HashMap<Control *, DockInfo> all_docks;
107+
Control *dock_tab_dragged = nullptr;
102108
bool docks_visible = true;
103109

104110
DockContextPopup *dock_context_popup = nullptr;
105111
PopupMenu *docks_menu = nullptr;
106112
Vector<Control *> docks_menu_docks;
107113
Control *closed_dock_parent = nullptr;
108114

115+
Control *_get_dock_tab_dragged();
116+
void _dock_drag_stopped();
109117
void _dock_split_dragged(int p_offset);
110118
void _dock_container_gui_input(const Ref<InputEvent> &p_input, TabContainer *p_dock_container);
111119
void _bottom_dock_button_gui_input(const Ref<InputEvent> &p_input, Control *p_dock, Button *p_bottom_button);
@@ -164,9 +172,42 @@ class EditorDockManager : public Object {
164172
EditorDockManager();
165173
};
166174

175+
class EditorDockDragHint : public Control {
176+
GDCLASS(EditorDockDragHint, Control);
177+
178+
private:
179+
EditorDockManager *dock_manager = nullptr;
180+
EditorDockManager::DockSlot occupied_slot = EditorDockManager::DOCK_SLOT_MAX;
181+
TabBar *drop_tabbar = nullptr;
182+
183+
Color valid_drop_color;
184+
Color invalid_drop_color;
185+
Ref<StyleBoxFlat> dock_drop_highlight;
186+
bool is_layout_horizontal = false;
187+
bool can_drop_dock = false;
188+
bool mouse_inside = false;
189+
bool mouse_inside_tabbar = false;
190+
191+
void _drag_move_tab(int p_from_index, int p_to_index);
192+
void _drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
193+
194+
protected:
195+
virtual void gui_input(const Ref<InputEvent> &p_event) override;
196+
197+
void _notification(int p_what);
198+
bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
199+
void drop_data(const Point2 &p_point, const Variant &p_data) override;
200+
201+
public:
202+
void set_slot(EditorDockManager::DockSlot p_slot);
203+
204+
EditorDockDragHint();
205+
};
206+
167207
class DockContextPopup : public PopupPanel {
168208
GDCLASS(DockContextPopup, PopupPanel);
169209

210+
private:
170211
VBoxContainer *dock_select_popup_vb = nullptr;
171212

172213
Button *make_float_button = nullptr;

scene/gui/tab_bar.cpp

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -572,46 +572,52 @@ void TabBar::_notification(int p_what) {
572572
}
573573

574574
if (dragging_valid_tab) {
575-
int x;
576-
577-
int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position());
578-
if (closest_tab != -1) {
579-
Rect2 tab_rect = get_tab_rect(closest_tab);
580-
x = tab_rect.position.x;
581-
582-
// Only add the tab_separation if closest tab is not on the edge.
583-
bool not_leftmost_tab = -1 != (rtl ? get_next_available(closest_tab) : get_previous_available(closest_tab));
584-
bool not_rightmost_tab = -1 != (rtl ? get_previous_available(closest_tab) : get_next_available(closest_tab));
585-
586-
// Calculate midpoint between tabs.
587-
if (get_local_mouse_position().x > tab_rect.get_center().x) {
588-
x += tab_rect.size.x;
589-
if (not_rightmost_tab) {
590-
x += Math::ceil(0.5f * theme_cache.tab_separation);
591-
}
592-
} else if (not_leftmost_tab) {
593-
x -= Math::floor(0.5f * theme_cache.tab_separation);
594-
}
595-
} else {
596-
if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
597-
x = get_tab_rect(0).position.x;
598-
if (rtl) {
599-
x += get_tab_rect(0).size.width;
600-
}
601-
} else {
602-
Rect2 tab_rect = get_tab_rect(get_tab_count() - 1);
575+
_draw_tab_drop(get_canvas_item());
576+
}
577+
} break;
578+
}
579+
}
603580

604-
x = tab_rect.position.x;
605-
if (!rtl) {
606-
x += tab_rect.size.width;
607-
}
608-
}
609-
}
581+
void TabBar::_draw_tab_drop(RID p_canvas_item) {
582+
Vector2 size = get_size();
583+
int x;
584+
bool rtl = is_layout_rtl();
585+
586+
int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position());
587+
if (closest_tab != -1) {
588+
Rect2 tab_rect = get_tab_rect(closest_tab);
589+
x = tab_rect.position.x;
590+
591+
// Only add the tab_separation if closest tab is not on the edge.
592+
bool not_leftmost_tab = -1 != (rtl ? get_next_available(closest_tab) : get_previous_available(closest_tab));
593+
bool not_rightmost_tab = -1 != (rtl ? get_previous_available(closest_tab) : get_next_available(closest_tab));
610594

611-
theme_cache.drop_mark_icon->draw(get_canvas_item(), Point2(x - theme_cache.drop_mark_icon->get_width() / 2, (size.height - theme_cache.drop_mark_icon->get_height()) / 2), theme_cache.drop_mark_color);
595+
// Calculate midpoint between tabs.
596+
if (get_local_mouse_position().x > tab_rect.get_center().x) {
597+
x += tab_rect.size.x;
598+
if (not_rightmost_tab) {
599+
x += Math::ceil(0.5f * theme_cache.tab_separation);
612600
}
613-
} break;
601+
} else if (not_leftmost_tab) {
602+
x -= Math::floor(0.5f * theme_cache.tab_separation);
603+
}
604+
} else {
605+
if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
606+
x = get_tab_rect(0).position.x;
607+
if (rtl) {
608+
x += get_tab_rect(0).size.width;
609+
}
610+
} else {
611+
Rect2 tab_rect = get_tab_rect(get_tab_count() - 1);
612+
613+
x = tab_rect.position.x;
614+
if (!rtl) {
615+
x += tab_rect.size.width;
616+
}
617+
}
614618
}
619+
620+
theme_cache.drop_mark_icon->draw(p_canvas_item, Point2(x - theme_cache.drop_mark_icon->get_width() / 2, (size.height - theme_cache.drop_mark_icon->get_height()) / 2), theme_cache.drop_mark_color);
615621
}
616622

617623
void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus) {

scene/gui/tab_bar.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class TabBar : public Control {
201201
Variant _handle_get_drag_data(const String &p_type, const Point2 &p_point);
202202
bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const;
203203
void _handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback);
204+
void _draw_tab_drop(RID p_canvas_item);
204205

205206
void add_tab(const String &p_str = "", const Ref<Texture2D> &p_icon = Ref<Texture2D>());
206207

0 commit comments

Comments
 (0)