Add scroll hints to ScrollContainer and Tree

This commit is contained in:
Michael Alexsander 2025-11-06 23:01:16 -03:00
parent 9dd6c4dbac
commit 639e396d98
No known key found for this signature in database
GPG key ID: A9C91EE110F4EABA
10 changed files with 282 additions and 16 deletions

View file

@ -52,6 +52,10 @@
<member name="scroll_deadzone" type="int" setter="set_deadzone" getter="get_deadzone" default="0"> <member name="scroll_deadzone" type="int" setter="set_deadzone" getter="get_deadzone" default="0">
Deadzone for touch scrolling. Lower deadzone makes the scrolling more sensitive. Deadzone for touch scrolling. Lower deadzone makes the scrolling more sensitive.
</member> </member>
<member name="scroll_hint_mode" type="int" setter="set_scroll_hint_mode" getter="get_scroll_hint_mode" enum="ScrollContainer.ScrollHintMode" default="0">
The way which scroll hints (indicators that show that the content can still be scrolled in a certain direction) will be shown.
[b]Note:[/b] Hints won't be shown if the content can be scrolled both vertically and horizontally.
</member>
<member name="scroll_horizontal" type="int" setter="set_h_scroll" getter="get_h_scroll" default="0"> <member name="scroll_horizontal" type="int" setter="set_h_scroll" getter="get_h_scroll" default="0">
The current horizontal scroll value. The current horizontal scroll value.
[b]Note:[/b] If you are setting this value in the [method Node._ready] function or earlier, it needs to be wrapped with [method Object.set_deferred], since scroll bar's [member Range.max_value] is not initialized yet. [b]Note:[/b] If you are setting this value in the [method Node._ready] function or earlier, it needs to be wrapped with [method Object.set_deferred], since scroll bar's [member Range.max_value] is not initialized yet.
@ -74,6 +78,9 @@
<member name="scroll_vertical_custom_step" type="float" setter="set_vertical_custom_step" getter="get_vertical_custom_step" default="-1.0"> <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. 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>
<member name="tile_scroll_hint" type="bool" setter="set_tile_scroll_hint" getter="is_scroll_hint_tiled" default="false">
If [code]true[/code], the scroll hint texture will be tiled instead of stretched. See [member scroll_hint_mode].
</member>
<member name="vertical_scroll_mode" type="int" setter="set_vertical_scroll_mode" getter="get_vertical_scroll_mode" enum="ScrollContainer.ScrollMode" default="1"> <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. Controls whether vertical scrollbar can be used and when it should be visible.
</member> </member>
@ -108,6 +115,18 @@
<constant name="SCROLL_MODE_RESERVE" value="4" enum="ScrollMode"> <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. 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>
<constant name="SCROLL_HINT_MODE_DISABLED" value="0" enum="ScrollHintMode">
Scroll hints will never be shown.
</constant>
<constant name="SCROLL_HINT_MODE_ALL" value="1" enum="ScrollHintMode">
Scroll hints will be shown at the top and bottom (if vertical), or left and right (if horizontal).
</constant>
<constant name="SCROLL_HINT_MODE_TOP_AND_LEFT" value="2" enum="ScrollHintMode">
Scroll hints will be shown at the top (if vertical), or the left (if horizontal).
</constant>
<constant name="SCROLL_HINT_MODE_BOTTOM_AND_RIGHT" value="3" enum="ScrollHintMode">
Scroll hints will be shown at the bottom (if horizontal), or the right (if horizontal).
</constant>
</constants> </constants>
<theme_items> <theme_items>
<theme_item name="scrollbar_h_separation" data_type="constant" type="int" default="0"> <theme_item name="scrollbar_h_separation" data_type="constant" type="int" default="0">
@ -116,6 +135,12 @@
<theme_item name="scrollbar_v_separation" data_type="constant" type="int" default="0"> <theme_item name="scrollbar_v_separation" data_type="constant" type="int" default="0">
The space between the ScrollContainer's horizontal scroll bar and its content, in pixels. No space will be added when the content's minimum size is larger than the ScrollContainer's size. The space between the ScrollContainer's horizontal scroll bar and its content, in pixels. No space will be added when the content's minimum size is larger than the ScrollContainer's size.
</theme_item> </theme_item>
<theme_item name="scroll_hint_horizontal" data_type="icon" type="Texture2D">
The indicator that will be shown when the content can still be scrolled horizontally. See [member scroll_hint_mode].
</theme_item>
<theme_item name="scroll_hint_vertical" data_type="icon" type="Texture2D">
The indicator that will be shown when the content can still be scrolled vertically. See [member scroll_hint_mode].
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox"> <theme_item name="focus" data_type="style" type="StyleBox">
The focus border [StyleBox] of the [ScrollContainer]. Only used if [member draw_focus_border] is [code]true[/code]. The focus border [StyleBox] of the [ScrollContainer]. Only used if [member draw_focus_border] is [code]true[/code].
</theme_item> </theme_item>

View file

@ -383,6 +383,9 @@
<member name="hide_root" type="bool" setter="set_hide_root" getter="is_root_hidden" default="false"> <member name="hide_root" type="bool" setter="set_hide_root" getter="is_root_hidden" default="false">
If [code]true[/code], the tree's root is hidden. If [code]true[/code], the tree's root is hidden.
</member> </member>
<member name="scroll_hint_mode" type="int" setter="set_scroll_hint_mode" getter="get_scroll_hint_mode" enum="Tree.ScrollHintMode" default="0">
The way which scroll hints (indicators that show that the content can still be scrolled in a certain direction) will be shown.
</member>
<member name="scroll_horizontal_enabled" type="bool" setter="set_h_scroll_enabled" getter="is_h_scroll_enabled" default="true"> <member name="scroll_horizontal_enabled" type="bool" setter="set_h_scroll_enabled" getter="is_h_scroll_enabled" default="true">
If [code]true[/code], enables horizontal scrolling. If [code]true[/code], enables horizontal scrolling.
</member> </member>
@ -392,6 +395,9 @@
<member name="select_mode" type="int" setter="set_select_mode" getter="get_select_mode" enum="Tree.SelectMode" default="0"> <member name="select_mode" type="int" setter="set_select_mode" getter="get_select_mode" enum="Tree.SelectMode" default="0">
Allows single or multiple selection. See the [enum SelectMode] constants. Allows single or multiple selection. See the [enum SelectMode] constants.
</member> </member>
<member name="tile_scroll_hint" type="bool" setter="set_tile_scroll_hint" getter="is_scroll_hint_tiled" default="false">
If [code]true[/code], the scroll hint texture will be tiled instead of stretched. See [member scroll_hint_mode].
</member>
</members> </members>
<signals> <signals>
<signal name="button_clicked"> <signal name="button_clicked">
@ -514,6 +520,18 @@
Enables "above item" and "below item" drop sections. The "above item" drop section covers the top half of the item, and the "below item" drop section covers the bottom half. Enables "above item" and "below item" drop sections. The "above item" drop section covers the top half of the item, and the "below item" drop section covers the bottom half.
When combined with [constant DROP_MODE_ON_ITEM], these drop sections halves the height and stays on top / bottom accordingly. When combined with [constant DROP_MODE_ON_ITEM], these drop sections halves the height and stays on top / bottom accordingly.
</constant> </constant>
<constant name="SCROLL_HINT_MODE_DISABLED" value="0" enum="ScrollHintMode">
Scroll hints will never be shown.
</constant>
<constant name="SCROLL_HINT_MODE_BOTH" value="1" enum="ScrollHintMode">
Scroll hints will be shown at the top and bottom.
</constant>
<constant name="SCROLL_HINT_MODE_TOP" value="2" enum="ScrollHintMode">
Only the top scroll hint will be shown.
</constant>
<constant name="SCROLL_HINT_MODE_BOTTOM" value="3" enum="ScrollHintMode">
Only the bottom scroll hint will be shown.
</constant>
</constants> </constants>
<theme_items> <theme_items>
<theme_item name="children_hl_line_color" data_type="color" type="Color" default="Color(0.27, 0.27, 0.27, 1)"> <theme_item name="children_hl_line_color" data_type="color" type="Color" default="Color(0.27, 0.27, 0.27, 1)">
@ -667,6 +685,9 @@
<theme_item name="indeterminate_disabled" data_type="icon" type="Texture2D"> <theme_item name="indeterminate_disabled" data_type="icon" type="Texture2D">
The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate and non-editable (see [method TreeItem.set_editable]). The check icon to display when the [constant TreeItem.CELL_MODE_CHECK] mode cell is indeterminate and non-editable (see [method TreeItem.set_editable]).
</theme_item> </theme_item>
<theme_item name="scroll_hint" data_type="icon" type="Texture2D">
The indicator that will be shown when the content can still be scrolled. See [member scroll_hint_mode].
</theme_item>
<theme_item name="select_arrow" data_type="icon" type="Texture2D"> <theme_item name="select_arrow" data_type="icon" type="Texture2D">
The arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell. The arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell.
</theme_item> </theme_item>

View file

@ -35,6 +35,7 @@
#include "editor/themes/editor_scale.h" #include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h" #include "editor/themes/editor_theme_manager.h"
#include "scene/gui/graph_edit.h" #include "scene/gui/graph_edit.h"
#include "scene/resources/compressed_texture.h"
#include "scene/resources/dpi_texture.h" #include "scene/resources/dpi_texture.h"
#include "scene/resources/image_texture.h" #include "scene/resources/image_texture.h"
#include "scene/resources/style_box_flat.h" #include "scene/resources/style_box_flat.h"
@ -1576,6 +1577,13 @@ void ThemeClassic::populate_editor_styles(const Ref<EditorTheme> &p_theme, Edito
Ref<StyleBoxFlat> style_widget_scroll_container = p_config.button_style_focus->duplicate(); Ref<StyleBoxFlat> style_widget_scroll_container = p_config.button_style_focus->duplicate();
p_theme->set_stylebox("focus", "ScrollContainer", style_widget_scroll_container); p_theme->set_stylebox("focus", "ScrollContainer", style_widget_scroll_container);
// Hide scroll hints.
Ref<CompressedTexture2D> empty_texture;
empty_texture.instantiate();
p_theme->set_icon("scroll_hint_vertical", "ScrollContainer", empty_texture);
p_theme->set_icon("scroll_hint_horizontal", "ScrollContainer", empty_texture);
p_theme->set_icon("scroll_hint", "Tree", empty_texture);
// This stylebox is used in 3d and 2d viewports (no borders). // This stylebox is used in 3d and 2d viewports (no borders).
Ref<StyleBoxFlat> style_content_panel_vp = p_config.content_panel_style->duplicate(); Ref<StyleBoxFlat> style_content_panel_vp = p_config.content_panel_style->duplicate();
style_content_panel_vp->set_content_margin_individual(p_config.border_width * 2, p_config.base_margin * EDSCALE, p_config.border_width * 2, p_config.border_width * 2); style_content_panel_vp->set_content_margin_individual(p_config.border_width * 2, p_config.base_margin * EDSCALE, p_config.border_width * 2, p_config.border_width * 2);

View file

@ -32,12 +32,13 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "scene/gui/panel_container.h" #include "scene/gui/panel_container.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/window.h" #include "scene/main/window.h"
#include "scene/theme/theme_db.h" #include "scene/theme/theme_db.h"
Size2 ScrollContainer::get_minimum_size() const { Size2 ScrollContainer::get_minimum_size() const {
// Calculated in this function, as it needs to traverse all child controls once to calculate; // Calculated in this function, as it needs to traverse all child controls once to calculate;
// and needs to be calculated before being used by update_scrollbars(). // and needs to be calculated before being used by `_update_scrollbars()`.
largest_child_min_size = Size2(); largest_child_min_size = Size2();
for (int i = 0; i < get_child_count(); i++) { for (int i = 0; i < get_child_count(); i++) {
@ -342,7 +343,8 @@ void ScrollContainer::ensure_control_visible(Control *p_control) {
} }
void ScrollContainer::_reposition_children() { void ScrollContainer::_reposition_children() {
update_scrollbars(); _update_scrollbars();
_update_scroll_hints();
Rect2 margins = _get_margins(); Rect2 margins = _get_margins();
Size2 size = get_size(); Size2 size = get_size();
@ -362,10 +364,7 @@ void ScrollContainer::_reposition_children() {
for (int i = 0; i < get_child_count(); i++) { for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i)); Control *c = as_sortable_control(get_child(i));
if (!c) { if (!c || c->is_internal()) {
continue;
}
if (c == h_scroll || c == v_scroll || c == focus_panel) {
continue; continue;
} }
Size2 minsize = c->get_combined_minimum_size(); Size2 minsize = c->get_combined_minimum_size();
@ -445,7 +444,10 @@ void ScrollContainer::_notification(int p_what) {
DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_set)); DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_SCROLL_OFFSET, callable_mp(this, &ScrollContainer::_accessibility_action_scroll_set));
} break; } break;
case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_THEME_CHANGED: {
_update_scroll_hints();
[[fallthrough]];
}
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: { case NOTIFICATION_TRANSLATION_CHANGED: {
_updating_scrollbars = true; _updating_scrollbars = true;
@ -579,7 +581,7 @@ void ScrollContainer::_notification(int p_what) {
} }
} }
void ScrollContainer::update_scrollbars() { void ScrollContainer::_update_scrollbars() {
Rect2 margins = _get_margins(); Rect2 margins = _get_margins();
Size2 size = get_size(); Size2 size = get_size();
@ -602,6 +604,55 @@ void ScrollContainer::update_scrollbars() {
callable_mp(this, &ScrollContainer::_update_scrollbar_position).call_deferred(); callable_mp(this, &ScrollContainer::_update_scrollbar_position).call_deferred();
} }
void ScrollContainer::_update_scroll_hints() {
Size2 size = get_size();
Rect2 margins = _get_margins();
Size2 scroll_size = size - margins.position + margins.size;
float v_scroll_value = v_scroll->get_value();
bool v_scroll_below_max = v_scroll_value < (largest_child_min_size.height - scroll_size.height - 1);
bool show_vertical_hints = v_scroll_value > 1 || v_scroll_below_max;
float h_scroll_value = h_scroll->get_value();
bool h_scroll_below_max = h_scroll_value < (largest_child_min_size.width - scroll_size.width - 1);
bool show_horizontal_hints = h_scroll_value > 1 || h_scroll_below_max;
bool rtl = is_layout_rtl();
if (show_vertical_hints) {
scroll_hint_top_left->set_texture(theme_cache.scroll_hint_vertical);
scroll_hint_top_left->set_visible(!show_horizontal_hints && (scroll_hint_mode == SCROLL_HINT_MODE_ALL || scroll_hint_mode == SCROLL_HINT_MODE_TOP_AND_LEFT) && v_scroll_value > 1);
scroll_hint_top_left->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, rtl ? -size.x : 0);
scroll_hint_top_left->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, rtl ? 0 : size.x);
scroll_hint_top_left->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
scroll_hint_top_left->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_BEGIN, theme_cache.scroll_hint_vertical->get_height());
scroll_hint_bottom_right->set_flip_h(false);
scroll_hint_bottom_right->set_flip_v(true);
scroll_hint_bottom_right->set_texture(theme_cache.scroll_hint_vertical);
scroll_hint_bottom_right->set_visible(!show_horizontal_hints && (scroll_hint_mode == SCROLL_HINT_MODE_ALL || scroll_hint_mode == SCROLL_HINT_MODE_BOTTOM_AND_RIGHT) && v_scroll_below_max);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, rtl ? -size.x : 0);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, rtl ? 0 : size.x);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -theme_cache.scroll_hint_vertical->get_height());
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
} else {
scroll_hint_top_left->set_texture(theme_cache.scroll_hint_horizontal);
scroll_hint_top_left->set_visible(!show_vertical_hints && (scroll_hint_mode == SCROLL_HINT_MODE_ALL || (rtl ? scroll_hint_mode == SCROLL_HINT_MODE_BOTTOM_AND_RIGHT : scroll_hint_mode == SCROLL_HINT_MODE_TOP_AND_LEFT)) && h_scroll_value > 1);
scroll_hint_top_left->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, rtl ? (size.x - theme_cache.scroll_hint_horizontal->get_width()) : 0);
scroll_hint_top_left->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, rtl ? size.x : theme_cache.scroll_hint_horizontal->get_width());
scroll_hint_top_left->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
scroll_hint_top_left->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
scroll_hint_bottom_right->set_flip_h(true);
scroll_hint_bottom_right->set_flip_v(false);
scroll_hint_bottom_right->set_texture(theme_cache.scroll_hint_horizontal);
scroll_hint_bottom_right->set_visible(!show_vertical_hints && (scroll_hint_mode == SCROLL_HINT_MODE_ALL || (rtl ? scroll_hint_mode == SCROLL_HINT_MODE_TOP_AND_LEFT : scroll_hint_mode == SCROLL_HINT_MODE_BOTTOM_AND_RIGHT)) && h_scroll_below_max);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, rtl ? -size.x : -theme_cache.scroll_hint_horizontal->get_width());
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, rtl ? (-size.x + theme_cache.scroll_hint_horizontal->get_width()) : 0);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
scroll_hint_bottom_right->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
}
}
void ScrollContainer::_scroll_moved(float) { void ScrollContainer::_scroll_moved(float) {
queue_sort(); queue_sort();
} }
@ -676,6 +727,34 @@ void ScrollContainer::set_deadzone(int p_deadzone) {
deadzone = p_deadzone; deadzone = p_deadzone;
} }
void ScrollContainer::set_scroll_hint_mode(ScrollHintMode p_mode) {
if (scroll_hint_mode == p_mode) {
return;
}
scroll_hint_mode = p_mode;
_update_scroll_hints();
}
ScrollContainer::ScrollHintMode ScrollContainer::get_scroll_hint_mode() const {
return scroll_hint_mode;
}
void ScrollContainer::set_tile_scroll_hint(bool p_enable) {
if (tile_scroll_hint == p_enable) {
return;
}
scroll_hint_top_left->set_stretch_mode(p_enable ? TextureRect::STRETCH_TILE : TextureRect::STRETCH_SCALE);
scroll_hint_bottom_right->set_stretch_mode(p_enable ? TextureRect::STRETCH_TILE : TextureRect::STRETCH_SCALE);
tile_scroll_hint = p_enable;
}
bool ScrollContainer::is_scroll_hint_tiled() {
return tile_scroll_hint;
}
bool ScrollContainer::is_following_focus() const { bool ScrollContainer::is_following_focus() const {
return follow_focus; return follow_focus;
} }
@ -691,10 +770,7 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const {
for (int i = 0; i < get_child_count(); i++) { for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisibilityMode::VISIBLE); Control *c = as_sortable_control(get_child(i), SortableVisibilityMode::VISIBLE);
if (!c) { if (!c || c->is_internal()) {
continue;
}
if (c == h_scroll || c == v_scroll || c == focus_panel) {
continue; continue;
} }
@ -742,6 +818,12 @@ void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone);
ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone);
ClassDB::bind_method(D_METHOD("set_scroll_hint_mode", "scroll_hint_mode"), &ScrollContainer::set_scroll_hint_mode);
ClassDB::bind_method(D_METHOD("get_scroll_hint_mode"), &ScrollContainer::get_scroll_hint_mode);
ClassDB::bind_method(D_METHOD("set_tile_scroll_hint", "tile_scroll_hint"), &ScrollContainer::set_tile_scroll_hint);
ClassDB::bind_method(D_METHOD("is_scroll_hint_tiled"), &ScrollContainer::is_scroll_hint_tiled);
ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus); ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus);
ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus); ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus);
@ -758,7 +840,7 @@ void ScrollContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus"); 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::BOOL, "draw_focus_border"), "set_draw_focus_border", "get_draw_focus_border");
ADD_GROUP("Scroll", "scroll_"); ADD_GROUP("Scrollbar", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_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"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_horizontal_custom_step", "get_horizontal_custom_step"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_horizontal_custom_step", "get_horizontal_custom_step");
@ -767,18 +849,30 @@ void ScrollContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show,Reserve"), "set_vertical_scroll_mode", "get_vertical_scroll_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show,Reserve"), "set_vertical_scroll_mode", "get_vertical_scroll_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone");
ADD_GROUP("Scroll Hint", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_hint_mode", PROPERTY_HINT_ENUM, "Disabled,All,Top and Left,Bottom and Right"), "set_scroll_hint_mode", "get_scroll_hint_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tile_scroll_hint"), "set_tile_scroll_hint", "is_scroll_hint_tiled");
BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED); BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED);
BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO); BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO);
BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS); BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS);
BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER); BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER);
BIND_ENUM_CONSTANT(SCROLL_MODE_RESERVE); BIND_ENUM_CONSTANT(SCROLL_MODE_RESERVE);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_DISABLED);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_ALL);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_TOP_AND_LEFT);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_BOTTOM_AND_RIGHT);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ScrollContainer, scrollbar_h_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ScrollContainer, scrollbar_h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ScrollContainer, scrollbar_v_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ScrollContainer, scrollbar_v_separation);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, focus_style, "focus"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, focus_style, "focus");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ScrollContainer, scroll_hint_vertical);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, ScrollContainer, scroll_hint_horizontal);
GLOBAL_DEF("gui/common/default_scroll_deadzone", 0); GLOBAL_DEF("gui/common/default_scroll_deadzone", 0);
} }
@ -802,6 +896,18 @@ bool ScrollContainer::child_has_focus() {
} }
ScrollContainer::ScrollContainer() { ScrollContainer::ScrollContainer() {
scroll_hint_top_left = memnew(TextureRect);
scroll_hint_top_left->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
scroll_hint_top_left->set_mouse_filter(MOUSE_FILTER_IGNORE);
scroll_hint_top_left->hide();
add_child(scroll_hint_top_left, false, INTERNAL_MODE_BACK);
scroll_hint_bottom_right = memnew(TextureRect);
scroll_hint_bottom_right->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
scroll_hint_bottom_right->set_mouse_filter(MOUSE_FILTER_IGNORE);
scroll_hint_bottom_right->hide();
add_child(scroll_hint_bottom_right, false, INTERNAL_MODE_BACK);
h_scroll = memnew(HScrollBar); h_scroll = memnew(HScrollBar);
h_scroll->set_name("_h_scroll"); h_scroll->set_name("_h_scroll");
add_child(h_scroll, false, INTERNAL_MODE_BACK); add_child(h_scroll, false, INTERNAL_MODE_BACK);

View file

@ -35,6 +35,7 @@
#include "scroll_bar.h" #include "scroll_bar.h"
class PanelContainer; class PanelContainer;
class TextureRect;
class ScrollContainer : public Container { class ScrollContainer : public Container {
GDCLASS(ScrollContainer, Container); GDCLASS(ScrollContainer, Container);
@ -48,6 +49,13 @@ public:
SCROLL_MODE_RESERVE, SCROLL_MODE_RESERVE,
}; };
enum ScrollHintMode {
SCROLL_HINT_MODE_DISABLED,
SCROLL_HINT_MODE_ALL,
SCROLL_HINT_MODE_TOP_AND_LEFT,
SCROLL_HINT_MODE_BOTTOM_AND_RIGHT,
};
private: private:
HScrollBar *h_scroll = nullptr; HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr; VScrollBar *v_scroll = nullptr;
@ -55,7 +63,7 @@ private:
mutable Size2 largest_child_min_size; // The largest one among the min sizes of all available child controls. mutable Size2 largest_child_min_size; // The largest one among the min sizes of all available child controls.
void update_scrollbars(); void _update_scrollbars();
Vector2 drag_speed; Vector2 drag_speed;
Vector2 drag_accum; Vector2 drag_accum;
@ -67,18 +75,29 @@ private:
bool beyond_deadzone = false; bool beyond_deadzone = false;
bool scroll_on_drag_hover = false; bool scroll_on_drag_hover = false;
TextureRect *scroll_hint_top_left = nullptr;
TextureRect *scroll_hint_bottom_right = nullptr;
ScrollMode horizontal_scroll_mode = SCROLL_MODE_AUTO; ScrollMode horizontal_scroll_mode = SCROLL_MODE_AUTO;
ScrollMode vertical_scroll_mode = SCROLL_MODE_AUTO; ScrollMode vertical_scroll_mode = SCROLL_MODE_AUTO;
void _update_scroll_hints();
int deadzone = 0; int deadzone = 0;
bool follow_focus = false; bool follow_focus = false;
int scroll_border = 20; int scroll_border = 20;
int scroll_speed = 12; int scroll_speed = 12;
ScrollHintMode scroll_hint_mode = SCROLL_HINT_MODE_DISABLED;
bool tile_scroll_hint = false;
struct ThemeCache { struct ThemeCache {
Ref<StyleBox> panel_style; Ref<StyleBox> panel_style;
Ref<StyleBox> focus_style; Ref<StyleBox> focus_style;
Ref<Texture2D> scroll_hint_vertical;
Ref<Texture2D> scroll_hint_horizontal;
int scrollbar_h_separation = 0; int scrollbar_h_separation = 0;
int scrollbar_v_separation = 0; int scrollbar_v_separation = 0;
} theme_cache; } theme_cache;
@ -134,8 +153,14 @@ public:
void set_vertical_scroll_mode(ScrollMode p_mode); void set_vertical_scroll_mode(ScrollMode p_mode);
ScrollMode get_vertical_scroll_mode() const; ScrollMode get_vertical_scroll_mode() const;
int get_deadzone() const;
void set_deadzone(int p_deadzone); void set_deadzone(int p_deadzone);
int get_deadzone() const;
void set_scroll_hint_mode(ScrollHintMode p_mode);
ScrollHintMode get_scroll_hint_mode() const;
void set_tile_scroll_hint(bool p_enable);
bool is_scroll_hint_tiled();
bool is_following_focus() const; bool is_following_focus() const;
void set_follow_focus(bool p_follow); void set_follow_focus(bool p_follow);
@ -155,3 +180,4 @@ public:
}; };
VARIANT_ENUM_CAST(ScrollContainer::ScrollMode); VARIANT_ENUM_CAST(ScrollContainer::ScrollMode);
VARIANT_ENUM_CAST(ScrollContainer::ScrollHintMode);

View file

@ -5127,6 +5127,21 @@ void Tree::_notification(int p_what) {
} }
} }
if (scroll_hint_mode != SCROLL_HINT_MODE_DISABLED) {
Size2 size = get_size();
float v_scroll_value = v_scroll->get_value();
bool v_scroll_below_max = v_scroll_value < (get_internal_min_size().height - size.height - 1);
if (v_scroll_value > 1 || v_scroll_below_max) {
int hint_height = theme_cache.scroll_hint->get_height();
if ((scroll_hint_mode == SCROLL_HINT_MODE_BOTH || scroll_hint_mode == SCROLL_HINT_MODE_TOP) && v_scroll_value > 1) {
draw_texture_rect(theme_cache.scroll_hint, Rect2(Point2(), Size2(size.width, hint_height)), tile_scroll_hint);
}
if ((scroll_hint_mode == SCROLL_HINT_MODE_BOTH || scroll_hint_mode == SCROLL_HINT_MODE_BOTTOM) && v_scroll_below_max) {
draw_texture_rect(theme_cache.scroll_hint, Rect2(Point2(0, size.height - hint_height), Size2(size.width, -hint_height)), tile_scroll_hint);
}
}
}
// Draw the focus outline last, so that it is drawn in front of the section headings. // Draw the focus outline last, so that it is drawn in front of the section headings.
// Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
if (has_focus(true)) { if (has_focus(true)) {
@ -5995,6 +6010,32 @@ bool Tree::is_v_scroll_enabled() const {
return v_scroll_enabled; return v_scroll_enabled;
} }
void Tree::set_scroll_hint_mode(ScrollHintMode p_mode) {
if (scroll_hint_mode == p_mode) {
return;
}
scroll_hint_mode = p_mode;
queue_redraw();
}
Tree::ScrollHintMode Tree::get_scroll_hint_mode() const {
return scroll_hint_mode;
}
void Tree::set_tile_scroll_hint(bool p_enable) {
if (tile_scroll_hint == p_enable) {
return;
}
tile_scroll_hint = p_enable;
queue_redraw();
}
bool Tree::is_scroll_hint_tiled() {
return tile_scroll_hint;
}
TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) { TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {
TreeItem *from = p_at; TreeItem *from = p_at;
TreeItem *loop = nullptr; // Safe-guard against infinite loop. TreeItem *loop = nullptr; // Safe-guard against infinite loop.
@ -6656,6 +6697,12 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_v_scroll_enabled", "h_scroll"), &Tree::set_v_scroll_enabled); ClassDB::bind_method(D_METHOD("set_v_scroll_enabled", "h_scroll"), &Tree::set_v_scroll_enabled);
ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &Tree::is_v_scroll_enabled); ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &Tree::is_v_scroll_enabled);
ClassDB::bind_method(D_METHOD("set_scroll_hint_mode", "scroll_hint_mode"), &Tree::set_scroll_hint_mode);
ClassDB::bind_method(D_METHOD("get_scroll_hint_mode"), &Tree::get_scroll_hint_mode);
ClassDB::bind_method(D_METHOD("set_tile_scroll_hint", "tile_scroll_hint"), &Tree::set_tile_scroll_hint);
ClassDB::bind_method(D_METHOD("is_scroll_hint_tiled"), &Tree::is_scroll_hint_tiled);
ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding); ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding);
ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden); ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden);
@ -6691,9 +6738,12 @@ void Tree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");
ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_tooltip"), "set_auto_tooltip", "is_auto_tooltip_enabled");
ADD_GROUP("Scroll", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_h_scroll_enabled", "is_h_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_h_scroll_enabled", "is_h_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_v_scroll_enabled", "is_v_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_v_scroll_enabled", "is_v_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_tooltip"), "set_auto_tooltip", "is_auto_tooltip_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_hint_mode", PROPERTY_HINT_ENUM, "Disabled,Both,Top,Bottom"), "set_scroll_hint_mode", "get_scroll_hint_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tile_scroll_hint"), "set_tile_scroll_hint", "is_scroll_hint_tiled");
ADD_SIGNAL(MethodInfo("item_selected")); ADD_SIGNAL(MethodInfo("item_selected"));
ADD_SIGNAL(MethodInfo("cell_selected")); ADD_SIGNAL(MethodInfo("cell_selected"));
@ -6719,6 +6769,11 @@ void Tree::_bind_methods() {
BIND_ENUM_CONSTANT(DROP_MODE_ON_ITEM); BIND_ENUM_CONSTANT(DROP_MODE_ON_ITEM);
BIND_ENUM_CONSTANT(DROP_MODE_INBETWEEN); BIND_ENUM_CONSTANT(DROP_MODE_INBETWEEN);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_DISABLED);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_BOTH);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_TOP);
BIND_ENUM_CONSTANT(SCROLL_HINT_MODE_BOTTOM);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, panel_style, "panel"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, focus_style, "focus"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, focus_style, "focus");
@ -6749,6 +6804,7 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, arrow_collapsed_mirrored); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, arrow_collapsed_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, select_arrow); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, select_arrow);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, updown); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, updown);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, scroll_hint);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, custom_button); BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, custom_button);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, custom_button_hover); BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, custom_button_hover);

View file

@ -466,6 +466,13 @@ public:
DROP_MODE_INBETWEEN = 2 DROP_MODE_INBETWEEN = 2
}; };
enum ScrollHintMode {
SCROLL_HINT_MODE_DISABLED,
SCROLL_HINT_MODE_BOTH,
SCROLL_HINT_MODE_TOP,
SCROLL_HINT_MODE_BOTTOM,
};
private: private:
friend class TreeItem; friend class TreeItem;
@ -622,6 +629,7 @@ private:
Ref<Texture2D> arrow_collapsed_mirrored; Ref<Texture2D> arrow_collapsed_mirrored;
Ref<Texture2D> select_arrow; Ref<Texture2D> select_arrow;
Ref<Texture2D> updown; Ref<Texture2D> updown;
Ref<Texture2D> scroll_hint;
Color font_color; Color font_color;
Color font_hovered_color; Color font_hovered_color;
@ -738,6 +746,9 @@ private:
bool allow_rmb_select = false; bool allow_rmb_select = false;
bool scrolling = false; bool scrolling = false;
ScrollHintMode scroll_hint_mode = SCROLL_HINT_MODE_DISABLED;
bool tile_scroll_hint = false;
bool allow_reselect = false; bool allow_reselect = false;
bool allow_search = true; bool allow_search = true;
@ -893,6 +904,12 @@ public:
void set_v_scroll_enabled(bool p_enable); void set_v_scroll_enabled(bool p_enable);
bool is_v_scroll_enabled() const; bool is_v_scroll_enabled() const;
void set_scroll_hint_mode(ScrollHintMode p_mode);
ScrollHintMode get_scroll_hint_mode() const;
void set_tile_scroll_hint(bool p_enable);
bool is_scroll_hint_tiled();
void set_cursor_can_exit_tree(bool p_enable); void set_cursor_can_exit_tree(bool p_enable);
VScrollBar *get_vscroll_bar() { return v_scroll; } VScrollBar *get_vscroll_bar() { return v_scroll; }
@ -932,3 +949,4 @@ public:
VARIANT_ENUM_CAST(Tree::SelectMode); VARIANT_ENUM_CAST(Tree::SelectMode);
VARIANT_ENUM_CAST(Tree::DropModeFlags); VARIANT_ENUM_CAST(Tree::DropModeFlags);
VARIANT_ENUM_CAST(Tree::ScrollHintMode);

View file

@ -664,6 +664,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
focus_style->set_border_color(style_focus_color); focus_style->set_border_color(style_focus_color);
theme->set_stylebox("focus", "ScrollContainer", focus_style); theme->set_stylebox("focus", "ScrollContainer", focus_style);
theme->set_icon("scroll_hint_vertical", "ScrollContainer", icons["scroll_hint_vertical"]);
theme->set_icon("scroll_hint_horizontal", "ScrollContainer", icons["scroll_hint_horizontal"]);
// Window // Window
theme->set_stylebox("embedded_border", "Window", sb_expand(make_flat_stylebox(style_popup_color, 10, 28, 10, 8), 8, 32, 8, 6)); theme->set_stylebox("embedded_border", "Window", sb_expand(make_flat_stylebox(style_popup_color, 10, 28, 10, 8), 8, 32, 8, 6));
@ -883,6 +886,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("arrow", "Tree", icons["arrow_down"]); theme->set_icon("arrow", "Tree", icons["arrow_down"]);
theme->set_icon("arrow_collapsed", "Tree", icons["arrow_right"]); theme->set_icon("arrow_collapsed", "Tree", icons["arrow_right"]);
theme->set_icon("arrow_collapsed_mirrored", "Tree", icons["arrow_left"]); theme->set_icon("arrow_collapsed_mirrored", "Tree", icons["arrow_left"]);
theme->set_icon("scroll_hint", "Tree", icons["scroll_hint_vertical"]);
theme->set_font("title_button_font", "Tree", Ref<Font>()); theme->set_font("title_button_font", "Tree", Ref<Font>());
theme->set_font(SceneStringName(font), "Tree", Ref<Font>()); theme->set_font(SceneStringName(font), "Tree", Ref<Font>());

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="32" viewBox="0 0 6.35 8.467"><defs><linearGradient id="a"><stop offset="0" stop-opacity=".3"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="4.233" x2="4.233" y1="0" y2="6.35" gradientTransform="translate(-8.467)" gradientUnits="userSpaceOnUse"/></defs><path fill="url(#b)" d="M-8.467 0H0V6.35H-8.467z" paint-order="fill markers stroke" transform="rotate(-90)"/></svg>

After

Width:  |  Height:  |  Size: 519 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="24" viewBox="0 0 8.467 6.35"><defs><linearGradient id="a"><stop offset="0" stop-opacity=".3"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="4.233" x2="4.233" y1="0" y2="6.35" gradientUnits="userSpaceOnUse"/></defs><path fill="url(#b)" d="M0 0H8.467V6.35H0z" paint-order="fill markers stroke"/></svg>

After

Width:  |  Height:  |  Size: 451 B