Skip to content

Commit 1bb6acb

Browse files
committed
[ScrollContainer] Add option to hold child at maximum scroll value when resized
1 parent 3769034 commit 1bb6acb

3 files changed

Lines changed: 85 additions & 6 deletions

File tree

doc/classes/ScrollContainer.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
4646
<member name="follow_focus" type="bool" setter="set_follow_focus" getter="is_following_focus" default="false">
4747
If [code]true[/code], the ScrollContainer will automatically scroll to focused children (including indirect children) to make sure they are fully visible.
4848
</member>
49+
<member name="hold_when_max_horizontal" type="bool" setter="set_hold_when_max_horizontal" getter="is_hold_when_max_horizontal" default="false">
50+
If [code]true[/code], when [member scroll_horizontal] is at its maximum value, the [ScrollContainer] will adjust [member scroll_horizontal] to its maximum possible value when resized, keeping the end of the child [Control] pinned to the end of the [ScrollContainer]. When [member scroll_horizontal] is at any other value than its maximum, the [ScrollContainer] will preserve the numerical value of [member scroll_horizontal] when resized, which means the end of the child [Control] may be subsumed by the end of the [ScrollContainer].
51+
</member>
52+
<member name="hold_when_max_vertical" type="bool" setter="set_hold_when_max_vertical" getter="is_hold_when_max_vertical" default="false">
53+
If [code]true[/code], when [member scroll_vertical] is at its maximum value, the [ScrollContainer] will adjust [member scroll_vertical] to its maximum possible value when resized, keeping the end of the child [Control] pinned to the end of the [ScrollContainer]. When [member scroll_vertical] is at any other value than its maximum, the [ScrollContainer] will preserve the numerical value of [member scroll_vertical] when resized, which means the end of the child [Control] may be subsumed by the end of the [ScrollContainer].
54+
</member>
4955
<member name="horizontal_scroll_mode" type="int" setter="set_horizontal_scroll_mode" getter="get_horizontal_scroll_mode" enum="ScrollContainer.ScrollMode" default="1">
5056
Controls whether horizontal scrollbar can be used and when it should be visible.
5157
</member>

scene/gui/scroll_container.cpp

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ void ScrollContainer::ensure_control_visible(Control *p_control) {
341341
void ScrollContainer::_reposition_children() {
342342
_update_scrollbars();
343343
_update_scroll_hints();
344+
Size2 size = get_size();
345+
Point2 ofs;
346+
bool h_scroll_is_visible = false;
347+
bool v_scroll_is_visible = false;
348+
bool is_resizing = previous_size != size;
349+
previous_size = size;
344350

345351
Rect2 margins = _get_margins();
346352
Size2 size = get_size();
@@ -352,23 +358,30 @@ void ScrollContainer::_reposition_children() {
352358

353359
if (_is_h_scroll_visible() || horizontal_scroll_mode == SCROLL_MODE_RESERVE) {
354360
size.y -= h_scroll->get_minimum_size().y + theme_cache.scrollbar_v_separation;
361+
h_scroll_is_visible = true;
362+
}
363+
if (hold_when_max_horizontal && child_previous_min_size == Size2()) {
364+
previous_h_scroll_was_max = true;
355365
}
356366

357367
if (reserve_vscroll) {
358-
int width = v_scroll->get_minimum_size().x + theme_cache.scrollbar_h_separation;
359-
size.x -= width;
360-
if (rtl) {
361-
ofs.x += width;
362-
}
368+
size.x -= v_scroll->get_minimum_size().x + theme_cache.scrollbar_h_separation;
369+
v_scroll_is_visible = true;
370+
}
371+
if (hold_when_max_vertical && child_previous_min_size == Size2()) {
372+
previous_v_scroll_was_max = true;
363373
}
364374

365375
for (int i = 0; i < get_child_count(); i++) {
366376
Control *c = as_sortable_control(get_child(i));
367377
if (!c || c == h_scroll || c == v_scroll || c == focus_panel || c == scroll_hint_top_left || c == scroll_hint_bottom_right) {
368378
continue;
369379
}
370-
371380
Size2 minsize = c->get_combined_minimum_size();
381+
if (child_previous_min_size != minsize) {
382+
is_resizing = true;
383+
}
384+
child_previous_min_size = minsize;
372385
Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize);
373386

374387
if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) {
@@ -381,6 +394,26 @@ void ScrollContainer::_reposition_children() {
381394
r.position += ofs;
382395
r.position = r.position.floor();
383396
fit_child_in_rect(c, r);
397+
if (hold_when_max_horizontal && is_resizing && h_scroll_is_visible && previous_h_scroll_was_max) {
398+
c->set_position(Point2(size.x - c->get_size().x, c->get_position().y));
399+
}
400+
if (hold_when_max_vertical && is_resizing && v_scroll_is_visible && previous_v_scroll_was_max) {
401+
c->set_position(Point2(c->get_position().x, size.y - c->get_size().y));
402+
}
403+
}
404+
if (h_scroll_is_visible) {
405+
if (hold_when_max_horizontal && is_resizing && previous_h_scroll_was_max) {
406+
h_scroll->set_value_no_signal(h_scroll->get_max() - h_scroll->get_page());
407+
_cancel_drag();
408+
}
409+
previous_h_scroll_was_max = h_scroll->get_max() - h_scroll->get_page() - h_scroll->get_value() <= max_value_snap;
410+
}
411+
if (v_scroll_is_visible) {
412+
if (hold_when_max_vertical && is_resizing && previous_v_scroll_was_max) {
413+
v_scroll->set_value_no_signal(v_scroll->get_max() - v_scroll->get_page());
414+
_cancel_drag();
415+
}
416+
previous_v_scroll_was_max = v_scroll->get_max() - v_scroll->get_page() - v_scroll->get_value() <= max_value_snap;
384417
}
385418

386419
if (draw_focus_border) {
@@ -803,6 +836,22 @@ VScrollBar *ScrollContainer::get_v_scroll_bar() {
803836
return v_scroll;
804837
}
805838

839+
bool ScrollContainer::is_hold_when_max_horizontal() const {
840+
return hold_when_max_horizontal;
841+
}
842+
843+
void ScrollContainer::set_hold_when_max_horizontal(bool p_keep_max) {
844+
hold_when_max_horizontal = p_keep_max;
845+
}
846+
847+
bool ScrollContainer::is_hold_when_max_vertical() const {
848+
return hold_when_max_vertical;
849+
}
850+
851+
void ScrollContainer::set_hold_when_max_vertical(bool p_keep_max) {
852+
hold_when_max_vertical = p_keep_max;
853+
}
854+
806855
void ScrollContainer::_bind_methods() {
807856
ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll);
808857
ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll);
@@ -834,6 +883,12 @@ void ScrollContainer::_bind_methods() {
834883
ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus);
835884
ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus);
836885

886+
ClassDB::bind_method(D_METHOD("set_hold_when_max_horizontal", "enabled"), &ScrollContainer::set_hold_when_max_horizontal);
887+
ClassDB::bind_method(D_METHOD("is_hold_when_max_horizontal"), &ScrollContainer::is_hold_when_max_horizontal);
888+
889+
ClassDB::bind_method(D_METHOD("set_hold_when_max_vertical", "enabled"), &ScrollContainer::set_hold_when_max_vertical);
890+
ClassDB::bind_method(D_METHOD("is_hold_when_max_vertical"), &ScrollContainer::is_hold_when_max_vertical);
891+
837892
ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &ScrollContainer::get_h_scroll_bar);
838893
ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &ScrollContainer::get_v_scroll_bar);
839894
ClassDB::bind_method(D_METHOD("ensure_control_visible", "control"), &ScrollContainer::ensure_control_visible);
@@ -848,6 +903,11 @@ void ScrollContainer::_bind_methods() {
848903
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_focus_border"), "set_draw_focus_border", "get_draw_focus_border");
849904

850905
ADD_GROUP("Scrollbar", "");
906+
ADD_GROUP("Resize Behavior", "");
907+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hold_when_max_horizontal"), "set_hold_when_max_horizontal", "is_hold_when_max_horizontal");
908+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hold_when_max_vertical"), "set_hold_when_max_vertical", "is_hold_when_max_vertical");
909+
910+
ADD_GROUP("Scroll", "scroll_");
851911
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll");
852912
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll");
853913
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_horizontal_custom_step", "get_horizontal_custom_step");

scene/gui/scroll_container.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ class ScrollContainer : public Container {
7474
bool drag_touching_deaccel = false;
7575
bool beyond_deadzone = false;
7676
bool scroll_on_drag_hover = false;
77+
bool previous_h_scroll_was_max;
78+
bool previous_v_scroll_was_max;
79+
bool hold_when_max_horizontal = false;
80+
bool hold_when_max_vertical = false;
81+
const int max_value_snap = 5;
82+
Size2 previous_size;
83+
Size2 child_previous_min_size;
7784

7885
TextureRect *scroll_hint_top_left = nullptr;
7986
TextureRect *scroll_hint_bottom_right = nullptr;
@@ -170,6 +177,12 @@ class ScrollContainer : public Container {
170177

171178
void set_scroll_on_drag_hover(bool p_scroll);
172179

180+
bool is_hold_when_max_horizontal() const;
181+
void set_hold_when_max_horizontal(bool p_keep_max);
182+
183+
bool is_hold_when_max_vertical() const;
184+
void set_hold_when_max_vertical(bool p_keep_max);
185+
173186
HScrollBar *get_h_scroll_bar();
174187
VScrollBar *get_v_scroll_bar();
175188
void ensure_control_visible(Control *p_control);

0 commit comments

Comments
 (0)