Add visual marker when dragging and dropping tabs

This commit is contained in:
Michael Alexsander 2022-03-19 22:24:36 -03:00
parent 19950076b1
commit 94b8f38de2
9 changed files with 158 additions and 25 deletions

View file

@ -323,6 +323,9 @@
</constant>
</constants>
<theme_items>
<theme_item name="drop_mark_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
Modulation color for the [theme_item drop_mark] icon.
</theme_item>
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Font color of disabled tabs.
</theme_item>
@ -356,6 +359,9 @@
<theme_item name="decrement_highlight" data_type="icon" type="Texture2D">
Icon for the left arrow button that appears when there are too many tabs to fit in the container width. Used when the button is being hovered with the cursor.
</theme_item>
<theme_item name="drop_mark" data_type="icon" type="Texture2D">
Icon shown to indicate where a dragged tab is gonna be dropped (see [member drag_to_rearrange_enabled]).
</theme_item>
<theme_item name="increment" data_type="icon" type="Texture2D">
Icon for the right arrow button that appears when there are too many tabs to fit in the container width. When the button is disabled (i.e. the last tab is visible) it appears semi-transparent.
</theme_item>

View file

@ -172,6 +172,9 @@
</signal>
</signals>
<theme_items>
<theme_item name="drop_mark_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
Modulation color for the [theme_item drop_mark] icon.
</theme_item>
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Font color of disabled tabs.
</theme_item>
@ -206,6 +209,9 @@
<theme_item name="decrement_highlight" data_type="icon" type="Texture2D">
Icon for the left arrow button that appears when there are too many tabs to fit in the container width. Used when the button is being hovered with the cursor.
</theme_item>
<theme_item name="drop_mark" data_type="icon" type="Texture2D">
Icon shown to indicate where a dragged tab is gonna be dropped (see [member drag_to_rearrange_enabled]).
</theme_item>
<theme_item name="increment" data_type="icon" type="Texture2D">
Icon for the right arrow button that appears when there are too many tabs to fit in the container width. When the button is disabled (i.e. the last tab is visible) it appears semi-transparent.
</theme_item>

View file

@ -612,7 +612,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
style_tab_selected->set_border_width_all(0);
style_tab_selected->set_border_width(SIDE_TOP, Math::round(2 * EDSCALE));
// Make the highlight line prominent, but not too prominent as to not be distracting.
style_tab_selected->set_border_color(dark_color_2.lerp(accent_color, 0.75));
Color tab_highlight = dark_color_2.lerp(accent_color, 0.75);
style_tab_selected->set_border_color(tab_highlight);
// Don't round the top corners to avoid creating a small blank space between the tabs and the main panel.
// This also makes the top highlight look better.
style_tab_selected->set_corner_radius_all(0);
@ -1056,17 +1057,19 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_stylebox("tab_selected", "TabBar", style_tab_selected);
theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected);
theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled);
theme->set_stylebox("button_pressed", "TabBar", style_menu);
theme->set_stylebox("button_highlight", "TabBar", style_menu);
theme->set_stylebox("SceneTabFG", "EditorStyles", style_tab_selected);
theme->set_stylebox("SceneTabBG", "EditorStyles", style_tab_unselected);
theme->set_color("font_selected_color", "TabContainer", font_color);
theme->set_color("font_unselected_color", "TabContainer", font_disabled_color);
theme->set_color("font_selected_color", "TabBar", font_color);
theme->set_color("font_unselected_color", "TabBar", font_disabled_color);
theme->set_color("drop_mark_color", "TabContainer", tab_highlight);
theme->set_color("drop_mark_color", "TabBar", tab_highlight);
theme->set_icon("menu", "TabContainer", theme->get_icon(SNAME("GuiTabMenu"), SNAME("EditorIcons")));
theme->set_icon("menu_highlight", "TabContainer", theme->get_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
theme->set_stylebox("SceneTabFG", "EditorStyles", style_tab_selected);
theme->set_stylebox("SceneTabBG", "EditorStyles", style_tab_unselected);
theme->set_icon("close", "TabBar", theme->get_icon(SNAME("GuiClose"), SNAME("EditorIcons")));
theme->set_stylebox("button_pressed", "TabBar", style_menu);
theme->set_stylebox("button_highlight", "TabBar", style_menu);
theme->set_icon("increment", "TabContainer", theme->get_icon(SNAME("GuiScrollArrowRight"), SNAME("EditorIcons")));
theme->set_icon("decrement", "TabContainer", theme->get_icon(SNAME("GuiScrollArrowLeft"), SNAME("EditorIcons")));
theme->set_icon("increment", "TabBar", theme->get_icon(SNAME("GuiScrollArrowRight"), SNAME("EditorIcons")));
@ -1075,6 +1078,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("decrement_highlight", "TabBar", theme->get_icon(SNAME("GuiScrollArrowLeftHl"), SNAME("EditorIcons")));
theme->set_icon("increment_highlight", "TabContainer", theme->get_icon(SNAME("GuiScrollArrowRightHl"), SNAME("EditorIcons")));
theme->set_icon("decrement_highlight", "TabContainer", theme->get_icon(SNAME("GuiScrollArrowLeftHl"), SNAME("EditorIcons")));
theme->set_icon("drop_mark", "TabContainer", theme->get_icon(SNAME("GuiTabDropMark"), SNAME("EditorIcons")));
theme->set_icon("drop_mark", "TabBar", theme->get_icon(SNAME("GuiTabDropMark"), SNAME("EditorIcons")));
theme->set_constant("hseparation", "TabBar", 4 * EDSCALE);
// Content of each tab

View file

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 16 32" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5 2h6v30h-6z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 127 B

View file

@ -35,6 +35,7 @@
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/viewport.h"
Size2 TabBar::get_minimum_size() const {
Size2 ms;
@ -158,7 +159,13 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
if (get_viewport()->gui_is_dragging() && can_drop_data(pos, get_viewport()->gui_get_drag_data())) {
dragging_valid_tab = true;
update();
}
_update_hover();
return;
}
@ -333,6 +340,13 @@ void TabBar::_notification(int p_what) {
}
} break;
case NOTIFICATION_DRAG_END: {
if (dragging_valid_tab) {
dragging_valid_tab = false;
update();
}
} break;
case NOTIFICATION_DRAW: {
if (tabs.is_empty()) {
return;
@ -346,8 +360,6 @@ void TabBar::_notification(int p_what) {
Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
Ref<Texture2D> incr = get_theme_icon(SNAME("increment"));
Ref<Texture2D> decr = get_theme_icon(SNAME("decrement"));
Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight"));
Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight"));
bool rtl = is_layout_rtl();
Vector2 size = get_size();
@ -391,7 +403,10 @@ void TabBar::_notification(int p_what) {
}
if (buttons_visible) {
int vofs = (get_size().height - incr->get_size().height) / 2;
Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight"));
Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight"));
int vofs = (size.height - incr->get_size().height) / 2;
if (rtl) {
if (missing_right) {
@ -419,6 +434,39 @@ void TabBar::_notification(int p_what) {
}
}
}
if (dragging_valid_tab) {
int x;
int tab_hover = get_hovered_tab();
if (tab_hover != -1) {
Rect2 tab_rect = get_tab_rect(tab_hover);
x = tab_rect.position.x;
if (get_local_mouse_position().x > x + tab_rect.size.width / 2) {
x += tab_rect.size.width;
}
} 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;
}
}
}
Ref<Texture2D> drop_mark = get_theme_icon(SNAME("drop_mark"));
Color drop_mark_color = get_theme_color(SNAME("drop_mark_color"));
drop_mark->draw(get_canvas_item(), Point2(x - drop_mark->get_width() / 2, (size.height - drop_mark->get_height()) / 2), drop_mark_color);
}
} break;
}
}
@ -906,6 +954,8 @@ void TabBar::_on_mouse_exited() {
cb_hover = -1;
hover = -1;
highlight_arrow = -1;
dragging_valid_tab = false;
update();
}
@ -1057,13 +1107,29 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
NodePath to_path = get_path();
if (from_path == to_path) {
if (hover_now < 0) {
hover_now = get_tab_count() - 1;
if (tab_from_id == hover_now) {
return;
}
// Drop the new tab to the left or right depending on where the target tab is being hovered.
if (hover_now != -1) {
Rect2 tab_rect = get_tab_rect(hover_now);
if (is_layout_rtl() ^ (p_point.x <= tab_rect.position.x + tab_rect.size.width / 2)) {
if (hover_now > tab_from_id) {
hover_now -= 1;
}
} else if (tab_from_id > hover_now) {
hover_now += 1;
}
} else {
hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count() - 1;
}
move_tab(tab_from_id, hover_now);
emit_signal(SNAME("active_tab_rearranged"), hover_now);
set_current_tab(hover_now);
if (!is_tab_disabled(hover_now)) {
emit_signal(SNAME("active_tab_rearranged"), hover_now);
set_current_tab(hover_now);
}
} else if (get_tabs_rearrange_group() != -1) {
// Drag and drop between Tabs.
@ -1075,11 +1141,17 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
return;
}
Tab moving_tab = from_tabs->tabs[tab_from_id];
if (hover_now < 0) {
hover_now = get_tab_count();
// Drop the new tab to the left or right depending on where the target tab is being hovered.
if (hover_now != -1) {
Rect2 tab_rect = get_tab_rect(hover_now);
if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
hover_now += 1;
}
} else {
hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count();
}
Tab moving_tab = from_tabs->tabs[tab_from_id];
from_tabs->remove_tab(tab_from_id);
tabs.insert(hover_now, moving_tab);
@ -1092,7 +1164,13 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
}
}
set_current_tab(hover_now);
if (!is_tab_disabled(hover_now)) {
set_current_tab(hover_now);
} else {
_update_cache();
update();
}
update_minimum_size();
if (tabs.size() == 1) {

View file

@ -101,6 +101,7 @@ private:
int max_width = 0;
bool scrolling_enabled = true;
bool drag_to_rearrange_enabled = false;
bool dragging_valid_tab = false;
bool scroll_to_selected = true;
int tabs_rearrange_group = -1;

View file

@ -173,9 +173,9 @@ void TabContainer::_notification(int p_what) {
int x = is_layout_rtl() ? 0 : get_size().width - menu->get_width();
if (menu_hovered) {
menu_hl->draw(get_canvas_item(), Size2(x, (header_height - menu_hl->get_height()) / 2));
menu_hl->draw(get_canvas_item(), Point2(x, (header_height - menu_hl->get_height()) / 2));
} else {
menu->draw(get_canvas_item(), Size2(x, (header_height - menu->get_height()) / 2));
menu->draw(get_canvas_item(), Point2(x, (header_height - menu->get_height()) / 2));
}
}
} break;
@ -201,6 +201,8 @@ void TabContainer::_on_theme_changed() {
tab_bar->add_theme_icon_override(SNAME("increment_highlight"), get_theme_icon(SNAME("increment_highlight")));
tab_bar->add_theme_icon_override(SNAME("decrement"), get_theme_icon(SNAME("decrement")));
tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), get_theme_icon(SNAME("decrement_highlight")));
tab_bar->add_theme_icon_override(SNAME("drop_mark"), get_theme_icon(SNAME("drop_mark")));
tab_bar->add_theme_color_override(SNAME("drop_mark_color"), get_theme_color(SNAME("drop_mark_color")));
tab_bar->add_theme_color_override(SNAME("font_selected_color"), get_theme_color(SNAME("font_selected_color")));
tab_bar->add_theme_color_override(SNAME("font_unselected_color"), get_theme_color(SNAME("font_unselected_color")));
tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color")));
@ -384,8 +386,6 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
return;
}
int hover_now = get_tab_idx_at_point(p_point);
Dictionary d = p_data;
if (!d.has("type")) {
return;
@ -393,11 +393,27 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
if (String(d["type"]) == "tabc_element") {
int tab_from_id = d["tabc_element"];
int hover_now = get_tab_idx_at_point(p_point);
NodePath from_path = d["from_path"];
NodePath to_path = get_path();
if (from_path == to_path) {
if (hover_now < 0) {
hover_now = get_tab_count() - 1;
if (tab_from_id == hover_now) {
return;
}
// Drop the new tab to the left or right depending on where the target tab is being hovered.
if (hover_now != -1) {
Rect2 tab_rect = tab_bar->get_tab_rect(hover_now);
if (is_layout_rtl() ^ (p_point.x <= tab_rect.position.x + tab_rect.size.width / 2)) {
if (hover_now > tab_from_id) {
hover_now -= 1;
}
} else if (tab_from_id > hover_now) {
hover_now += 1;
}
} else {
hover_now = is_layout_rtl() ^ (p_point.x < tab_bar->get_tab_rect(0).position.x) ? 0 : get_tab_count() - 1;
}
move_child(get_tab_control(tab_from_id), get_tab_control(hover_now)->get_index(false));
@ -407,16 +423,31 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
} else if (get_tabs_rearrange_group() != -1) {
// Drag and drop between TabContainers.
Node *from_node = get_node(from_path);
TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node);
if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
// Get the tab properties before they get erased by the child removal.
String tab_title = from_tabc->get_tab_title(tab_from_id);
bool tab_disabled = from_tabc->is_tab_disabled(tab_from_id);
// Drop the new tab to the left or right depending on where the target tab is being hovered.
if (hover_now != -1) {
Rect2 tab_rect = tab_bar->get_tab_rect(hover_now);
if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
hover_now += 1;
}
} else {
hover_now = is_layout_rtl() ^ (p_point.x < tab_bar->get_tab_rect(0).position.x) ? 0 : get_tab_count();
}
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
from_tabc->remove_child(moving_tabc);
add_child(moving_tabc, true);
if (hover_now < 0) {
hover_now = get_tab_count() - 1;
}
set_tab_title(get_tab_count() - 1, tab_title);
set_tab_disabled(get_tab_count() - 1, tab_disabled);
move_child(moving_tabc, get_tab_control(hover_now)->get_index(false));
if (!is_tab_disabled(hover_now)) {

View file

@ -801,6 +801,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("increment_highlight", "TabContainer", icons["scroll_button_right_hl"]);
theme->set_icon("decrement", "TabContainer", icons["scroll_button_left"]);
theme->set_icon("decrement_highlight", "TabContainer", icons["scroll_button_left_hl"]);
theme->set_icon("drop_mark", "TabContainer", icons["tabs_drop_mark"]);
theme->set_icon("menu", "TabContainer", icons["tabs_menu"]);
theme->set_icon("menu_highlight", "TabContainer", icons["tabs_menu_hl"]);
@ -811,6 +812,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_unselected_color", "TabContainer", control_font_low_color);
theme->set_color("font_disabled_color", "TabContainer", control_font_disabled_color);
theme->set_color("font_outline_color", "TabContainer", Color(1, 1, 1));
theme->set_color("drop_mark_color", "TabContainer", Color(1, 1, 1));
theme->set_constant("side_margin", "TabContainer", 8 * scale);
theme->set_constant("icon_separation", "TabContainer", 4 * scale);
@ -828,6 +830,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("increment_highlight", "TabBar", icons["scroll_button_right_hl"]);
theme->set_icon("decrement", "TabBar", icons["scroll_button_left"]);
theme->set_icon("decrement_highlight", "TabBar", icons["scroll_button_left_hl"]);
theme->set_icon("drop_mark", "TabBar", icons["tabs_drop_mark"]);
theme->set_icon("close", "TabBar", icons["close"]);
theme->set_font("font", "TabBar", Ref<Font>());
@ -837,6 +840,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_unselected_color", "TabBar", control_font_low_color);
theme->set_color("font_disabled_color", "TabBar", control_font_disabled_color);
theme->set_color("font_outline_color", "TabBar", Color(1, 1, 1));
theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1));
theme->set_constant("hseparation", "TabBar", 4 * scale);
theme->set_constant("outline_size", "TabBar", 0);

View file

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 16 32" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5 1h6v30h-6z" fill="#d3d3d3"/></svg>

After

Width:  |  Height:  |  Size: 130 B