diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index 771e972410a..54b11460a10 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -43,6 +43,7 @@ #include "editor/gui/editor_bottom_panel.h" #include "editor/themes/editor_scale.h" #include "editor/window_wrapper.h" +#include "scene/resources/style_box_flat.h" enum class TabStyle { TEXT_ONLY, @@ -52,6 +53,115 @@ enum class TabStyle { EditorDockManager *EditorDockManager::singleton = nullptr; +bool EditorDockDragHint::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + return can_drop_dock; +} + +void EditorDockDragHint::drop_data(const Point2 &p_point, const Variant &p_data) { + // Drop dock into last spot if not over tabbar. + if (drop_tabbar->get_rect().has_point(p_point)) { + 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 { + dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], drop_tabbar->get_tab_count()); + } +} + +void EditorDockDragHint::_drag_move_tab(int p_from_index, int p_to_index) { + dock_manager->_move_dock_tab_index(dock_manager->_get_dock_tab_dragged(), p_to_index, true); +} + +void EditorDockDragHint::_drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) { + dock_manager->_move_dock(dock_manager->_get_dock_tab_dragged(), dock_manager->dock_slot[occupied_slot], p_to_index); +} + +void EditorDockDragHint::gui_input(const Ref &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + 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) { + queue_redraw(); + } + mouse_inside_tabbar = drop_tabbar->get_rect().has_point(pos); + } +} + +void EditorDockDragHint::set_slot(EditorDockManager::DockSlot p_slot) { + occupied_slot = p_slot; + drop_tabbar = dock_manager->dock_slot[occupied_slot]->get_tab_bar(); +} + +void EditorDockDragHint::_notification(int p_what) { + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/theme")) { + dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int()); + if (mouse_inside) { + queue_redraw(); + } + } + } break; + + case NOTIFICATION_THEME_CHANGED: { + valid_drop_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); + } break; + + case NOTIFICATION_MOUSE_ENTER: + case NOTIFICATION_MOUSE_EXIT: { + mouse_inside = p_what == NOTIFICATION_MOUSE_ENTER; + queue_redraw(); + } break; + + case NOTIFICATION_DRAG_BEGIN: { + Control *dragged_dock = dock_manager->_get_dock_tab_dragged(); + if (!dragged_dock) { + return; + } + + can_drop_dock = true; + + dock_drop_highlight->set_border_color(valid_drop_color); + dock_drop_highlight->set_bg_color(valid_drop_color * Color(1, 1, 1, 0.1)); + } break; + case NOTIFICATION_DRAG_END: { + dock_manager->_dock_drag_stopped(); + can_drop_dock = false; + mouse_inside = false; + hide(); + } break; + + case NOTIFICATION_DRAW: { + if (!mouse_inside) { + return; + } + + // Draw highlights around docks that can be dropped. + Rect2 dock_rect = Rect2(Point2(), get_size()).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->get_global_rect().has_point(get_global_mouse_position())) { + drop_tabbar->_draw_tab_drop(get_canvas_item()); + } + } break; + } +} + +EditorDockDragHint::EditorDockDragHint() { + dock_manager = EditorDockManager::get_singleton(); + + set_as_top_level(true); + dock_drop_highlight.instantiate(); + dock_drop_highlight->set_corner_radius_all(EDSCALE * EDITOR_GET("interface/theme/corner_radius").operator int()); + dock_drop_highlight->set_border_width_all(Math::round(2 * EDSCALE)); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + void DockSplitContainer::_update_visibility() { if (is_updating) { return; @@ -120,6 +230,56 @@ DockSplitContainer::DockSplitContainer() { } } +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +Control *EditorDockManager::_get_dock_tab_dragged() { + if (dock_tab_dragged) { + return dock_tab_dragged; + } + + Dictionary dock_drop_data = dock_slot[DOCK_SLOT_LEFT_BL]->get_viewport()->gui_get_drag_data(); + + // Check if we are dragging a dock. + const String type = dock_drop_data.get("type", ""); + if (type == "tab_container_tab") { + Node *from_node = dock_slot[DOCK_SLOT_LEFT_BL]->get_node(dock_drop_data["from_path"]); + if (!from_node) { + return nullptr; + } + + TabContainer *parent = Object::cast_to(from_node->get_parent()); + if (!parent) { + return nullptr; + } + + // TODO: Update logic when GH-106503 is merged to cast directly to EditorDock instead of the below check. + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + if (dock_slot[i] == parent) { + dock_tab_dragged = parent->get_tab_control(dock_drop_data["tab_index"]); + break; + } + } + if (!dock_tab_dragged) { + return nullptr; + } + + for (int i = 0; i < DOCK_SLOT_MAX; i++) { + if (dock_slot[i]->is_visible_in_tree()) { + dock_drag_rects[i]->set_rect(dock_slot[i]->get_global_rect()); + dock_drag_rects[i]->show(); + } + } + + return dock_tab_dragged; + } + return nullptr; +} + +void EditorDockManager::_dock_drag_stopped() { + dock_tab_dragged = nullptr; +} + void EditorDockManager::_dock_split_dragged(int p_offset) { EditorNode::get_singleton()->save_editor_layout_delayed(); } @@ -836,6 +996,12 @@ void EditorDockManager::register_dock_slot(DockSlot p_dock_slot, TabContainer *p p_tab_container->set_use_hidden_tabs_for_min_size(true); p_tab_container->get_tab_bar()->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_dock_container_gui_input).bind(p_tab_container)); p_tab_container->hide(); + + // Create dock dragging hint. + dock_drag_rects[p_dock_slot] = memnew(EditorDockDragHint); + dock_drag_rects[p_dock_slot]->set_slot(p_dock_slot); + dock_drag_rects[p_dock_slot]->hide(); + EditorNode::get_singleton()->get_gui_base()->add_child(dock_drag_rects[p_dock_slot]); } int EditorDockManager::get_vsplit_count() const { @@ -860,6 +1026,9 @@ EditorDockManager::EditorDockManager() { EditorNode::get_singleton()->get_gui_base()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorDockManager::update_docks_menu)); } +//////////////////////////////////////////////// +//////////////////////////////////////////////// + void DockContextPopup::_notification(int p_what) { switch (p_what) { case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: diff --git a/editor/editor_dock_manager.h b/editor/editor_dock_manager.h index 25a39c15356..585c3bc54cc 100644 --- a/editor/editor_dock_manager.h +++ b/editor/editor_dock_manager.h @@ -37,9 +37,11 @@ class Button; class ConfigFile; class Control; class PopupMenu; +class TabBar; class TabContainer; class VBoxContainer; class WindowWrapper; +class StyleBoxFlat; class DockSplitContainer : public SplitContainer { GDCLASS(DockSplitContainer, SplitContainer); @@ -58,6 +60,7 @@ public: }; class DockContextPopup; +class EditorDockDragHint; class EditorDockManager : public Object { GDCLASS(EditorDockManager, Object); @@ -78,6 +81,7 @@ public: private: friend class DockContextPopup; + friend class EditorDockDragHint; struct DockInfo { String title; @@ -101,7 +105,9 @@ private: Vector dock_windows; TabContainer *dock_slot[DOCK_SLOT_MAX]; + EditorDockDragHint *dock_drag_rects[DOCK_SLOT_MAX]; HashMap all_docks; + Control *dock_tab_dragged = nullptr; bool docks_visible = true; DockContextPopup *dock_context_popup = nullptr; @@ -109,6 +115,8 @@ private: Vector docks_menu_docks; Control *closed_dock_parent = nullptr; + Control *_get_dock_tab_dragged(); + void _dock_drag_stopped(); void _dock_split_dragged(int p_offset); void _dock_container_gui_input(const Ref &p_input, TabContainer *p_dock_container); void _bottom_dock_button_gui_input(const Ref &p_input, Control *p_dock, Button *p_bottom_button); @@ -167,9 +175,40 @@ public: EditorDockManager(); }; +class EditorDockDragHint : public Control { + GDCLASS(EditorDockDragHint, Control); + +private: + EditorDockManager *dock_manager = nullptr; + EditorDockManager::DockSlot occupied_slot = EditorDockManager::DOCK_SLOT_MAX; + TabBar *drop_tabbar = nullptr; + + Color valid_drop_color; + Ref dock_drop_highlight; + bool can_drop_dock = false; + bool mouse_inside = false; + bool mouse_inside_tabbar = false; + + void _drag_move_tab(int p_from_index, int p_to_index); + void _drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index); + +protected: + virtual void gui_input(const Ref &p_event) override; + + void _notification(int p_what); + bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; + void drop_data(const Point2 &p_point, const Variant &p_data) override; + +public: + void set_slot(EditorDockManager::DockSlot p_slot); + + EditorDockDragHint(); +}; + class DockContextPopup : public PopupPanel { GDCLASS(DockContextPopup, PopupPanel); +private: VBoxContainer *dock_select_popup_vb = nullptr; Button *make_float_button = nullptr; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index aecc32c6d8e..80cac446be6 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -572,48 +572,54 @@ void TabBar::_notification(int p_what) { } if (dragging_valid_tab) { - int x; - - int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position()); - if (closest_tab != -1) { - Rect2 tab_rect = get_tab_rect(closest_tab); - x = tab_rect.position.x; - - // Only add the tab_separation if closest tab is not on the edge. - bool not_leftmost_tab = -1 != (rtl ? get_next_available(closest_tab) : get_previous_available(closest_tab)); - bool not_rightmost_tab = -1 != (rtl ? get_previous_available(closest_tab) : get_next_available(closest_tab)); - - // Calculate midpoint between tabs. - if (get_local_mouse_position().x > tab_rect.get_center().x) { - x += tab_rect.size.x; - if (not_rightmost_tab) { - x += Math::ceil(0.5f * theme_cache.tab_separation); - } - } else if (not_leftmost_tab) { - x -= Math::floor(0.5f * theme_cache.tab_separation); - } - } else { - if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) { - x = get_tab_rect(0).position.x; - if (rtl) { - x += get_tab_rect(0).size.width; - } - } else { - Rect2 tab_rect = get_tab_rect(get_tab_count() - 1); - - x = tab_rect.position.x; - if (!rtl) { - x += tab_rect.size.width; - } - } - } - - 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); + _draw_tab_drop(get_canvas_item()); } } break; } } +void TabBar::_draw_tab_drop(RID p_canvas_item) { + Vector2 size = get_size(); + int x; + bool rtl = is_layout_rtl(); + + int closest_tab = get_closest_tab_idx_to_point(get_local_mouse_position()); + if (closest_tab != -1) { + Rect2 tab_rect = get_tab_rect(closest_tab); + x = tab_rect.position.x; + + // Only add the tab_separation if closest tab is not on the edge. + bool not_leftmost_tab = -1 != (rtl ? get_next_available(closest_tab) : get_previous_available(closest_tab)); + bool not_rightmost_tab = -1 != (rtl ? get_previous_available(closest_tab) : get_next_available(closest_tab)); + + // Calculate midpoint between tabs. + if (get_local_mouse_position().x > tab_rect.get_center().x) { + x += tab_rect.size.x; + if (not_rightmost_tab) { + x += Math::ceil(0.5f * theme_cache.tab_separation); + } + } else if (not_leftmost_tab) { + x -= Math::floor(0.5f * theme_cache.tab_separation); + } + } else { + if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) { + x = get_tab_rect(0).position.x; + if (rtl) { + x += get_tab_rect(0).size.width; + } + } else { + Rect2 tab_rect = get_tab_rect(get_tab_count() - 1); + + x = tab_rect.position.x; + if (!rtl) { + x += tab_rect.size.width; + } + } + } + + 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); +} + void TabBar::_draw_tab(Ref &p_tab_style, Color &p_font_color, int p_index, float p_x, bool p_focus) { RID ci = get_canvas_item(); bool rtl = is_layout_rtl(); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 29a51aee45e..e4a1bc0efcf 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -201,6 +201,7 @@ public: Variant _handle_get_drag_data(const String &p_type, const Point2 &p_point); bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const; 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); + void _draw_tab_drop(RID p_canvas_item); void add_tab(const String &p_str = "", const Ref &p_icon = Ref());