From e487fc56117b967eb5725c9c493ca38b81a2a482 Mon Sep 17 00:00:00 2001 From: kobewi Date: Mon, 15 Jun 2026 15:45:29 +0200 Subject: [PATCH] Add margin drop support for dock slots --- editor/docks/dock_tab_container.cpp | 82 +++++++++++++++++++++++++--- editor/docks/dock_tab_container.h | 9 +++ editor/docks/editor_dock_manager.cpp | 5 ++ editor/docks/editor_dock_manager.h | 2 + editor/editor_node.cpp | 24 ++++++++ editor/gui/editor_bottom_panel.cpp | 2 + 6 files changed, 117 insertions(+), 7 deletions(-) diff --git a/editor/docks/dock_tab_container.cpp b/editor/docks/dock_tab_container.cpp index b4df41ec4c..0160d754bc 100644 --- a/editor/docks/dock_tab_container.cpp +++ b/editor/docks/dock_tab_container.cpp @@ -45,11 +45,16 @@ bool EditorDockDragHint::can_drop_data(const Point2 &p_point, const Variant &p_d void EditorDockDragHint::drop_data(const Point2 &p_point, const Variant &p_data) { // Drop dock into last spot if not over tabbar. - if (drop_tabbar_parent->get_rect().has_point(p_point)) { + if (mouse_inside_tabbar) { 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)); } else { EditorDockManager *dock_manager = EditorDockManager::get_singleton(); - dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_container, drop_tabbar->get_tab_count()); + if (mouse_margin_index == -1) { + dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_container, drop_tabbar->get_tab_count()); + } else { + DockTabContainer *target_container = EditorDockManager::get_singleton()->get_dock_container(mouse_margin_target_slot); + dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), target_container, target_container->get_tab_count()); + } } } @@ -67,13 +72,46 @@ void EditorDockDragHint::gui_input(const Ref &p_event) { Ref mm = p_event; if (mm.is_valid()) { - Point2 pos = mm->get_position(); - - // Redraw when inside the tabbar and just exited. if (mouse_inside_tabbar) { + // Always redraw, to update tab indicators. queue_redraw(); } + const Point2 pos = mm->get_position(); mouse_inside_tabbar = drop_tabbar_parent->get_rect().has_point(pos); + + if (mouse_inside_tabbar) { + mouse_margin_index = -1; + return; + } + const Vector2 size = get_size(); + + int new_margin_index = -1; + if (pos.x < size.x * SIDE_DROP_MARGIN && dock_container) { + new_margin_index = SIDE_LEFT; + } else if (pos.x > size.x * (1.0 - SIDE_DROP_MARGIN)) { + new_margin_index = SIDE_RIGHT; + } else if (pos.y < size.y * SIDE_DROP_MARGIN) { + new_margin_index = SIDE_TOP; + } else if (pos.y > size.y * (1.0 - SIDE_DROP_MARGIN)) { + new_margin_index = SIDE_BOTTOM; + } + if (new_margin_index > -1) { + mouse_margin_target_slot = dock_container->get_margin_drop_slot(new_margin_index); + if (mouse_margin_target_slot == -1) { + new_margin_index = -1; + } else { + EditorDock *dragged_dock = EditorDockManager::get_singleton()->_get_dock_tab_dragged(); + DockTabContainer *target_container = EditorDockManager::get_singleton()->get_dock_container(mouse_margin_target_slot); + if (!(dragged_dock->get_available_layouts() & target_container->layout)) { + new_margin_index = -1; + } + } + } + + if (new_margin_index != mouse_margin_index) { + mouse_margin_index = new_margin_index; + queue_redraw(); + } } } @@ -136,12 +174,25 @@ void EditorDockDragHint::_notification(int p_what) { return; } + const Vector2 size = get_size(); + Rect2 dock_rect = Rect2(Point2(), size); + if (mouse_margin_index == SIDE_LEFT) { + dock_rect.size.x = size.x * SIDE_DROP_MARGIN; + } else if (mouse_margin_index == SIDE_RIGHT) { + dock_rect.position.x = size.x * (1.0 - SIDE_DROP_MARGIN); + dock_rect.size.x = size.x * 0.2; + } else if (mouse_margin_index == SIDE_TOP) { + dock_rect.size.y = size.y * SIDE_DROP_MARGIN; + } else if (mouse_margin_index == SIDE_BOTTOM) { + dock_rect.position.y = size.y * (1.0 - SIDE_DROP_MARGIN); + dock_rect.size.y = size.y * 0.2; + } // Draw highlights around docks that can be dropped. - Rect2 dock_rect = Rect2(Point2(), get_size()).grow(2 * EDSCALE); + dock_rect = dock_rect.grow(2 * EDSCALE); draw_style_box(dock_drop_highlight, dock_rect); // Only display tabbar hint if the mouse is over the tabbar. - if (drop_tabbar_parent->get_global_rect().has_point(get_global_mouse_position())) { + if (mouse_inside_tabbar) { draw_set_transform(drop_tabbar_parent->get_position()); // The TabBar isn't always on top. drop_tabbar->_draw_tab_drop(get_canvas_item()); } @@ -194,6 +245,23 @@ bool DockTabContainer::can_switch_dock() const { return EditorDockManager::get_singleton()->are_docks_visible(); } +void DockTabContainer::add_margin_valid_drop(int p_margin, int p_target_dock_slot) { + DEV_ASSERT(!valid_drop_margins.has(p_margin)); + valid_drop_margins[p_margin] = p_target_dock_slot; +} + +int DockTabContainer::get_margin_drop_slot(int p_margin) const { + const int *target_slot = valid_drop_margins.getptr(p_margin); + if (!target_slot) { + return -1; + } + DockTabContainer *tab_container = EditorDockManager::get_singleton()->get_dock_container(*target_slot); + if (!tab_container->is_visible()) { + return *target_slot; + } + return -1; +} + void DockTabContainer::save_docks_to_config(Ref p_layout, const String &p_section) { PackedStringArray names; names.reserve_exact(get_tab_count()); diff --git a/editor/docks/dock_tab_container.h b/editor/docks/dock_tab_container.h index 8f384e86f5..92a9077220 100644 --- a/editor/docks/dock_tab_container.h +++ b/editor/docks/dock_tab_container.h @@ -42,6 +42,8 @@ class StyleBoxFlat; class EditorDockDragHint : public Control { GDCLASS(EditorDockDragHint, Control); + static constexpr float SIDE_DROP_MARGIN = 0.2; + DockTabContainer *dock_container = nullptr; Control *drop_tabbar_parent = nullptr; TabBar *drop_tabbar = nullptr; @@ -50,6 +52,8 @@ class EditorDockDragHint : public Control { bool can_drop_dock = false; bool mouse_inside = false; bool mouse_inside_tabbar = false; + int mouse_margin_index = -1; + int mouse_margin_target_slot = -1; bool highlighted = false; void _drag_move_tab(int p_from_index, int p_to_index); @@ -74,6 +78,8 @@ class DockTabContainer : public TabContainer { EditorDockDragHint *drag_hint = nullptr; + HashMap valid_drop_margins; + void _pre_popup(const Size2i &p_size); void _tab_rmb_clicked(int p_tab_idx); @@ -102,6 +108,9 @@ public: virtual bool can_switch_dock() const; virtual Rect2 get_floating_dock_rect(EditorDock *p_dock) { return DockTabContainer::get_default_floating_dock_rect(p_dock); } + void add_margin_valid_drop(int p_margin, int p_target_dock_slot); + int get_margin_drop_slot(int p_margin) const; + // There is no equivalent load method, because loading needs to handle floating and closing. void save_docks_to_config(Ref p_layout, const String &p_section); virtual void load_selected_tab(int p_idx); diff --git a/editor/docks/editor_dock_manager.cpp b/editor/docks/editor_dock_manager.cpp index 5d3e4af0b8..219406bc87 100644 --- a/editor/docks/editor_dock_manager.cpp +++ b/editor/docks/editor_dock_manager.cpp @@ -195,6 +195,11 @@ void EditorDockManager::_update_layout() { EditorNode::get_singleton()->save_editor_layout_delayed(); } +DockTabContainer *EditorDockManager::get_dock_container(int p_slot) const { + ERR_FAIL_INDEX_V(p_slot, EditorDock::DOCK_SLOT_MAX, nullptr); + return dock_slots[p_slot]; +} + void EditorDockManager::update_docks_menu() { docks_menu->clear(); docks_menu->reset_size(); diff --git a/editor/docks/editor_dock_manager.h b/editor/docks/editor_dock_manager.h index 62ffb9db0a..ecb8eca0ae 100644 --- a/editor/docks/editor_dock_manager.h +++ b/editor/docks/editor_dock_manager.h @@ -129,6 +129,8 @@ private: public: static EditorDockManager *get_singleton() { return singleton; } + DockTabContainer *get_dock_container(int p_slot) const; + void update_docks_menu(); void update_tab_styles(); void set_tab_icon_max_width(int p_max_width); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2fa0303d63..6a86a87f76 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -8725,12 +8725,17 @@ EditorNode::EditorNode() { { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_LEFT_UL, Rect2i(0, 0, 1, 3))); dock_container->set_name("DockSlotLeftUL"); + dock_container->add_margin_valid_drop(SIDE_RIGHT, EditorDock::DOCK_SLOT_LEFT_UR); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_LEFT_BL); left_l_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_LEFT_BL, Rect2i(0, 3, 1, 3))); dock_container->set_name("DockSlotLeftBL"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_LEFT_UL); + dock_container->add_margin_valid_drop(SIDE_RIGHT, EditorDock::DOCK_SLOT_LEFT_BR); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_BOTTOM_L); left_l_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } @@ -8742,12 +8747,17 @@ EditorNode::EditorNode() { { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_LEFT_UR, Rect2i(1, 0, 1, 3))); dock_container->set_name("DockSlotLeftUR"); + dock_container->add_margin_valid_drop(SIDE_LEFT, EditorDock::DOCK_SLOT_LEFT_UL); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_LEFT_BR); left_r_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_LEFT_BR, Rect2i(1, 3, 1, 3))); dock_container->set_name("DockSlotLeftBR"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_LEFT_UR); + dock_container->add_margin_valid_drop(SIDE_LEFT, EditorDock::DOCK_SLOT_LEFT_BL); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_BOTTOM_L); left_r_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } @@ -8771,12 +8781,17 @@ EditorNode::EditorNode() { { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_RIGHT_UL, Rect2i(6, 0, 1, 3))); dock_container->set_name("DockSlotRightUL"); + dock_container->add_margin_valid_drop(SIDE_RIGHT, EditorDock::DOCK_SLOT_RIGHT_UR); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_RIGHT_BL); right_l_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_RIGHT_BL, Rect2i(6, 3, 1, 3))); dock_container->set_name("DockSlotRightBL"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_RIGHT_UL); + dock_container->add_margin_valid_drop(SIDE_RIGHT, EditorDock::DOCK_SLOT_RIGHT_BR); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_BOTTOM_R); right_l_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } @@ -8788,12 +8803,17 @@ EditorNode::EditorNode() { { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_RIGHT_UR, Rect2i(7, 0, 1, 3))); dock_container->set_name("DockSlotRightUR"); + dock_container->add_margin_valid_drop(SIDE_LEFT, EditorDock::DOCK_SLOT_RIGHT_UL); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_RIGHT_BR); right_r_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } { DockTabContainer *dock_container = memnew(SideDockTabContainer(EditorDock::DOCK_SLOT_RIGHT_BR, Rect2i(7, 3, 1, 3))); dock_container->set_name("DockSlotRightBR"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_RIGHT_UR); + dock_container->add_margin_valid_drop(SIDE_LEFT, EditorDock::DOCK_SLOT_RIGHT_BL); + dock_container->add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_BOTTOM_R); right_r_vsplit->add_child(dock_container); dock_slots.push_back(dock_container); } @@ -8804,12 +8824,16 @@ EditorNode::EditorNode() { { DockTabContainer *dock_container = memnew(BottomSideDockTabContainer(EditorDock::DOCK_SLOT_BOTTOM_L, Rect2i(0, 6, 4, 2))); dock_container->set_name("DockSlotBottomL"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_LEFT_BL); + dock_container->add_margin_valid_drop(SIDE_RIGHT, EditorDock::DOCK_SLOT_BOTTOM_R); bottom_hsplit->add_child(dock_container); dock_slots.push_back(dock_container); } { DockTabContainer *dock_container = memnew(BottomSideDockTabContainer(EditorDock::DOCK_SLOT_BOTTOM_R, Rect2i(4, 6, 4, 2))); dock_container->set_name("DockSlotBottomR"); + dock_container->add_margin_valid_drop(SIDE_TOP, EditorDock::DOCK_SLOT_RIGHT_BR); + dock_container->add_margin_valid_drop(SIDE_LEFT, EditorDock::DOCK_SLOT_BOTTOM_L); bottom_hsplit->add_child(dock_container); dock_slots.push_back(dock_container); } diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index 8569fcf0c6..7d7c5b6178 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -295,6 +295,8 @@ EditorBottomPanel::EditorBottomPanel() : set_deselect_enabled(true); set_theme_type_variation("BottomPanel"); + add_margin_valid_drop(SIDE_BOTTOM, EditorDock::DOCK_SLOT_BOTTOM_L); + bottom_hbox = memnew(HBoxContainer); bottom_hbox->set_mouse_filter(MOUSE_FILTER_IGNORE); get_internal_container()->add_child(bottom_hbox);