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
30 changes: 30 additions & 0 deletions doc/classes/ScrollContainer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
<member name="follow_focus" type="bool" setter="set_follow_focus" getter="is_following_focus" default="false">
If [code]true[/code], the ScrollContainer will automatically scroll to focused children (including indirect children) to make sure they are fully visible.
</member>
<member name="horizontal_content_align" type="int" setter="set_horizontal_content_align" getter="get_horizontal_content_align" enum="ScrollContainer.ContentHAlign" default="0">
Controls Horizontal Placement of Contents.
[b]Left[/b] ([constant CONTENT_H_ALIGN_LEFT]) = Horizontally Align Contents in Left [i](Default)[/i]
[b]Center[/b] ([constant CONTENT_H_ALIGN_CENTER]) = Horizontally Align Contents in Center
[b]Right[/b] ([constant CONTENT_H_ALIGN_RIGHT]) = Horizontally Align Contents in Right
</member>
<member name="horizontal_scroll_mode" type="int" setter="set_horizontal_scroll_mode" getter="get_horizontal_scroll_mode" enum="ScrollContainer.ScrollMode" default="1">
Controls whether horizontal scrollbar can be used and when it should be visible.
</member>
Expand Down Expand Up @@ -74,6 +80,12 @@
<member name="scroll_vertical_custom_step" type="float" setter="set_vertical_custom_step" getter="get_vertical_custom_step" default="-1.0">
Overrides the [member ScrollBar.custom_step] used when clicking the internal scroll bar's vertical increment and decrement buttons or when using arrow keys when the [ScrollBar] is focused.
</member>
<member name="vertical_content_align" type="int" setter="set_vertical_content_align" getter="get_vertical_content_align" enum="ScrollContainer.ContentVAlign" default="0">
Controls Vertical Placement of Contents.
[b]Top[/b] ([constant CONTENT_V_ALIGN_TOP]) = Vertically Align Contents at Top [i](Default)[/i]
[b]Center[/b] ([constant CONTENT_V_ALIGN_CENTER]) = Vertically Align Contents in Center
[b]Bottom[/b] ([constant CONTENT_V_ALIGN_BOTTOM]) = Vertically Align Contents at Bottom
</member>
<member name="vertical_scroll_mode" type="int" setter="set_vertical_scroll_mode" getter="get_vertical_scroll_mode" enum="ScrollContainer.ScrollMode" default="1">
Controls whether vertical scrollbar can be used and when it should be visible.
</member>
Expand Down Expand Up @@ -108,6 +120,24 @@
<constant name="SCROLL_MODE_RESERVE" value="4" enum="ScrollMode">
Combines [constant SCROLL_MODE_AUTO] and [constant SCROLL_MODE_SHOW_ALWAYS]. The scrollbar is only visible if necessary, but the content size is adjusted as if it was always visible. It's useful for ensuring that content size stays the same regardless if the scrollbar is visible.
</constant>
<constant name="CONTENT_H_ALIGN_LEFT" value="0" enum="ContentHAlign">
Left horizontal alignment of contents.
</constant>
<constant name="CONTENT_H_ALIGN_CENTER" value="1" enum="ContentHAlign">
Center horizontal alignment of contents.
</constant>
<constant name="CONTENT_H_ALIGN_RIGHT" value="2" enum="ContentHAlign">
Right horizontal alignment of contents.
</constant>
<constant name="CONTENT_V_ALIGN_TOP" value="0" enum="ContentVAlign">
Top vertical alignment of contents.
</constant>
<constant name="CONTENT_V_ALIGN_CENTER" value="1" enum="ContentVAlign">
Center vertical alignment of contents.
</constant>
<constant name="CONTENT_V_ALIGN_BOTTOM" value="2" enum="ContentVAlign">
Bottom vertical alignment of contents.
</constant>
</constants>
<theme_items>
<theme_item name="focus" data_type="style" type="StyleBox">
Expand Down
112 changes: 110 additions & 2 deletions scene/gui/scroll_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,45 @@ void ScrollContainer::_reposition_children() {
size.x -= v_scroll->get_minimum_size().x;
}

float viewport_w = size.x;
float viewport_h = size.y;

float total_content_w = 0.0f;
float total_content_h = 0.0f;
for (int ci = 0; ci < get_child_count(); ci++) {
Control *cc = as_sortable_control(get_child(ci));
if (!cc || cc == h_scroll || cc == v_scroll || cc == focus_panel) {
continue;
}
Size2 ms = cc->get_combined_minimum_size();
float child_w = ms.x;
float child_h = ms.y;
if (cc->get_h_size_flags().has_flag(SIZE_EXPAND)) {
child_w = MAX(viewport_w, ms.x);
}
if (cc->get_v_size_flags().has_flag(SIZE_EXPAND)) {
child_h = MAX(viewport_h, ms.y);
}
total_content_w = MAX(total_content_w, child_w);
total_content_h += child_h;
}

float max_scroll_x = total_content_w > viewport_w ? (total_content_w - viewport_w) : 0.0f;
float max_scroll_y = total_content_h > viewport_h ? (total_content_h - viewport_h) : 0.0f;
float scroll_x = h_scroll ? CLAMP(h_scroll->get_value(), 0.0f, max_scroll_x) : 0.0f;
float scroll_y = v_scroll ? CLAMP(v_scroll->get_value(), 0.0f, max_scroll_y) : 0.0f;

float group_v_offset = 0.0f;
if (max_scroll_y <= 0.0f && total_content_h < viewport_h) {
float extra = viewport_h - total_content_h;
if (vertical_content_align == CONTENT_V_ALIGN_CENTER) {
group_v_offset = Math::floor(extra * 0.5f);
} else if (vertical_content_align == CONTENT_V_ALIGN_BOTTOM) {
group_v_offset = Math::floor(extra);
}
}
ofs.y += group_v_offset;

for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
Expand All @@ -370,18 +409,44 @@ void ScrollContainer::_reposition_children() {
}
Size2 minsize = c->get_combined_minimum_size();

Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize);
Rect2 r = Rect2(ofs, minsize);
if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) {
r.size.width = MAX(size.width, minsize.width);
}
if (c->get_v_size_flags().has_flag(SIZE_EXPAND)) {
r.size.height = MAX(size.height, minsize.height);
}
r.position += ofs;
if (rtl && reserve_vscroll) {
r.position.x += v_scroll->get_minimum_size().x;
}
r.position = r.position.floor();

{
float content_w = r.size.width;
float viewport_w_local = viewport_w;
ContentHAlign align_h = horizontal_content_align;
if (is_layout_rtl()) {
if (align_h == CONTENT_H_ALIGN_LEFT) {
align_h = CONTENT_H_ALIGN_RIGHT;
} else if (align_h == CONTENT_H_ALIGN_RIGHT) {
align_h = CONTENT_H_ALIGN_LEFT;
}
}
if (max_scroll_x <= 0.0f && content_w < viewport_w_local) {
float extra = viewport_w_local - content_w;
if (align_h == CONTENT_H_ALIGN_CENTER) {
r.position.x += Math::floor(extra * 0.5f);
} else if (align_h == CONTENT_H_ALIGN_RIGHT) {
r.position.x += Math::floor(extra);
}
}
}

r.position.x -= scroll_x;
r.position.y -= scroll_y;

r.position = r.position.floor();

fit_child_in_rect(c, r);
}

Expand Down Expand Up @@ -752,12 +817,21 @@ void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_draw_focus_border", "draw"), &ScrollContainer::set_draw_focus_border);
ClassDB::bind_method(D_METHOD("get_draw_focus_border"), &ScrollContainer::get_draw_focus_border);

ClassDB::bind_method(D_METHOD("set_horizontal_content_align", "alignment"), &ScrollContainer::set_horizontal_content_align);
ClassDB::bind_method(D_METHOD("get_horizontal_content_align"), &ScrollContainer::get_horizontal_content_align);

ClassDB::bind_method(D_METHOD("set_vertical_content_align", "alignment"), &ScrollContainer::set_vertical_content_align);
ClassDB::bind_method(D_METHOD("get_vertical_content_align"), &ScrollContainer::get_vertical_content_align);

ADD_SIGNAL(MethodInfo("scroll_started"));
ADD_SIGNAL(MethodInfo("scroll_ended"));

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_focus_border"), "set_draw_focus_border", "get_draw_focus_border");

ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_content_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_horizontal_content_align", "get_horizontal_content_align");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_content_align", PROPERTY_HINT_ENUM, "Top,Center,Bottom"), "set_vertical_content_align", "get_vertical_content_align");

ADD_GROUP("Scroll", "scroll_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll");
Expand All @@ -773,6 +847,14 @@ void ScrollContainer::_bind_methods() {
BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER);
BIND_ENUM_CONSTANT(SCROLL_MODE_RESERVE);

BIND_ENUM_CONSTANT(CONTENT_H_ALIGN_LEFT);
BIND_ENUM_CONSTANT(CONTENT_H_ALIGN_CENTER);
BIND_ENUM_CONSTANT(CONTENT_H_ALIGN_RIGHT);

BIND_ENUM_CONSTANT(CONTENT_V_ALIGN_TOP);
BIND_ENUM_CONSTANT(CONTENT_V_ALIGN_CENTER);
BIND_ENUM_CONSTANT(CONTENT_V_ALIGN_BOTTOM);

BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, focus_style, "focus");

Expand Down Expand Up @@ -824,3 +906,29 @@ ScrollContainer::ScrollContainer() {

set_clip_contents(true);
}

void ScrollContainer::set_horizontal_content_align(ContentHAlign p_align) {
ERR_FAIL_INDEX(p_align, CONTENT_H_ALIGN_MAX);
if (horizontal_content_align == p_align) {
return;
}
horizontal_content_align = p_align;
queue_sort();
}

ScrollContainer::ContentHAlign ScrollContainer::get_horizontal_content_align() const {
return horizontal_content_align;
}

void ScrollContainer::set_vertical_content_align(ContentVAlign p_align) {
ERR_FAIL_INDEX(p_align, CONTENT_V_ALIGN_MAX);
if (vertical_content_align == p_align) {
return;
}
vertical_content_align = p_align;
queue_sort();
}

ScrollContainer::ContentVAlign ScrollContainer::get_vertical_content_align() const {
return vertical_content_align;
}
25 changes: 25 additions & 0 deletions scene/gui/scroll_container.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,28 @@ class ScrollContainer : public Container {
SCROLL_MODE_RESERVE,
};

enum ContentHAlign {
CONTENT_H_ALIGN_LEFT,
CONTENT_H_ALIGN_CENTER,
CONTENT_H_ALIGN_RIGHT,
CONTENT_H_ALIGN_MAX
};

enum ContentVAlign {
CONTENT_V_ALIGN_TOP,
CONTENT_V_ALIGN_CENTER,
CONTENT_V_ALIGN_BOTTOM,
CONTENT_V_ALIGN_MAX
};

private:
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
PanelContainer *focus_panel = nullptr;

ContentHAlign horizontal_content_align = CONTENT_H_ALIGN_LEFT;
ContentVAlign vertical_content_align = CONTENT_V_ALIGN_TOP;

mutable Size2 largest_child_min_size; // The largest one among the min sizes of all available child controls.

void update_scrollbars();
Expand Down Expand Up @@ -113,6 +130,12 @@ class ScrollContainer : public Container {
public:
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;

void set_horizontal_content_align(ContentHAlign p_align);
ContentHAlign get_horizontal_content_align() const;

void set_vertical_content_align(ContentVAlign p_align);
ContentVAlign get_vertical_content_align() const;

void set_h_scroll(int p_pos);
int get_h_scroll() const;

Expand Down Expand Up @@ -152,3 +175,5 @@ class ScrollContainer : public Container {
};

VARIANT_ENUM_CAST(ScrollContainer::ScrollMode);
VARIANT_ENUM_CAST(ScrollContainer::ContentHAlign);
VARIANT_ENUM_CAST(ScrollContainer::ContentVAlign);