Browse Source

Improvements to SplitContainer including a drag bar background StyleBox.

Koyper 2 years ago
parent
commit
f7005deb1e

+ 50 - 7
doc/classes/SplitContainer.xml

@@ -16,13 +16,39 @@
 				Clamps the [member split_offset] value to not go outside the currently possible minimal and maximum values.
 				Clamps the [member split_offset] value to not go outside the currently possible minimal and maximum values.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_drag_area_control">
+			<return type="Control" />
+			<description>
+				Returns the drag area [Control]. For example, you can move a pre-configured button into the drag area [Control] so that it rides along with the split bar. Try setting the [Button] anchors to [code]center[/code] prior to the [code]reparent()[/code] call.
+				[codeblock]
+				$BarnacleButton.reparent($SplitContainer.get_drag_area_control())
+				[/codeblock]
+				[b]Note:[/b] The drag area [Control] is drawn over the [SplitContainer]'s children, so [CanvasItem] draw objects called from the [Control] and children added to the [Control] will also appear over the [SplitContainer]'s children. Try setting [member Control.mouse_filter] of custom children to [constant Control.MOUSE_FILTER_IGNORE] to prevent blocking the mouse from dragging if desired.
+				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash.
+			</description>
+		</method>
 	</methods>
 	</methods>
 	<members>
 	<members>
 		<member name="collapsed" type="bool" setter="set_collapsed" getter="is_collapsed" default="false">
 		<member name="collapsed" type="bool" setter="set_collapsed" getter="is_collapsed" default="false">
 			If [code]true[/code], the area of the first [Control] will be collapsed and the dragger will be disabled.
 			If [code]true[/code], the area of the first [Control] will be collapsed and the dragger will be disabled.
 		</member>
 		</member>
+		<member name="drag_area_highlight_in_editor" type="bool" setter="set_drag_area_highlight_in_editor" getter="is_drag_area_highlight_in_editor_enabled" default="false">
+			Highlights the drag area [Rect2] so you can see where it is during development. The drag area is gold if [member dragging_enabled] is [code]true[/code], and red if [code]false[/code].
+		</member>
+		<member name="drag_area_margin_begin" type="int" setter="set_drag_area_margin_begin" getter="get_drag_area_margin_begin" default="0">
+			Reduces the size of the drag area and split bar [theme_item split_bar_background] at the beginning of the container.
+		</member>
+		<member name="drag_area_margin_end" type="int" setter="set_drag_area_margin_end" getter="get_drag_area_margin_end" default="0">
+			Reduces the size of the drag area and split bar [theme_item split_bar_background] at the end of the container.
+		</member>
+		<member name="drag_area_offset" type="int" setter="set_drag_area_offset" getter="get_drag_area_offset" default="0">
+			Shifts the drag area in the axis of the container to prevent the drag area from overlapping the [ScrollBar] or other selectable [Control] of a child node.
+		</member>
 		<member name="dragger_visibility" type="int" setter="set_dragger_visibility" getter="get_dragger_visibility" enum="SplitContainer.DraggerVisibility" default="0">
 		<member name="dragger_visibility" type="int" setter="set_dragger_visibility" getter="get_dragger_visibility" enum="SplitContainer.DraggerVisibility" default="0">
-			Determines the dragger's visibility. See [enum DraggerVisibility] for details.
+			Determines the dragger's visibility. See [enum DraggerVisibility] for details. This property does not determine whether dragging is enabled or not. Use [member dragging_enabled] for that.
+		</member>
+		<member name="dragging_enabled" type="bool" setter="set_dragging_enabled" getter="is_dragging_enabled" default="true">
+			Enables or disables split dragging.
 		</member>
 		</member>
 		<member name="split_offset" type="int" setter="set_split_offset" getter="get_split_offset" default="0">
 		<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].
 			The initial offset of the splitting between the two [Control]s, with [code]0[/code] being at the end of the first [Control].
@@ -33,6 +59,16 @@
 		</member>
 		</member>
 	</members>
 	</members>
 	<signals>
 	<signals>
+		<signal name="drag_ended">
+			<description>
+				Emitted when the user ends dragging.
+			</description>
+		</signal>
+		<signal name="drag_started">
+			<description>
+				Emitted when the user starts dragging.
+			</description>
+		</signal>
 		<signal name="dragged">
 		<signal name="dragged">
 			<param index="0" name="offset" type="int" />
 			<param index="0" name="offset" type="int" />
 			<description>
 			<description>
@@ -42,24 +78,28 @@
 	</signals>
 	</signals>
 	<constants>
 	<constants>
 		<constant name="DRAGGER_VISIBLE" value="0" enum="DraggerVisibility">
 		<constant name="DRAGGER_VISIBLE" value="0" enum="DraggerVisibility">
-			The split dragger is visible when the cursor hovers it.
+			The split dragger icon is always visible when [theme_item autohide] is [code]false[/code], otherwise visible only when the cursor hovers it.
+			The size of the grabber icon determines the minimum [theme_item separation].
+			The dragger icon is automatically hidden if the length of the grabber icon is longer than the split bar.
 		</constant>
 		</constant>
 		<constant name="DRAGGER_HIDDEN" value="1" enum="DraggerVisibility">
 		<constant name="DRAGGER_HIDDEN" value="1" enum="DraggerVisibility">
-			The split dragger is never visible.
+			The split dragger icon is never visible regardless of the value of [theme_item autohide].
+			The size of the grabber icon determines the minimum [theme_item separation].
 		</constant>
 		</constant>
 		<constant name="DRAGGER_HIDDEN_COLLAPSED" value="2" enum="DraggerVisibility">
 		<constant name="DRAGGER_HIDDEN_COLLAPSED" value="2" enum="DraggerVisibility">
-			The split dragger is never visible and its space collapsed.
+			The split dragger icon is not visible, and the split bar is collapsed to zero thickness.
 		</constant>
 		</constant>
 	</constants>
 	</constants>
 	<theme_items>
 	<theme_items>
 		<theme_item name="autohide" data_type="constant" type="int" default="1">
 		<theme_item name="autohide" data_type="constant" type="int" default="1">
-			Boolean value. If 1 ([code]true[/code]), the grabber will hide automatically when it isn't under the cursor. If 0 ([code]false[/code]), it's always visible.
+			Boolean value. If [code]1[/code] ([code]true[/code]), the grabber will hide automatically when it isn't under the cursor. If [code]0[/code] ([code]false[/code]), it's always visible. The [member dragger_visibility] must be [constant DRAGGER_VISIBLE].
 		</theme_item>
 		</theme_item>
 		<theme_item name="minimum_grab_thickness" data_type="constant" type="int" default="6">
 		<theme_item name="minimum_grab_thickness" data_type="constant" type="int" default="6">
-			The minimum thickness of the area users can click on to grab the splitting line. If [theme_item separation] or [theme_item h_grabber] / [theme_item v_grabber]'s thickness are too small, this ensure that the splitting line can still be dragged.
+			The minimum thickness of the area users can click on to grab the split bar. This ensures that the split bar can still be dragged if [theme_item separation] or [theme_item h_grabber] / [theme_item v_grabber]'s size is too narrow to easily select.
 		</theme_item>
 		</theme_item>
 		<theme_item name="separation" data_type="constant" type="int" default="12">
 		<theme_item name="separation" data_type="constant" type="int" default="12">
-			The space between sides of the container.
+			The split bar thickness, i.e., the gap between the two children of the container. This is overridden by the size of the grabber icon if [member dragger_visibility] is set to [constant DRAGGER_VISIBLE], or [constant DRAGGER_HIDDEN], and [theme_item separation] is smaller than the size of the grabber icon in the same axis.
+			[b]Note:[/b] To obtain [theme_item separation] values less than the size of the grabber icon, for example a [code]1 px[/code] hairline, set [theme_item h_grabber] or [theme_item v_grabber] to a new [ImageTexture], which effectively sets the grabber icon size to [code]0 px[/code].
 		</theme_item>
 		</theme_item>
 		<theme_item name="grabber" data_type="icon" type="Texture2D">
 		<theme_item name="grabber" data_type="icon" type="Texture2D">
 			The icon used for the grabber drawn in the middle area.
 			The icon used for the grabber drawn in the middle area.
@@ -70,5 +110,8 @@
 		<theme_item name="v_grabber" data_type="icon" type="Texture2D">
 		<theme_item name="v_grabber" data_type="icon" type="Texture2D">
 			The icon used for the grabber drawn in the middle area when [member vertical] is [code]true[/code].
 			The icon used for the grabber drawn in the middle area when [member vertical] is [code]true[/code].
 		</theme_item>
 		</theme_item>
+		<theme_item name="split_bar_background" data_type="style" type="StyleBox">
+			Determines the background of the split bar if its thickness is greater than zero.
+		</theme_item>
 	</theme_items>
 	</theme_items>
 </class>
 </class>

+ 165 - 79
scene/gui/split_container.cpp

@@ -32,6 +32,7 @@
 
 
 #include "scene/gui/label.h"
 #include "scene/gui/label.h"
 #include "scene/gui/margin_container.h"
 #include "scene/gui/margin_container.h"
+#include "scene/main/window.h"
 #include "scene/theme/theme_db.h"
 #include "scene/theme/theme_db.h"
 
 
 void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
@@ -39,7 +40,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 
 
 	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 
 
-	if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
+	if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) {
 		return;
 		return;
 	}
 	}
 
 
@@ -48,8 +49,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 	if (mb.is_valid()) {
 	if (mb.is_valid()) {
 		if (mb->get_button_index() == MouseButton::LEFT) {
 		if (mb->get_button_index() == MouseButton::LEFT) {
 			if (mb->is_pressed()) {
 			if (mb->is_pressed()) {
-				sc->_compute_middle_sep(true);
+				sc->_compute_split_offset(true);
 				dragging = true;
 				dragging = true;
+				sc->emit_signal(SNAME("drag_started"));
 				drag_ofs = sc->split_offset;
 				drag_ofs = sc->split_offset;
 				if (sc->vertical) {
 				if (sc->vertical) {
 					drag_from = get_transform().xform(mb->get_position()).y;
 					drag_from = get_transform().xform(mb->get_position()).y;
@@ -59,6 +61,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 			} else {
 			} else {
 				dragging = false;
 				dragging = false;
 				queue_redraw();
 				queue_redraw();
+				sc->emit_signal(SNAME("drag_ended"));
 			}
 			}
 		}
 		}
 	}
 	}
@@ -76,7 +79,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 		} else {
 		} else {
 			sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
 			sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
 		}
 		}
-		sc->_compute_middle_sep(true);
+		sc->_compute_split_offset(true);
 		sc->queue_sort();
 		sc->queue_sort();
 		sc->emit_signal(SNAME("dragged"), sc->get_split_offset());
 		sc->emit_signal(SNAME("dragged"), sc->get_split_offset());
 	}
 	}
@@ -84,11 +87,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
 
 
 Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
 Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
 	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 	SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
-
-	if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) {
+	if (!sc->collapsed && sc->dragging_enabled) {
 		return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
 		return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
 	}
 	}
-
 	return Control::get_cursor_shape(p_pos);
 	return Control::get_cursor_shape(p_pos);
 }
 }
 
 
@@ -101,7 +102,6 @@ void SplitContainerDragger::_notification(int p_what) {
 				queue_redraw();
 				queue_redraw();
 			}
 			}
 		} break;
 		} break;
-
 		case NOTIFICATION_MOUSE_EXIT: {
 		case NOTIFICATION_MOUSE_EXIT: {
 			mouse_inside = false;
 			mouse_inside = false;
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
@@ -109,22 +109,25 @@ void SplitContainerDragger::_notification(int p_what) {
 				queue_redraw();
 				queue_redraw();
 			}
 			}
 		} break;
 		} break;
-
 		case NOTIFICATION_DRAW: {
 		case NOTIFICATION_DRAW: {
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
 			SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
-			if (!dragging && !mouse_inside && sc->theme_cache.autohide) {
-				return;
+			draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect);
+			if (sc->dragger_visibility == sc->DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide)) {
+				Ref<Texture2D> tex = sc->_get_grabber_icon();
+				float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y);
+				if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits.
+					draw_texture(tex, (split_bar_rect.get_position() + (split_bar_rect.get_size() - tex->get_size()) * 0.5));
+				}
+			}
+			if (sc->show_drag_area && Engine::get_singleton()->is_editor_hint()) {
+				draw_rect(Rect2(Vector2(0, 0), get_size()), sc->dragging_enabled ? Color(1, 1, 0, 0.3) : Color(1, 0, 0, 0.3));
 			}
 			}
-
-			Ref<Texture2D> tex = sc->_get_grabber_icon();
-			draw_texture(tex, (get_size() - tex->get_size()) / 2);
 		} break;
 		} break;
 	}
 	}
 }
 }
 
 
 Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const {
 Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const {
 	int idx = 0;
 	int idx = 0;
-
 	for (int i = 0; i < get_child_count(false); i++) {
 	for (int i = 0; i < get_child_count(false); i++) {
 		Control *c = as_sortable_control(get_child(i, false), p_visibility_mode);
 		Control *c = as_sortable_control(get_child(i, false), p_visibility_mode);
 		if (!c) {
 		if (!c) {
@@ -137,7 +140,6 @@ Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_
 
 
 		idx++;
 		idx++;
 	}
 	}
-
 	return nullptr;
 	return nullptr;
 }
 }
 
 
@@ -153,45 +155,48 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
 	}
 	}
 }
 }
 
 
-void SplitContainer::_compute_middle_sep(bool p_clamp) {
+int SplitContainer::_get_separation() const {
+	if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) {
+		return 0;
+	}
+	// DRAGGER_VISIBLE or DRAGGER_HIDDEN.
+	Ref<Texture2D> g = _get_grabber_icon();
+	return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width());
+}
+
+void SplitContainer::_compute_split_offset(bool p_clamp) {
 	Control *first = _get_sortable_child(0);
 	Control *first = _get_sortable_child(0);
 	Control *second = _get_sortable_child(1);
 	Control *second = _get_sortable_child(1);
+	int axis_index = vertical ? 1 : 0;
+	int size = get_size()[axis_index];
+	int sep = _get_separation();
 
 
-	// Determine expanded children.
-	bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND;
-	bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND;
-
-	// Compute the minimum size.
-	int axis = vertical ? 1 : 0;
-	int size = get_size()[axis];
-	int ms_first = first->get_combined_minimum_size()[axis];
-	int ms_second = second->get_combined_minimum_size()[axis];
-
-	// Determine the separation between items.
-	Ref<Texture2D> g = _get_grabber_icon();
-	int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
-
-	// Compute the wished separation_point.
-	int wished_middle_sep = 0;
+	// Compute the wished size.
+	int wished_size = 0;
 	int split_offset_with_collapse = 0;
 	int split_offset_with_collapse = 0;
 	if (!collapsed) {
 	if (!collapsed) {
 		split_offset_with_collapse = split_offset;
 		split_offset_with_collapse = split_offset;
 	}
 	}
-	if (first_expanded && second_expanded) {
+	bool first_is_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND;
+	bool second_is_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND;
+
+	if (first_is_expanded && second_is_expanded) {
 		float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio());
 		float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio());
-		wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse;
-	} else if (first_expanded) {
-		wished_middle_sep = size - sep + split_offset_with_collapse;
+		wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse;
+	} else if (first_is_expanded) {
+		wished_size = size - sep + split_offset_with_collapse;
 	} else {
 	} else {
-		wished_middle_sep = split_offset_with_collapse;
+		wished_size = split_offset_with_collapse;
 	}
 	}
 
 
-	// Clamp the middle sep to acceptatble values.
-	middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second);
+	// 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);
 
 
 	// Clamp the split_offset if requested.
 	// Clamp the split_offset if requested.
 	if (p_clamp) {
 	if (p_clamp) {
-		split_offset -= wished_middle_sep - middle_sep;
+		split_offset -= wished_size - computed_split_offset;
 	}
 	}
 }
 }
 
 
@@ -199,8 +204,7 @@ void SplitContainer::_resort() {
 	Control *first = _get_sortable_child(0);
 	Control *first = _get_sortable_child(0);
 	Control *second = _get_sortable_child(1);
 	Control *second = _get_sortable_child(1);
 
 
-	// If we have only one element.
-	if (!first || !second) {
+	if (!first || !second) { // Only one child.
 		if (first) {
 		if (first) {
 			fit_child_in_rect(first, Rect2(Point2(), get_size()));
 			fit_child_in_rect(first, Rect2(Point2(), get_size()));
 		} else if (second) {
 		} else if (second) {
@@ -209,53 +213,50 @@ void SplitContainer::_resort() {
 		dragging_area_control->hide();
 		dragging_area_control->hide();
 		return;
 		return;
 	}
 	}
+	dragging_area_control->set_visible(!collapsed);
 
 
-	// If we have more that one.
-	_compute_middle_sep(false);
+	_compute_split_offset(false); // This recalculates and sets computed_split_offset.
 
 
-	// Determine the separation between items.
-	Ref<Texture2D> g = _get_grabber_icon();
-	int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
+	int sep = _get_separation();
+	bool is_rtl = is_layout_rtl();
 
 
-	// Move the children, including the dragger.
+	// Move the children.
 	if (vertical) {
 	if (vertical) {
-		fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep)));
-		int sofs = middle_sep + sep;
+		fit_child_in_rect(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(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
 	} else {
 	} else {
-		if (is_layout_rtl()) {
-			middle_sep = get_size().width - middle_sep - sep;
-			fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
-			int sofs = middle_sep + sep;
+		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)));
+			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(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
 		} else {
 		} else {
-			fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
-			int sofs = middle_sep + sep;
+			fit_child_in_rect(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(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
 		}
 		}
 	}
 	}
 
 
-	// Handle the dragger visibility and position.
-	if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) {
-		dragging_area_control->show();
-
-		int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
-		if (vertical) {
-			dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size)));
-		} else {
-			dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height)));
-		}
-
-		dragging_area_control->queue_redraw();
+	dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE);
+	const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
+	float split_bar_offset = (dragger_ctrl_size - sep) * 0.5;
+	if (vertical) {
+		Rect2 split_bar_rect = Rect2(is_rtl ? drag_area_margin_end : drag_area_margin_begin, computed_split_offset, get_size().width - drag_area_margin_begin - drag_area_margin_end, sep);
+		dragging_area_control->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size));
+		dragging_area_control->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size);
 	} else {
 	} else {
-		dragging_area_control->hide();
+		Rect2 split_bar_rect = Rect2(computed_split_offset, drag_area_margin_begin, sep, get_size().height - drag_area_margin_begin - drag_area_margin_end);
+		dragging_area_control->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (is_rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y));
+		dragging_area_control->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (is_rtl ? -1 : 1), 0.0), split_bar_rect.size);
 	}
 	}
+	queue_redraw();
+	dragging_area_control->queue_redraw();
 }
 }
 
 
 Size2 SplitContainer::get_minimum_size() const {
 Size2 SplitContainer::get_minimum_size() const {
 	Size2i minimum;
 	Size2i minimum;
-	Ref<Texture2D> g = _get_grabber_icon();
-	int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
+	int sep = _get_separation();
 
 
 	for (int i = 0; i < 2; i++) {
 	for (int i = 0; i < 2; i++) {
 		Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE);
 		Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE);
@@ -297,11 +298,9 @@ void SplitContainer::_notification(int p_what) {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 		case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
 			queue_sort();
 			queue_sort();
 		} break;
 		} break;
-
 		case NOTIFICATION_SORT_CHILDREN: {
 		case NOTIFICATION_SORT_CHILDREN: {
 			_resort();
 			_resort();
 		} break;
 		} break;
-
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			update_minimum_size();
 			update_minimum_size();
 		} break;
 		} break;
@@ -312,9 +311,7 @@ void SplitContainer::set_split_offset(int p_offset) {
 	if (split_offset == p_offset) {
 	if (split_offset == p_offset) {
 		return;
 		return;
 	}
 	}
-
 	split_offset = p_offset;
 	split_offset = p_offset;
-
 	queue_sort();
 	queue_sort();
 }
 }
 
 
@@ -326,8 +323,7 @@ void SplitContainer::clamp_split_offset() {
 	if (!_get_sortable_child(0) || !_get_sortable_child(1)) {
 	if (!_get_sortable_child(0) || !_get_sortable_child(1)) {
 		return;
 		return;
 	}
 	}
-
-	_compute_middle_sep(true);
+	_compute_split_offset(true);
 	queue_sort();
 	queue_sort();
 }
 }
 
 
@@ -335,7 +331,6 @@ void SplitContainer::set_collapsed(bool p_collapsed) {
 	if (collapsed == p_collapsed) {
 	if (collapsed == p_collapsed) {
 		return;
 		return;
 	}
 	}
-
 	collapsed = p_collapsed;
 	collapsed = p_collapsed;
 	queue_sort();
 	queue_sort();
 }
 }
@@ -344,7 +339,6 @@ void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
 	if (dragger_visibility == p_visibility) {
 	if (dragger_visibility == p_visibility) {
 		return;
 		return;
 	}
 	}
-
 	dragger_visibility = p_visibility;
 	dragger_visibility = p_visibility;
 	queue_sort();
 	queue_sort();
 }
 }
@@ -368,6 +362,26 @@ bool SplitContainer::is_vertical() const {
 	return vertical;
 	return vertical;
 }
 }
 
 
+void SplitContainer::set_dragging_enabled(bool p_enabled) {
+	if (dragging_enabled == p_enabled) {
+		return;
+	}
+	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"));
+	}
+	if (get_viewport()) {
+		get_viewport()->update_mouse_cursor_state();
+	}
+	_resort();
+}
+
+bool SplitContainer::is_dragging_enabled() const {
+	return dragging_enabled;
+}
+
 Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const {
 Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const {
 	Vector<int> flags;
 	Vector<int> flags;
 	flags.append(SIZE_FILL);
 	flags.append(SIZE_FILL);
@@ -392,6 +406,51 @@ Vector<int> SplitContainer::get_allowed_size_flags_vertical() const {
 	return flags;
 	return flags;
 }
 }
 
 
+void SplitContainer::set_drag_area_margin_begin(int p_margin) {
+	if (drag_area_margin_begin == p_margin) {
+		return;
+	}
+	drag_area_margin_begin = p_margin;
+	queue_sort();
+}
+
+int SplitContainer::get_drag_area_margin_begin() const {
+	return drag_area_margin_begin;
+}
+
+void SplitContainer::set_drag_area_margin_end(int p_margin) {
+	if (drag_area_margin_end == p_margin) {
+		return;
+	}
+	drag_area_margin_end = p_margin;
+	queue_sort();
+}
+
+int SplitContainer::get_drag_area_margin_end() const {
+	return drag_area_margin_end;
+}
+
+void SplitContainer::set_drag_area_offset(int p_offset) {
+	if (drag_area_offset == p_offset) {
+		return;
+	}
+	drag_area_offset = p_offset;
+	queue_sort();
+}
+
+int SplitContainer::get_drag_area_offset() const {
+	return drag_area_offset;
+}
+
+void SplitContainer::set_show_drag_area_enabled(bool p_enabled) {
+	show_drag_area = p_enabled;
+	dragging_area_control->queue_redraw();
+}
+
+bool SplitContainer::is_show_drag_area_enabled() const {
+	return show_drag_area;
+}
+
 void SplitContainer::_bind_methods() {
 void SplitContainer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset);
 	ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset);
 	ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset);
 	ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset);
@@ -406,13 +465,39 @@ void SplitContainer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical);
 	ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical);
 	ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical);
 	ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical);
 
 
+	ClassDB::bind_method(D_METHOD("set_dragging_enabled", "dragging_enabled"), &SplitContainer::set_dragging_enabled);
+	ClassDB::bind_method(D_METHOD("is_dragging_enabled"), &SplitContainer::is_dragging_enabled);
+
+	ClassDB::bind_method(D_METHOD("set_drag_area_margin_begin", "margin"), &SplitContainer::set_drag_area_margin_begin);
+	ClassDB::bind_method(D_METHOD("get_drag_area_margin_begin"), &SplitContainer::get_drag_area_margin_begin);
+
+	ClassDB::bind_method(D_METHOD("set_drag_area_margin_end", "margin"), &SplitContainer::set_drag_area_margin_end);
+	ClassDB::bind_method(D_METHOD("get_drag_area_margin_end"), &SplitContainer::get_drag_area_margin_end);
+
+	ClassDB::bind_method(D_METHOD("set_drag_area_offset", "offset"), &SplitContainer::set_drag_area_offset);
+	ClassDB::bind_method(D_METHOD("get_drag_area_offset"), &SplitContainer::get_drag_area_offset);
+
+	ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled);
+	ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled);
+
+	ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control);
+
 	ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
 	ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
+	ADD_SIGNAL(MethodInfo("drag_started"));
+	ADD_SIGNAL(MethodInfo("drag_ended"));
 
 
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset");
 	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, "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, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
 
 
+	ADD_GROUP("Drag Area", "drag_area_");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_end", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_end", "get_drag_area_margin_end");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_offset", "get_drag_area_offset");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_area_highlight_in_editor"), "set_drag_area_highlight_in_editor", "is_drag_area_highlight_in_editor_enabled");
+
 	BIND_ENUM_CONSTANT(DRAGGER_VISIBLE);
 	BIND_ENUM_CONSTANT(DRAGGER_VISIBLE);
 	BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
 	BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
 	BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);
 	BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);
@@ -423,6 +508,7 @@ void SplitContainer::_bind_methods() {
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber");
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber");
+	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background");
 }
 }
 
 
 SplitContainer::SplitContainer(bool p_vertical) {
 SplitContainer::SplitContainer(bool p_vertical) {

+ 29 - 2
scene/gui/split_container.h

@@ -35,6 +35,8 @@
 
 
 class SplitContainerDragger : public Control {
 class SplitContainerDragger : public Control {
 	GDCLASS(SplitContainerDragger, Control);
 	GDCLASS(SplitContainerDragger, Control);
+	friend class SplitContainer;
+	Rect2 split_bar_rect;
 
 
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
@@ -62,11 +64,16 @@ public:
 	};
 	};
 
 
 private:
 private:
+	int show_drag_area = false;
+	int drag_area_margin_begin = 0;
+	int drag_area_margin_end = 0;
+	int drag_area_offset = 0;
 	int split_offset = 0;
 	int split_offset = 0;
-	int middle_sep = 0;
+	int computed_split_offset = 0;
 	bool vertical = false;
 	bool vertical = false;
 	bool collapsed = false;
 	bool collapsed = false;
 	DraggerVisibility dragger_visibility = DRAGGER_VISIBLE;
 	DraggerVisibility dragger_visibility = DRAGGER_VISIBLE;
+	bool dragging_enabled = true;
 
 
 	SplitContainerDragger *dragging_area_control = nullptr;
 	SplitContainerDragger *dragging_area_control = nullptr;
 
 
@@ -77,10 +84,13 @@ private:
 		Ref<Texture2D> grabber_icon;
 		Ref<Texture2D> grabber_icon;
 		Ref<Texture2D> grabber_icon_h;
 		Ref<Texture2D> grabber_icon_h;
 		Ref<Texture2D> grabber_icon_v;
 		Ref<Texture2D> grabber_icon_v;
+		float base_scale = 1.0;
+		Ref<StyleBox> split_bar_background;
 	} theme_cache;
 	} theme_cache;
 
 
 	Ref<Texture2D> _get_grabber_icon() const;
 	Ref<Texture2D> _get_grabber_icon() const;
-	void _compute_middle_sep(bool p_clamp);
+	void _compute_split_offset(bool p_clamp);
+	int _get_separation() const;
 	void _resort();
 	void _resort();
 	Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const;
 	Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const;
 
 
@@ -105,11 +115,28 @@ public:
 	void set_vertical(bool p_vertical);
 	void set_vertical(bool p_vertical);
 	bool is_vertical() const;
 	bool is_vertical() const;
 
 
+	void set_dragging_enabled(bool p_enabled);
+	bool is_dragging_enabled() const;
+
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
 
 
 	virtual Vector<int> get_allowed_size_flags_horizontal() const override;
 	virtual Vector<int> get_allowed_size_flags_horizontal() const override;
 	virtual Vector<int> get_allowed_size_flags_vertical() const override;
 	virtual Vector<int> get_allowed_size_flags_vertical() const override;
 
 
+	void set_drag_area_margin_begin(int p_margin);
+	int get_drag_area_margin_begin() const;
+
+	void set_drag_area_margin_end(int p_margin);
+	int get_drag_area_margin_end() const;
+
+	void set_drag_area_offset(int p_offset);
+	int get_drag_area_offset() const;
+
+	void set_show_drag_area_enabled(bool p_enabled);
+	bool is_show_drag_area_enabled() const;
+
+	Control *get_drag_area_control() { return dragging_area_control; }
+
 	SplitContainer(bool p_vertical = false);
 	SplitContainer(bool p_vertical = false);
 };
 };
 
 

+ 3 - 0
scene/theme/default_theme.cpp

@@ -1174,6 +1174,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_constant("v_separation", "VFlowContainer", Math::round(4 * scale));
 	theme->set_constant("v_separation", "VFlowContainer", Math::round(4 * scale));
 
 
 	theme->set_stylebox(SceneStringName(panel), "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0));
 	theme->set_stylebox(SceneStringName(panel), "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0));
+	theme->set_stylebox("split_bar_background", "SplitContainer", make_empty_stylebox(0, 0, 0, 0));
+	theme->set_stylebox("split_bar_background", "VSplitContainer", make_empty_stylebox(0, 0, 0, 0));
+	theme->set_stylebox("split_bar_background", "HSplitContainer", make_empty_stylebox(0, 0, 0, 0));
 
 
 	theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]);
 	theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]);
 	theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]);
 	theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]);