Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/classes/SplitContainer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
</method>
</methods>
<members>
<member name="auto_snap" type="int" setter="set_auto_snap" getter="get_auto_snap" enum="SplitContainer.Snap" default="3">
Determines the edges on which the dragger will auto-snap. See [enum Snap] for details.
</member>
<member name="collapsed" type="bool" setter="set_collapsed" getter="is_collapsed" default="false">
If [code]true[/code], the dragger will be disabled and the children will be sized as if the [member split_offset] was [code]0[/code].
</member>
Expand All @@ -50,6 +53,9 @@
<member name="dragging_enabled" type="bool" setter="set_dragging_enabled" getter="is_dragging_enabled" default="true">
Enables or disables split dragging.
</member>
<member name="snap_state" type="int" setter="set_snap_state" getter="get_snap_state" enum="SplitContainer.Snap" default="0">
The current snap state. If [constant SNAP_NONE] the dragger is not snapped. If [constant SNAP_FIRST] or [constant SNAP_SECOND] the dragger is snapped to the edge where the first or second child lies on.
</member>
<member name="split_offset" type="int" setter="set_split_offset" getter="get_split_offset" default="0">
The initial offset of the splitting between the two [Control]s, with [code]0[/code] being at the end of the first [Control].
</member>
Expand All @@ -75,6 +81,11 @@
Emitted when the dragger is dragged by user.
</description>
</signal>
<signal name="snap_state_changed">
<description>
Wmitted when the snap state changes.
</description>
</signal>
</signals>
<constants>
<constant name="DRAGGER_VISIBLE" value="0" enum="DraggerVisibility">
Expand All @@ -89,6 +100,18 @@
<constant name="DRAGGER_HIDDEN_COLLAPSED" value="2" enum="DraggerVisibility">
The split dragger icon is not visible, and the split bar is collapsed to zero thickness.
</constant>
<constant name="SNAP_NONE" value="0" enum="Snap">
The dragger will never snap automatically to any of the container's edges.
</constant>
<constant name="SNAP_FIRST" value="1" enum="Snap">
The dragger will snap automatically to the edge on which the first child lies when the user attempts to resize the child less than a third of its minimum size. If [member vertical] is [code]true[/code], this is the top edge. Otherwise it is the left edge.
</constant>
<constant name="SNAP_SECOND" value="2" enum="Snap">
The dragger will snap automatically to the edge on which the second child lies when the user attempts to resize the child less than a third of the its minimum size. If [member vertical] is [code]true[/code], this is the bottom edge. Otherwise it is the right edge.
</constant>
<constant name="SNAP_BOTH" value="3" enum="Snap">
Equivalent to setting both [constant SNAP_FIRST] and [constant SNAP_SECOND]. This is the default value for [member auto_snap].
</constant>
</constants>
<theme_items>
<theme_item name="autohide" data_type="constant" type="int" default="1">
Expand Down
12 changes: 12 additions & 0 deletions editor/editor_dock_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,11 +514,13 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str
for (int i = 0; i < vsplits.size(); i++) {
if (vsplits[i]->is_visible_in_tree()) {
p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset());
p_layout->set_value(p_section, "dock_split_snap_" + itos(i + 1), (int)vsplits[i]->get_snap_state());
}
}

for (int i = 0; i < hsplits.size(); i++) {
p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE));
p_layout->set_value(p_section, "dock_hsplit_snap_" + itos(i + 1), (int)hsplits[i]->get_snap_state());
}
}

Expand Down Expand Up @@ -607,6 +609,11 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
}
int ofs = p_layout->get_value(p_section, "dock_split_" + itos(i + 1));
vsplits[i]->set_split_offset(ofs);
if (!p_layout->has_section_key(p_section, "dock_split_snap_" + itos(i + 1))) {
continue;
}
SplitContainer::Snap snap = (SplitContainer::Snap)p_layout->get_value(p_section, "dock_split_snap_" + itos(i + 1));
vsplits[i]->set_snap_state(snap);
}

for (int i = 0; i < hsplits.size(); i++) {
Expand All @@ -615,6 +622,11 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
}
int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1));
hsplits[i]->set_split_offset(ofs * EDSCALE);
if (!p_layout->has_section_key(p_section, "dock_hsplit_snap_" + itos(i + 1))) {
continue;
}
SplitContainer::Snap snap = (SplitContainer::Snap)p_layout->get_value(p_section, "dock_hsplit_snap_" + itos(i + 1));
hsplits[i]->set_snap_state(snap);
}
update_docks_menu();
}
Expand Down
5 changes: 5 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8131,11 +8131,16 @@ EditorNode::EditorNode() {
// There are 4 vsplits and 4 hsplits.
for (int i = 0; i < editor_dock_manager->get_vsplit_count(); i++) {
default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0);
default_layout->set_value(docks_section, "dock_split_snap_" + itos(i + 1), 0);
}
default_layout->set_value(docks_section, "dock_hsplit_1", 0);
default_layout->set_value(docks_section, "dock_hsplit_2", 270);
default_layout->set_value(docks_section, "dock_hsplit_3", -270);
default_layout->set_value(docks_section, "dock_hsplit_4", 0);
default_layout->set_value(docks_section, "dock_hsplit_snap_1", 0);
default_layout->set_value(docks_section, "dock_hsplit_snap_2", 0);
default_layout->set_value(docks_section, "dock_hsplit_snap_3", 0);
default_layout->set_value(docks_section, "dock_hsplit_snap_4", 0);

_update_layouts_menu();

Expand Down
37 changes: 37 additions & 0 deletions editor/gui/editor_bottom_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@

void EditorBottomPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);
center_split->connect(SNAME("drag_ended"), callable_mp(this, &EditorBottomPanel::_center_split_drag_ended));
} break;

case NOTIFICATION_EXIT_TREE: {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);
center_split->disconnect(SNAME("drag_ended"), callable_mp(this, &EditorBottomPanel::_center_split_drag_ended));
} break;

case NOTIFICATION_THEME_CHANGED: {
pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
Expand Down Expand Up @@ -100,6 +112,31 @@ void EditorBottomPanel::_update_disabled_buttons() {
right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
}

void EditorBottomPanel::_center_split_drag_started() {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);

// Save the split offset so that it can be restored when snapping to top edge
center_split_start_split_offset = center_split->get_split_offset();
}

void EditorBottomPanel::_center_split_drag_ended() {
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
ERR_FAIL_NULL(center_split);

// Reset the snap state after the drag ends to either expanding the bottom panel (when the splitter is snapped
// at the top edge) or to collapsing the bottom panel (when the splitter is snapped at the bottom edge)
if (center_split->get_snap_state() == SplitContainer::SNAP_FIRST) {
center_split->set_snap_state(SplitContainer::SNAP_NONE);
center_split->set_split_offset(center_split_start_split_offset);
expand_button->set_pressed_no_signal(true);
EditorNode::get_top_split()->set_visible(false);
} else if (center_split->get_snap_state() == SplitContainer::SNAP_SECOND) {
center_split->set_snap_state(SplitContainer::SNAP_NONE);
_switch_by_control(false, last_opened_control);
}
}

void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
ERR_FAIL_INDEX(p_idx, items.size());

Expand Down
3 changes: 3 additions & 0 deletions editor/gui/editor_bottom_panel.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class EditorBottomPanel : public PanelContainer {

Vector<BottomPanelItem> items;
bool lock_panel_switching = false;
int center_split_start_split_offset = 0;

VBoxContainer *item_vbox = nullptr;
HBoxContainer *bottom_hbox = nullptr;
Expand All @@ -69,6 +70,8 @@ class EditorBottomPanel : public PanelContainer {
void _scroll(bool p_right);
void _update_scroll_buttons();
void _update_disabled_buttons();
void _center_split_drag_started();
void _center_split_drag_ended();

bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);

Expand Down
111 changes: 96 additions & 15 deletions scene/gui/split_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
drag_from = get_transform().xform(mb->get_position()).x;
}
} else {
dragging = false;
queue_redraw();
sc->emit_signal(SNAME("drag_ended"));
_end_dragging();
}
}
}
Expand All @@ -83,6 +81,13 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
}
}

void SplitContainerDragger::_end_dragging() {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
dragging = false;
queue_redraw();
sc->emit_signal(SNAME("drag_ended"));
}

Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (!sc->collapsed && sc->dragging_enabled) {
Expand Down Expand Up @@ -196,6 +201,19 @@ Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisibilityMode p
return nullptr;
}

void SplitContainer::_fit_child_in_rect_with_visibility_update(Control *p_child, const Rect2 &p_rect) {
// For very small rects (like when a size is set to 0 via autosnapping) hide the child instead
// of changing its rectangle. The child is scaled to 0 instead of changing visibility to avoid
// affecting any existing visibility state for both the SplitContainer users and for the size
// calculations SplitContainer itself doe
if (snap_state != Snap::SNAP_NONE && (p_rect.size.y < 1.0 || p_rect.size.x < 1.0)) {
p_child->set_scale(Vector2(0, 0));
} else {
// fit_child_in_rect resets scaling to 1
fit_child_in_rect(p_child, p_rect);
}
}

Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
if (is_fixed) {
return theme_cache.grabber_icon;
Expand Down Expand Up @@ -245,7 +263,26 @@ void SplitContainer::_compute_split_offset(bool p_clamp) {
// Clamp the split offset to acceptable values.
int first_min_size = first->get_combined_minimum_size()[axis_index];
int second_min_size = second->get_combined_minimum_size()[axis_index];
computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size);

// Check autosnapping
if (dragging_area_control->dragging) {
if ((auto_snap & SNAP_FIRST) != 0 && wished_size < first_min_size / 3) {
set_snap_state(Snap::SNAP_FIRST);
} else if ((auto_snap & SNAP_SECOND) != 0 && wished_size > size - sep - second_min_size / 3) {
set_snap_state(Snap::SNAP_SECOND);
} else {
set_snap_state(Snap::SNAP_NONE);
}
}

// Apply snapping
if (!collapsed && (snap_state & SNAP_FIRST) != 0) {
computed_split_offset = 0;
} else if (!collapsed && (snap_state & SNAP_SECOND) != 0) {
computed_split_offset = size;
} else {
computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size);
}

// Clamp the split_offset if requested.
if (p_clamp) {
Expand Down Expand Up @@ -275,19 +312,19 @@ void SplitContainer::_resort() {

// Move the children.
if (vertical) {
fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset)));
_fit_child_in_rect_with_visibility_update(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset)));
int sofs = computed_split_offset + sep;
fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
_fit_child_in_rect_with_visibility_update(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
} else {
if (is_rtl) {
computed_split_offset = get_size().width - computed_split_offset - sep;
fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
_fit_child_in_rect_with_visibility_update(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
int sofs = computed_split_offset + sep;
fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
_fit_child_in_rect_with_visibility_update(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
} else {
fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
_fit_child_in_rect_with_visibility_update(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
int sofs = computed_split_offset + sep;
fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
_fit_child_in_rect_with_visibility_update(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
}
}

Expand Down Expand Up @@ -385,23 +422,55 @@ void SplitContainer::set_collapsed(bool p_collapsed) {
return;
}
collapsed = p_collapsed;
if (collapsed && dragging_area_control->dragging) {
dragging_area_control->_end_dragging();
}
queue_sort();
}

bool SplitContainer::is_collapsed() const {
return collapsed;
}

void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
if (dragger_visibility == p_visibility) {
return;
}
dragger_visibility = p_visibility;
if (dragger_visibility != DraggerVisibility::DRAGGER_VISIBLE && dragging_area_control->dragging) {
dragging_area_control->_end_dragging();
}
queue_sort();
}

SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const {
return dragger_visibility;
}

bool SplitContainer::is_collapsed() const {
return collapsed;
void SplitContainer::set_auto_snap(Snap p_auto_snap) {
if (auto_snap == p_auto_snap) {
return;
}
auto_snap = p_auto_snap;
}

SplitContainer::Snap SplitContainer::get_auto_snap() const {
return auto_snap;
}

void SplitContainer::set_snap_state(Snap p_snap_state) {
if (snap_state == p_snap_state) {
return;
}
snap_state = p_snap_state;
if (!collapsed) {
queue_sort();
}
emit_signal(SNAME("snap_state_changed"));
}

SplitContainer::Snap SplitContainer::get_snap_state() const {
return snap_state;
}

void SplitContainer::set_vertical(bool p_vertical) {
Expand All @@ -421,9 +490,7 @@ void SplitContainer::set_dragging_enabled(bool p_enabled) {
}
dragging_enabled = p_enabled;
if (!dragging_enabled && dragging_area_control->dragging) {
dragging_area_control->dragging = false;
// queue_redraw() is called by _resort().
emit_signal(SNAME("drag_ended"));
dragging_area_control->_end_dragging();
}
if (get_viewport()) {
get_viewport()->update_mouse_cursor_state();
Expand Down Expand Up @@ -515,6 +582,12 @@ void SplitContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_dragger_visibility", "mode"), &SplitContainer::set_dragger_visibility);
ClassDB::bind_method(D_METHOD("get_dragger_visibility"), &SplitContainer::get_dragger_visibility);

ClassDB::bind_method(D_METHOD("set_auto_snap", "auto_snap"), &SplitContainer::set_auto_snap);
ClassDB::bind_method(D_METHOD("get_auto_snap"), &SplitContainer::get_auto_snap);

ClassDB::bind_method(D_METHOD("set_snap_state", "snap_state"), &SplitContainer::set_snap_state);
ClassDB::bind_method(D_METHOD("get_snap_state"), &SplitContainer::get_snap_state);

ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical);

Expand All @@ -538,11 +611,14 @@ void SplitContainer::_bind_methods() {
ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
ADD_SIGNAL(MethodInfo("drag_started"));
ADD_SIGNAL(MethodInfo("drag_ended"));
ADD_SIGNAL(MethodInfo("snap_state_changed"));

ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
ADD_PROPERTY(PropertyInfo(Variant::INT, "auto_snap", PROPERTY_HINT_ENUM, "None,First,Second,Both"), "set_auto_snap", "get_auto_snap");
ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_state", PROPERTY_HINT_ENUM, "None,First,Second"), "set_snap_state", "get_snap_state");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");

ADD_GROUP("Drag Area", "drag_area_");
Expand All @@ -555,6 +631,11 @@ void SplitContainer::_bind_methods() {
BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);

BIND_ENUM_CONSTANT(SNAP_NONE);
BIND_ENUM_CONSTANT(SNAP_FIRST);
BIND_ENUM_CONSTANT(SNAP_SECOND);
BIND_ENUM_CONSTANT(SNAP_BOTH);

BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, minimum_grab_thickness);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, autohide);
Expand Down
Loading