Improvements to SplitContainer including a drag bar background StyleBox.

This commit is contained in:
Koyper 2023-02-03 11:14:22 -06:00
parent ee363af0ed
commit f7005deb1e
4 changed files with 247 additions and 88 deletions

View File

@ -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>

View File

@ -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->dragging_enabled) {
if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) {
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) { draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect);
return; 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. // Compute the wished size.
bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; int wished_size = 0;
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;
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; wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse;
} else if (first_expanded) { } else if (first_is_expanded) {
wished_middle_sep = size - sep + split_offset_with_collapse; 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. // Clamp the split offset to acceptable values.
middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second); 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) { // Only one child.
if (!first || !second) {
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_split_offset(false); // This recalculates and sets computed_split_offset.
_compute_middle_sep(false);
// Determine the separation between items. int sep = _get_separation();
Ref<Texture2D> g = _get_grabber_icon(); bool is_rtl = is_layout_rtl();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
// 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))); fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset)));
int sofs = middle_sep + sep; 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()) { if (is_rtl) {
middle_sep = get_size().width - middle_sep - sep; computed_split_offset = get_size().width - computed_split_offset - sep;
fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
int sofs = middle_sep + sep; 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))); fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height)));
int sofs = middle_sep + sep; 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. dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE);
if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) { const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
dragging_area_control->show(); float split_bar_offset = (dragger_ctrl_size - sep) * 0.5;
if (vertical) {
int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); 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);
if (vertical) { 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->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, 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 {
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();
} 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 = _get_separation();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
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_split_offset(true);
_compute_middle_sep(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) {

View File

@ -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);
}; };

View File

@ -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"]);