From a9ce6cb178e74a4a7e067674cd0c40a5c3276fff Mon Sep 17 00:00:00 2001
From: Face <69168154+face-hh@users.noreply.github.com>
Date: Wed, 23 Jul 2025 15:23:32 +0300
Subject: [PATCH] input (text/checkbox), form, button
---
Assets/Icons/checkbox.svg | 1 +
Assets/Icons/checkbox.svg.import | 37 +
Assets/Icons/checkbox_disabled.svg | 1 +
Assets/Icons/checkbox_disabled.svg.import | 37 +
Assets/Icons/checkbox_pressed.svg | 1 +
Assets/Icons/checkbox_pressed.svg.import | 37 +
Assets/Icons/checkbox_pressed_grayscale.svg | 1 +
.../checkbox_pressed_grayscale.svg.import | 37 +
README.md | 1 +
Scenes/Styles/BrowserText.tres | 65 +-
Scenes/Tags/button.tscn | 20 +
Scenes/Tags/form.tscn | 10 +
Scenes/Tags/input.tscn | 34 +
Scenes/Tags/span.tscn | 3 +-
Scenes/main.tscn | 50 +-
Scripts/B9/HTMLParser.gd | 3 -
Scripts/TabContainer.gd | 2 +-
Scripts/Tags/br.gd | 2 +-
Scripts/Tags/button.gd | 18 +
Scripts/Tags/button.gd.uid | 1 +
Scripts/Tags/form.gd | 4 +
Scripts/Tags/form.gd.uid | 1 +
Scripts/Tags/input.gd | 23 +
Scripts/Tags/input.gd.uid | 1 +
Scripts/main.gd | 56 ++
addons/SmoothScroll/SmoothScrollContainer.gd | 916 ++++++++++++++++++
.../SmoothScroll/SmoothScrollContainer.gd.uid | 1 +
addons/SmoothScroll/class-icon.svg | 19 +
addons/SmoothScroll/class-icon.svg.import | 37 +
addons/SmoothScroll/debug_font.tres | 3 +
addons/SmoothScroll/icon.afdesign | Bin 0 -> 49347 bytes
addons/SmoothScroll/plugin.cfg | 8 +
addons/SmoothScroll/plugin.gd | 12 +
addons/SmoothScroll/plugin.gd.uid | 1 +
.../scroll_damper/cubic_scroll_damper.gd | 33 +
.../scroll_damper/cubic_scroll_damper.gd.uid | 1 +
.../scroll_damper/expo_scroll_damper.gd | 47 +
.../scroll_damper/expo_scroll_damper.gd.uid | 1 +
.../SmoothScroll/scroll_damper/icon.afdesign | Bin 0 -> 36905 bytes
addons/SmoothScroll/scroll_damper/icon.svg | 29 +
.../scroll_damper/icon.svg.import | 37 +
.../scroll_damper/linear_scroll_damper.gd | 33 +
.../scroll_damper/linear_scroll_damper.gd.uid | 1 +
.../scroll_damper/quad_scroll_damper.gd | 33 +
.../scroll_damper/quad_scroll_damper.gd.uid | 1 +
.../scroll_damper/scroll_damper.gd | 74 ++
.../scroll_damper/scroll_damper.gd.uid | 1 +
project.godot | 4 +
48 files changed, 1720 insertions(+), 18 deletions(-)
create mode 100644 Assets/Icons/checkbox.svg
create mode 100644 Assets/Icons/checkbox.svg.import
create mode 100644 Assets/Icons/checkbox_disabled.svg
create mode 100644 Assets/Icons/checkbox_disabled.svg.import
create mode 100644 Assets/Icons/checkbox_pressed.svg
create mode 100644 Assets/Icons/checkbox_pressed.svg.import
create mode 100644 Assets/Icons/checkbox_pressed_grayscale.svg
create mode 100644 Assets/Icons/checkbox_pressed_grayscale.svg.import
create mode 100644 Scenes/Tags/button.tscn
create mode 100644 Scenes/Tags/form.tscn
create mode 100644 Scenes/Tags/input.tscn
create mode 100644 Scripts/Tags/button.gd
create mode 100644 Scripts/Tags/button.gd.uid
create mode 100644 Scripts/Tags/form.gd
create mode 100644 Scripts/Tags/form.gd.uid
create mode 100644 Scripts/Tags/input.gd
create mode 100644 Scripts/Tags/input.gd.uid
create mode 100644 addons/SmoothScroll/SmoothScrollContainer.gd
create mode 100644 addons/SmoothScroll/SmoothScrollContainer.gd.uid
create mode 100644 addons/SmoothScroll/class-icon.svg
create mode 100644 addons/SmoothScroll/class-icon.svg.import
create mode 100644 addons/SmoothScroll/debug_font.tres
create mode 100644 addons/SmoothScroll/icon.afdesign
create mode 100644 addons/SmoothScroll/plugin.cfg
create mode 100644 addons/SmoothScroll/plugin.gd
create mode 100644 addons/SmoothScroll/plugin.gd.uid
create mode 100644 addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd
create mode 100644 addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd.uid
create mode 100644 addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd
create mode 100644 addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd.uid
create mode 100644 addons/SmoothScroll/scroll_damper/icon.afdesign
create mode 100644 addons/SmoothScroll/scroll_damper/icon.svg
create mode 100644 addons/SmoothScroll/scroll_damper/icon.svg.import
create mode 100644 addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd
create mode 100644 addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd.uid
create mode 100644 addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd
create mode 100644 addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd.uid
create mode 100644 addons/SmoothScroll/scroll_damper/scroll_damper.gd
create mode 100644 addons/SmoothScroll/scroll_damper/scroll_damper.gd.uid
diff --git a/Assets/Icons/checkbox.svg b/Assets/Icons/checkbox.svg
new file mode 100644
index 0000000..7a84b76
--- /dev/null
+++ b/Assets/Icons/checkbox.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Assets/Icons/checkbox.svg.import b/Assets/Icons/checkbox.svg.import
new file mode 100644
index 0000000..3955cd8
--- /dev/null
+++ b/Assets/Icons/checkbox.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dn4dxn8hkrd64"
+path="res://.godot/imported/checkbox.svg-b131741b9ad567d1cf024db4c0f11166.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Icons/checkbox.svg"
+dest_files=["res://.godot/imported/checkbox.svg-b131741b9ad567d1cf024db4c0f11166.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Assets/Icons/checkbox_disabled.svg b/Assets/Icons/checkbox_disabled.svg
new file mode 100644
index 0000000..6950b25
--- /dev/null
+++ b/Assets/Icons/checkbox_disabled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Assets/Icons/checkbox_disabled.svg.import b/Assets/Icons/checkbox_disabled.svg.import
new file mode 100644
index 0000000..8bcef9c
--- /dev/null
+++ b/Assets/Icons/checkbox_disabled.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bap17ryrkcyey"
+path="res://.godot/imported/checkbox_disabled.svg-d293c35e32232bb898ac1dc4c01f3d34.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Icons/checkbox_disabled.svg"
+dest_files=["res://.godot/imported/checkbox_disabled.svg-d293c35e32232bb898ac1dc4c01f3d34.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Assets/Icons/checkbox_pressed.svg b/Assets/Icons/checkbox_pressed.svg
new file mode 100644
index 0000000..b0f6aed
--- /dev/null
+++ b/Assets/Icons/checkbox_pressed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Assets/Icons/checkbox_pressed.svg.import b/Assets/Icons/checkbox_pressed.svg.import
new file mode 100644
index 0000000..395100b
--- /dev/null
+++ b/Assets/Icons/checkbox_pressed.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5scqw224r2e5"
+path="res://.godot/imported/checkbox_pressed.svg-7864c501a8fa0fa8194946d708755b56.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Icons/checkbox_pressed.svg"
+dest_files=["res://.godot/imported/checkbox_pressed.svg-7864c501a8fa0fa8194946d708755b56.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Assets/Icons/checkbox_pressed_grayscale.svg b/Assets/Icons/checkbox_pressed_grayscale.svg
new file mode 100644
index 0000000..c33cd38
--- /dev/null
+++ b/Assets/Icons/checkbox_pressed_grayscale.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Assets/Icons/checkbox_pressed_grayscale.svg.import b/Assets/Icons/checkbox_pressed_grayscale.svg.import
new file mode 100644
index 0000000..8313616
--- /dev/null
+++ b/Assets/Icons/checkbox_pressed_grayscale.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6ucuyluuw43"
+path="res://.godot/imported/checkbox_pressed_grayscale.svg-45c89d5e4123fde397a9e7b9084c1a59.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Icons/checkbox_pressed_grayscale.svg"
+dest_files=["res://.godot/imported/checkbox_pressed_grayscale.svg-45c89d5e4123fde397a9e7b9084c1a59.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/README.md b/README.md
index a6c9928..e8ce9e3 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ TODO:
5. **Store** tab containers so switching tabs won't erase previous tab.
6. **GIF** support
7. **Video** support via [GDE GoZen](https://github.com/VoylinsGamedevJourney/gde_gozen)
+8. **More input types** (password, email, number, etc.)
Issues:
1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing.
\ No newline at end of file
diff --git a/Scenes/Styles/BrowserText.tres b/Scenes/Styles/BrowserText.tres
index 6f49502..5a24c0f 100644
--- a/Scenes/Styles/BrowserText.tres
+++ b/Scenes/Styles/BrowserText.tres
@@ -1,4 +1,53 @@
-[gd_resource type="Theme" load_steps=7 format=3 uid="uid://bn6rbmdy60lhr"]
+[gd_resource type="Theme" load_steps=17 format=3 uid="uid://bn6rbmdy60lhr"]
+
+[ext_resource type="Texture2D" uid="uid://dn4dxn8hkrd64" path="res://Assets/Icons/checkbox.svg" id="1_75mhk"]
+[ext_resource type="Texture2D" uid="uid://b6ucuyluuw43" path="res://Assets/Icons/checkbox_pressed_grayscale.svg" id="2_2abar"]
+[ext_resource type="Texture2D" uid="uid://c5scqw224r2e5" path="res://Assets/Icons/checkbox_pressed.svg" id="2_3k2jb"]
+[ext_resource type="Texture2D" uid="uid://bap17ryrkcyey" path="res://Assets/Icons/checkbox_disabled.svg" id="4_c32on"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_c32on"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7g0pl"]
+bg_color = Color(0.168627, 0.168627, 0.168627, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c32on"]
+bg_color = Color(0.105882, 0.105882, 0.105882, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_75mhk"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jecr6"]
+content_margin_left = 5.0
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_75mhk"]
+content_margin_left = 5.0
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
[sub_resource type="SystemFont" id="SystemFont_jecr6"]
font_names = PackedStringArray("Serif")
@@ -23,6 +72,20 @@ font_names = PackedStringArray("Serif")
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jecr6"]
[resource]
+Button/styles/focus = SubResource("StyleBoxEmpty_c32on")
+Button/styles/hover = SubResource("StyleBoxFlat_7g0pl")
+Button/styles/normal = SubResource("StyleBoxFlat_c32on")
+CheckBox/icons/checked = ExtResource("2_3k2jb")
+CheckBox/icons/checked_disabled = ExtResource("2_2abar")
+CheckBox/icons/unchecked = ExtResource("1_75mhk")
+CheckBox/icons/unchecked_disabled = ExtResource("4_c32on")
+CheckBox/styles/focus = SubResource("StyleBoxEmpty_75mhk")
+LineEdit/colors/caret_color = Color(0, 0, 0, 1)
+LineEdit/colors/font_color = Color(0, 0, 0, 1)
+LineEdit/colors/font_placeholder_color = Color(0, 0, 0, 0.6)
+LineEdit/styles/focus = SubResource("StyleBoxFlat_jecr6")
+LineEdit/styles/normal = SubResource("StyleBoxFlat_75mhk")
+LineEdit/styles/read_only = null
RichTextLabel/colors/font_selected_color = Color(1, 1, 1, 1)
RichTextLabel/colors/selection_color = Color(0.313726, 0.403922, 0.8, 1)
RichTextLabel/fonts/bold_font = SubResource("SystemFont_jecr6")
diff --git a/Scenes/Tags/button.tscn b/Scenes/Tags/button.tscn
new file mode 100644
index 0000000..bc1656d
--- /dev/null
+++ b/Scenes/Tags/button.tscn
@@ -0,0 +1,20 @@
+[gd_scene load_steps=3 format=3 uid="uid://dhqqt4hg21w8d"]
+
+[ext_resource type="Script" uid="uid://cks35eudcm1wj" path="res://Scripts/Tags/button.gd" id="1_button"]
+[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"]
+
+[node name="Button" type="Control"]
+custom_minimum_size = Vector2(100, 30)
+layout_mode = 3
+anchors_preset = 0
+script = ExtResource("1_button")
+
+[node name="ButtonNode" type="Button" parent="."]
+layout_mode = 1
+offset_right = 100.0
+offset_bottom = 30.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_default_cursor_shape = 2
+theme = ExtResource("2_theme")
+text = "Button"
diff --git a/Scenes/Tags/form.tscn b/Scenes/Tags/form.tscn
new file mode 100644
index 0000000..f8a08ce
--- /dev/null
+++ b/Scenes/Tags/form.tscn
@@ -0,0 +1,10 @@
+[gd_scene load_steps=2 format=3 uid="uid://bw8h5k6j2m3n4"]
+
+[ext_resource type="Script" uid="uid://cn2iolk6biupv" path="res://Scripts/Tags/form.gd" id="1_form"]
+
+[node name="Form" type="VBoxContainer"]
+anchors_preset = 10
+anchor_right = 1.0
+grow_horizontal = 2
+theme_override_constants/separation = 22
+script = ExtResource("1_form")
diff --git a/Scenes/Tags/input.tscn b/Scenes/Tags/input.tscn
new file mode 100644
index 0000000..d15529c
--- /dev/null
+++ b/Scenes/Tags/input.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=3 format=3 uid="uid://c7yay102a3b4c"]
+
+[ext_resource type="Script" uid="uid://kv6ebscarj2e" path="res://Scripts/Tags/input.gd" id="1_input"]
+[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"]
+
+[node name="Input" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = 400.0
+offset_bottom = 31.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+script = ExtResource("1_input")
+
+[node name="LineEdit" type="LineEdit" parent="."]
+layout_mode = 1
+offset_right = 400.0
+offset_bottom = 35.0
+theme = ExtResource("2_theme")
+placeholder_text = "Enter text..."
+caret_blink = true
+
+[node name="CheckBox" type="CheckBox" parent="."]
+visible = false
+layout_mode = 0
+offset_right = 31.0
+offset_bottom = 31.0
+theme = ExtResource("2_theme")
+theme_override_constants/icon_max_width = 24
+flat = true
diff --git a/Scenes/Tags/span.tscn b/Scenes/Tags/span.tscn
index 259b813..5973bf5 100644
--- a/Scenes/Tags/span.tscn
+++ b/Scenes/Tags/span.tscn
@@ -1,6 +1,6 @@
[gd_scene load_steps=3 format=3 uid="uid://bkj3x5y2m8qrl"]
-[ext_resource type="Script" uid="uid://cjk4x6y8m9wts" path="res://Scripts/Tags/span.gd" id="1_span"]
+[ext_resource type="Script" uid="uid://4pbphta3r67k" path="res://Scripts/Tags/span.gd" id="1_span"]
[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"]
[node name="SPAN" type="VBoxContainer"]
@@ -17,5 +17,6 @@ mouse_default_cursor_shape = 1
theme = ExtResource("2_theme")
theme_override_colors/default_color = Color(0, 0, 0, 1)
bbcode_enabled = true
+text = "Placeholder"
fit_content = true
selection_enabled = true
diff --git a/Scenes/main.tscn b/Scenes/main.tscn
index 58a0eb5..0eec7bc 100644
--- a/Scenes/main.tscn
+++ b/Scenes/main.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=23 format=3 uid="uid://bytm7bt2s4ak8"]
+[gd_scene load_steps=27 format=3 uid="uid://bytm7bt2s4ak8"]
[ext_resource type="Script" uid="uid://bg5iqnwic1rio" path="res://Scripts/main.gd" id="1_8q3xr"]
[ext_resource type="Texture2D" uid="uid://df1m4j7uxi63v" path="res://Assets/Icons/chevron-down.svg" id="2_6bp64"]
@@ -9,6 +9,8 @@
[ext_resource type="PackedScene" uid="uid://sqhcxhcre081" path="res://Scenes/Tab.tscn" id="4_344ge"]
[ext_resource type="Texture2D" uid="uid://cu4hjoba6etf" path="res://Assets/Icons/rotate-cw.svg" id="5_344ge"]
[ext_resource type="Texture2D" uid="uid://cehbtwq6gq0cn" path="res://Assets/Icons/plus.svg" id="5_ynf5e"]
+[ext_resource type="Script" uid="uid://bgqglerkcylxx" path="res://addons/SmoothScroll/SmoothScrollContainer.gd" id="10_d1ilt"]
+[ext_resource type="Script" uid="uid://b7h0k2h2qwlqv" path="res://addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd" id="11_6iyac"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_344ge"]
@@ -61,6 +63,18 @@ expand_margin_left = 40.0
LineEdit/styles/focus = SubResource("StyleBoxEmpty_8gbba")
LineEdit/styles/normal = SubResource("StyleBoxFlat_8gbba")
+[sub_resource type="Resource" id="Resource_fdnlq"]
+script = ExtResource("11_6iyac")
+friction = 4.0
+minimum_velocity = 0.4
+rebound_strength = 7.0
+
+[sub_resource type="Resource" id="Resource_jkdf5"]
+script = ExtResource("11_6iyac")
+friction = 4.0
+minimum_velocity = 0.4
+rebound_strength = 7.0
+
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_white"]
bg_color = Color(1, 1, 1, 1)
@@ -190,14 +204,22 @@ stretch_mode = 5
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
-[node name="WebsiteContainer" type="VBoxContainer" parent="VBoxContainer"]
-unique_name_in_owner = true
+[node name="SmoothScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
-theme_override_constants/separation = 22
+script = ExtResource("10_d1ilt")
+wheel_scroll_damper = SubResource("Resource_fdnlq")
+dragging_scroll_damper = SubResource("Resource_jkdf5")
+drag_with_mouse = false
+allow_overdragging = false
+metadata/_custom_type_script = "uid://bgqglerkcylxx"
-[node name="Control" type="Control" parent="VBoxContainer/WebsiteContainer"]
+[node name="WebsiteContainer" type="VBoxContainer" parent="VBoxContainer/SmoothScrollContainer"]
+unique_name_in_owner = true
layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 22
[node name="WebsiteBackground" type="Panel" parent="."]
z_index = -1
@@ -213,18 +235,24 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_white")
[node name="Panel" type="Panel" parent="."]
z_index = -5
-layout_mode = 2
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
offset_top = 58.0
-offset_right = 1920.0
-offset_bottom = 125.0
+grow_horizontal = 2
+grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_6iyac")
[node name="Panel2" type="Panel" parent="."]
z_index = -6
-layout_mode = 2
-offset_right = 1920.0
-offset_bottom = 125.0
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_21xkr")
diff --git a/Scripts/B9/HTMLParser.gd b/Scripts/B9/HTMLParser.gd
index d99a853..6b91fdf 100644
--- a/Scripts/B9/HTMLParser.gd
+++ b/Scripts/B9/HTMLParser.gd
@@ -80,9 +80,6 @@ class HTMLElement:
func is_inline_element() -> bool:
return tag_name in ["b", "i", "u", "small", "mark", "code", "span", "a"]
-
- func is_heading_element() -> bool:
- return tag_name in ["h1", "h2", "h3", "h4", "h5", "h6"]
class ParseResult:
var root: HTMLElement
diff --git a/Scripts/TabContainer.gd b/Scripts/TabContainer.gd
index 82f388f..a86ca4e 100644
--- a/Scripts/TabContainer.gd
+++ b/Scripts/TabContainer.gd
@@ -86,7 +86,7 @@ func create_tab() -> void:
# WARNING: temporary
main.render()
-func _input(event: InputEvent) -> void:
+func _input(_event: InputEvent) -> void:
if Input.is_action_just_pressed("NewTab"):
create_tab()
if Input.is_action_just_pressed("CloseTab"):
diff --git a/Scripts/Tags/br.gd b/Scripts/Tags/br.gd
index e372935..d590451 100644
--- a/Scripts/Tags/br.gd
+++ b/Scripts/Tags/br.gd
@@ -1,4 +1,4 @@
extends Control
-func init(element: HTMLParser.HTMLElement) -> void:
+func init(_element: HTMLParser.HTMLElement) -> void:
pass
diff --git a/Scripts/Tags/button.gd b/Scripts/Tags/button.gd
new file mode 100644
index 0000000..051ffd8
--- /dev/null
+++ b/Scripts/Tags/button.gd
@@ -0,0 +1,18 @@
+extends Control
+
+func init(element: HTMLParser.HTMLElement) -> void:
+ var button_node: Button = $ButtonNode
+
+ var button_text = element.text_content.strip_edges()
+ if button_text.length() == 0:
+ button_text = element.get_bbcode_formatted_text()
+
+ if button_text.length() > 0:
+ button_node.text = button_text
+
+ button_node.custom_minimum_size = button_node.get_theme_default_font().get_string_size(
+ button_node.text,
+ HORIZONTAL_ALIGNMENT_LEFT,
+ -1,
+ button_node.get_theme_default_font_size()
+ ) + Vector2(20, 10) # Add padding
diff --git a/Scripts/Tags/button.gd.uid b/Scripts/Tags/button.gd.uid
new file mode 100644
index 0000000..485c216
--- /dev/null
+++ b/Scripts/Tags/button.gd.uid
@@ -0,0 +1 @@
+uid://cks35eudcm1wj
diff --git a/Scripts/Tags/form.gd b/Scripts/Tags/form.gd
new file mode 100644
index 0000000..fd81a25
--- /dev/null
+++ b/Scripts/Tags/form.gd
@@ -0,0 +1,4 @@
+extends VBoxContainer
+
+func init(_element: HTMLParser.HTMLElement) -> void:
+ pass
diff --git a/Scripts/Tags/form.gd.uid b/Scripts/Tags/form.gd.uid
new file mode 100644
index 0000000..375a87d
--- /dev/null
+++ b/Scripts/Tags/form.gd.uid
@@ -0,0 +1 @@
+uid://cn2iolk6biupv
diff --git a/Scripts/Tags/input.gd b/Scripts/Tags/input.gd
new file mode 100644
index 0000000..76bd305
--- /dev/null
+++ b/Scripts/Tags/input.gd
@@ -0,0 +1,23 @@
+extends Control
+
+func init(element: HTMLParser.HTMLElement) -> void:
+ var line_edit: LineEdit = $LineEdit
+ var check_box: CheckBox = $CheckBox
+
+ var input_type = element.get_attribute("type").to_lower()
+ var placeholder = element.get_attribute("placeholder")
+ var value = element.get_attribute("value")
+
+ match input_type:
+ "checkbox":
+ line_edit.visible = false
+ check_box.visible = true
+ if value and value == "true": check_box.button_pressed = true
+ custom_minimum_size = check_box.size
+ _: # Default to text input
+ line_edit.visible = true
+ check_box.visible = false
+ custom_minimum_size = line_edit.size
+
+ if placeholder: line_edit.placeholder_text = placeholder
+ if value: line_edit.text = value
diff --git a/Scripts/Tags/input.gd.uid b/Scripts/Tags/input.gd.uid
new file mode 100644
index 0000000..ec2969e
--- /dev/null
+++ b/Scripts/Tags/input.gd.uid
@@ -0,0 +1 @@
+uid://kv6ebscarj2e
diff --git a/Scripts/main.gd b/Scripts/main.gd
index 523c13a..beb86b3 100644
--- a/Scripts/main.gd
+++ b/Scripts/main.gd
@@ -19,6 +19,16 @@ const H3 = preload("res://Scenes/Tags/h3.tscn")
const H4 = preload("res://Scenes/Tags/h4.tscn")
const H5 = preload("res://Scenes/Tags/h5.tscn")
const H6 = preload("res://Scenes/Tags/h6.tscn")
+const FORM = preload("res://Scenes/Tags/form.tscn")
+const INPUT = preload("res://Scenes/Tags/input.tscn")
+const BUTTON = preload("res://Scenes/Tags/button.tscn")
+
+const MIN_SIZE = Vector2i(750, 200)
+
+func _ready():
+ ProjectSettings.set_setting("display/window/size/min_width", MIN_SIZE.x)
+ ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y)
+ DisplayServer.window_set_min_size(MIN_SIZE)
func render():
# Clear existing content
@@ -64,6 +74,17 @@ both spaces and
line breaks
+
+
@@ -176,6 +197,24 @@ line breaks
var separator = SEPARATOR.instantiate()
separator.init(element)
website_container.add_child(separator)
+ "form":
+ var form = FORM.instantiate()
+ form.init(element)
+ website_container.add_child(form)
+
+ # Render form children
+ for child_element in element.children:
+ var child_node = create_element_node(child_element)
+ if child_node:
+ form.add_child(child_node)
+ "input":
+ var input = INPUT.instantiate()
+ input.init(element)
+ website_container.add_child(input)
+ "button":
+ var button = BUTTON.instantiate()
+ button.init(element)
+ website_container.add_child(button)
"span":
var span = SPAN.instantiate()
span.init(element)
@@ -213,3 +252,20 @@ func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
return true
return false
+
+func create_element_node(element: HTMLParser.HTMLElement) -> Control:
+ match element.tag_name:
+ "input":
+ var input = INPUT.instantiate()
+ input.init(element)
+ return input
+ "button":
+ var button = BUTTON.instantiate()
+ button.init(element)
+ return button
+ "span":
+ var span = SPAN.instantiate()
+ span.init(element)
+ return span
+ _:
+ return null
diff --git a/addons/SmoothScroll/SmoothScrollContainer.gd b/addons/SmoothScroll/SmoothScrollContainer.gd
new file mode 100644
index 0000000..4c3c9ce
--- /dev/null
+++ b/addons/SmoothScroll/SmoothScrollContainer.gd
@@ -0,0 +1,916 @@
+## Smooth scroll functionality for ScrollContainer
+##
+## Applies velocity based momentum and "overdrag"
+## functionality to a ScrollContainer
+@tool
+extends ScrollContainer
+class_name SmoothScrollContainer
+
+@export_group("Mouse Wheel")
+## Drag impact for one scroll input
+@export_range(0, 10, 0.01, "or_greater", "hide_slider")
+var speed := 1000.0
+## ScrollDamper for wheel scrolling
+@export
+var wheel_scroll_damper: ScrollDamper = ExpoScrollDamper.new()
+
+@export_group("Dragging")
+## ScrollDamper for dragging
+@export
+var dragging_scroll_damper: ScrollDamper = ExpoScrollDamper.new()
+### Allow dragging with mouse or not
+@export
+var drag_with_mouse := true
+## Allow dragging with touch or not
+@export
+var drag_with_touch := true
+
+@export_group("Container")
+## Below this value, snap content to boundary
+@export
+var just_snap_under := 0.4
+## Margin of the currently focused element
+@export_range(0, 50)
+var follow_focus_margin := 20
+## Makes the container scrollable vertically
+@export
+var allow_vertical_scroll := true
+## Makes the container scrollable horizontally
+@export
+var allow_horizontal_scroll := true
+## Makes the container only scrollable where the content has overflow
+@export
+var auto_allow_scroll := true
+## Whether the content of this container should be allowed to overshoot at the ends
+## before interpolating back to its bounds
+@export
+var allow_overdragging := true
+
+@export_group("Scroll Bar")
+## Hides scrollbar as long as not hovered or interacted with
+@export
+var hide_scrollbar_over_time := false:
+ set(val): hide_scrollbar_over_time = _set_hide_scrollbar_over_time(val)
+## Time after scrollbar starts to fade out when 'hide_scrollbar_over_time' is true
+@export
+var scrollbar_hide_time := 5.0
+## Fadein time for scrollbar when 'hide_scrollbar_over_time' is true
+@export
+var scrollbar_fade_in_time := 0.2
+## Fadeout time for scrollbar when 'hide_scrollbar_over_time' is true
+@export
+var scrollbar_fade_out_time := 0.5
+
+@export_group("Input")
+## If true sets the input event as handled with set_input_as_handled()
+@export
+var handle_input := true
+
+@export_group("Debug")
+## Adds debug information
+@export
+var debug_mode := false
+
+## Current velocity of the `content_node`
+var velocity := Vector2(0,0)
+## Control node to move when scrolling
+var content_node: Control
+## Current position of `content_node`
+var pos := Vector2(0, 0)
+## Current ScrollDamper to use, recording to last input type
+var scroll_damper: ScrollDamper
+## When true, `content_node`'s position is only set by dragging the h scroll bar
+var h_scrollbar_dragging := false
+## When true, `content_node`'s position is only set by dragging the v scroll bar
+var v_scrollbar_dragging := false
+## When ture, `content_node` follows drag position
+var content_dragging := false
+## When ture, `content_node` has moved by dragging
+var content_dragging_moved := false
+## Timer for hiding scroll bar
+var scrollbar_hide_timer := Timer.new()
+## Tween for showing scroll bar
+var scrollbar_show_tween: Tween
+## Tween for hiding scroll bar
+var scrollbar_hide_tween: Tween
+## Tween for scroll x to
+var scroll_x_to_tween: Tween
+## Tween for scroll y to
+var scroll_y_to_tween: Tween
+## [0,1] Mouse or touch's relative movement accumulation when overdrag[br]
+## [2,3] Position where dragging starts[br]
+## [4,5,6,7] Left_distance, right_distance, top_distance, bottom_distance
+var drag_temp_data := []
+## Whether touch point is in deadzone.
+var is_in_deadzone := false
+## Whether mouse is on h or v scroll bar
+var mouse_on_scrollbar := false
+
+## If content is being scrolled
+var is_scrolling := false:
+ set(val):
+ if is_scrolling != val:
+ if val:
+ emit_signal("scroll_started")
+ else:
+ emit_signal("scroll_ended")
+ is_scrolling = val
+
+## Last type of input used to scroll
+enum SCROLL_TYPE {WHEEL, BAR, DRAG}
+var last_scroll_type: SCROLL_TYPE
+
+#region Virtual Functions
+
+func _ready() -> void:
+ if debug_mode:
+ setup_debug_drawing()
+ # Initialize variables
+ scroll_damper = wheel_scroll_damper
+
+ get_v_scroll_bar().gui_input.connect(_scrollbar_input.bind(true))
+ get_h_scroll_bar().gui_input.connect(_scrollbar_input.bind(false))
+ get_v_scroll_bar().mouse_entered.connect(_mouse_on_scroll_bar.bind(true))
+ get_v_scroll_bar().mouse_exited.connect(_mouse_on_scroll_bar.bind(false))
+ get_h_scroll_bar().mouse_entered.connect(_mouse_on_scroll_bar.bind(true))
+ get_h_scroll_bar().mouse_exited.connect(_mouse_on_scroll_bar.bind(false))
+ get_viewport().gui_focus_changed.connect(_on_focus_changed)
+
+ for c in get_children():
+ if not c is ScrollBar:
+ content_node = c
+
+ add_child(scrollbar_hide_timer)
+ scrollbar_hide_timer.one_shot = true
+ scrollbar_hide_timer.timeout.connect(_scrollbar_hide_timer_timeout)
+ if hide_scrollbar_over_time:
+ scrollbar_hide_timer.start(scrollbar_hide_time)
+ get_tree().node_added.connect(_on_node_added)
+
+func _process(delta: float) -> void:
+ if Engine.is_editor_hint(): return
+ scroll(true, velocity.y, pos.y, delta)
+ scroll(false, velocity.x, pos.x, delta)
+ update_scrollbars()
+ update_is_scrolling()
+
+ if debug_mode:
+ queue_redraw()
+
+# Detecting mouse entering and exiting scroll bar
+func _mouse_on_scroll_bar(entered: bool) -> void:
+ mouse_on_scrollbar = entered
+
+# Forwarding scroll inputs from scrollbar
+func _scrollbar_input(event: InputEvent, vertical: bool) -> void:
+ if event is InputEventMouseButton:
+ if event.button_index == MOUSE_BUTTON_WHEEL_DOWN\
+ or event.button_index == MOUSE_BUTTON_WHEEL_UP\
+ or event.button_index == MOUSE_BUTTON_WHEEL_LEFT\
+ or event.button_index == MOUSE_BUTTON_WHEEL_RIGHT:
+ _gui_input(event)
+
+ if event.button_index == MOUSE_BUTTON_LEFT:
+ if event.pressed:
+ if vertical:
+ v_scrollbar_dragging = true
+ last_scroll_type = SCROLL_TYPE.BAR
+ kill_scroll_to_tweens()
+ else:
+ h_scrollbar_dragging = true
+ last_scroll_type = SCROLL_TYPE.BAR
+ kill_scroll_to_tweens()
+ else:
+ if vertical:
+ v_scrollbar_dragging = false
+ else:
+ h_scrollbar_dragging = false
+
+ if event is InputEventScreenTouch:
+ if event.pressed:
+ if vertical:
+ v_scrollbar_dragging = true
+ last_scroll_type = SCROLL_TYPE.BAR
+ kill_scroll_to_tweens()
+ else:
+ h_scrollbar_dragging = true
+ last_scroll_type = SCROLL_TYPE.BAR
+ kill_scroll_to_tweens()
+ else:
+ if vertical:
+ v_scrollbar_dragging = false
+ else:
+ h_scrollbar_dragging = false
+
+func _gui_input(event: InputEvent) -> void:
+ # Show scroll bars when mouse moves
+ if hide_scrollbar_over_time and event is InputEventMouseMotion:
+ show_scrollbars()
+
+ if event is InputEventMouseButton:
+ match event.button_index:
+ MOUSE_BUTTON_WHEEL_DOWN:
+ if event.pressed:
+ last_scroll_type = SCROLL_TYPE.WHEEL
+ if event.shift_pressed or not should_scroll_vertical():
+ if should_scroll_horizontal():
+ velocity.x -= speed * event.factor
+ else:
+ if should_scroll_vertical():
+ velocity.y -= speed * event.factor
+ scroll_damper = wheel_scroll_damper
+ kill_scroll_to_tweens()
+ MOUSE_BUTTON_WHEEL_UP:
+ if event.pressed:
+ last_scroll_type = SCROLL_TYPE.WHEEL
+ if event.shift_pressed or not should_scroll_vertical():
+ if should_scroll_horizontal():
+ velocity.x += speed * event.factor
+ else:
+ if should_scroll_vertical():
+ velocity.y += speed * event.factor
+ scroll_damper = wheel_scroll_damper
+ kill_scroll_to_tweens()
+ MOUSE_BUTTON_WHEEL_LEFT:
+ if event.pressed:
+ last_scroll_type = SCROLL_TYPE.WHEEL
+ if event.shift_pressed:
+ if should_scroll_vertical():
+ velocity.y -= speed * event.factor
+ else:
+ if should_scroll_horizontal():
+ velocity.x += speed * event.factor
+ scroll_damper = wheel_scroll_damper
+ kill_scroll_to_tweens()
+ MOUSE_BUTTON_WHEEL_RIGHT:
+ if event.pressed:
+ last_scroll_type = SCROLL_TYPE.WHEEL
+ if event.shift_pressed:
+ if should_scroll_vertical():
+ velocity.y += speed * event.factor
+ else:
+ if should_scroll_horizontal():
+ velocity.x -= speed * event.factor
+ scroll_damper = wheel_scroll_damper
+ kill_scroll_to_tweens()
+ MOUSE_BUTTON_LEFT:
+ if event.pressed:
+ if !drag_with_mouse: return
+ content_dragging = true
+ is_in_deadzone = true
+ scroll_damper = dragging_scroll_damper
+ last_scroll_type = SCROLL_TYPE.DRAG
+ init_drag_temp_data()
+ kill_scroll_to_tweens()
+ else:
+ content_dragging = false
+ is_in_deadzone = false
+
+ if (event is InputEventScreenDrag and drag_with_touch) \
+ or (event is InputEventMouseMotion and drag_with_mouse):
+ if content_dragging:
+ if should_scroll_horizontal():
+ drag_temp_data[0] += event.relative.x
+ if should_scroll_vertical():
+ drag_temp_data[1] += event.relative.y
+ remove_all_children_focus(self)
+ handle_content_dragging()
+
+ if event is InputEventPanGesture:
+ if should_scroll_horizontal():
+ velocity.x = -event.delta.x * speed
+ kill_scroll_to_tweens()
+ if should_scroll_vertical():
+ velocity.y = -event.delta.y * speed
+ kill_scroll_to_tweens()
+
+ if event is InputEventScreenTouch:
+ if event.pressed:
+ if !drag_with_touch: return
+ content_dragging = true
+ is_in_deadzone = true
+ scroll_damper = dragging_scroll_damper
+ last_scroll_type = SCROLL_TYPE.DRAG
+ init_drag_temp_data()
+ kill_scroll_to_tweens()
+ else:
+ content_dragging = false
+ is_in_deadzone = false
+ # Handle input if handle_input is true
+ if handle_input:
+ get_tree().get_root().set_input_as_handled()
+
+# Scroll to new focused element
+func _on_focus_changed(control: Control) -> void:
+ if follow_focus:
+ self.ensure_control_visible(control)
+
+func _draw() -> void:
+ if debug_mode:
+ draw_debug()
+
+# Sets default mouse filter for SmoothScroll children to MOUSE_FILTER_PASS
+func _on_node_added(node: Node) -> void:
+ if node is Control and Engine.is_editor_hint():
+ if is_ancestor_of(node):
+ node.mouse_filter = Control.MOUSE_FILTER_PASS
+
+func _scrollbar_hide_timer_timeout() -> void:
+ if !any_scroll_bar_dragged():
+ hide_scrollbars()
+
+func _set_hide_scrollbar_over_time(value: bool) -> bool:
+ if value == false:
+ if scrollbar_hide_timer != null:
+ scrollbar_hide_timer.stop()
+ if scrollbar_show_tween != null:
+ scrollbar_show_tween.kill()
+ if scrollbar_hide_tween != null:
+ scrollbar_hide_tween.kill()
+ get_h_scroll_bar().modulate = Color.WHITE
+ get_v_scroll_bar().modulate = Color.WHITE
+ else:
+ if scrollbar_hide_timer != null and scrollbar_hide_timer.is_inside_tree():
+ scrollbar_hide_timer.start(scrollbar_hide_time)
+ return value
+
+func _get(property: StringName) -> Variant:
+ match property:
+ "scroll_horizontal":
+ if !content_node: return 0
+ return -int(content_node.position.x)
+ "scroll_vertical":
+ if !content_node: return 0
+ return -int(content_node.position.y)
+ _:
+ return null
+
+func _set(property: StringName, value: Variant) -> bool:
+ match property:
+ "scroll_horizontal":
+ if !content_node:
+ scroll_horizontal = 0
+ return true
+ scroll_horizontal = value
+ kill_scroll_x_to_tween()
+ velocity.x = 0.0
+ pos.x = clampf(
+ -value as float,
+ -get_child_size_x_diff(content_node, true),
+ 0.0
+ )
+ return true
+ "scroll_vertical":
+ if !content_node:
+ scroll_vertical = 0
+ return true
+ scroll_vertical = value
+ kill_scroll_y_to_tween()
+ velocity.y = 0.0
+ pos.y = clampf(
+ -value as float,
+ -get_child_size_y_diff(content_node, true),
+ 0.0
+ )
+ return true
+ _:
+ return false
+
+#endregion
+
+#region Scrolling Logic
+
+func scroll(vertical: bool, axis_velocity: float, axis_pos: float, delta: float):
+ # If no scroll needed, don't apply forces
+ if vertical:
+ if not should_scroll_vertical():
+ return
+ else:
+ if not should_scroll_horizontal():
+ return
+ if !scroll_damper: return
+ # Applies counterforces when overdragging
+ if not content_dragging:
+ axis_velocity = handle_overdrag(vertical, axis_velocity, axis_pos, delta)
+ # Move content node by applying velocity
+ var slide_result = scroll_damper.slide(axis_velocity, delta)
+ axis_velocity = slide_result[0]
+ axis_pos += slide_result[1]
+ # Snap to boundary if close enough
+ var snap_result = snap(vertical, axis_velocity, axis_pos)
+ axis_velocity = snap_result[0]
+ axis_pos = snap_result[1]
+ else:
+ # Preserve dragging velocity for 1 frame
+ # in case no movement event while releasing dragging with touch
+ if content_dragging_moved:
+ content_dragging_moved = false
+ else:
+ axis_velocity = 0.0
+ # If using scroll bar dragging, set the content_node's
+ # position by using the scrollbar position
+ if handle_scrollbar_drag():
+ return
+
+ if vertical:
+ if not allow_overdragging:
+ # Clamp if calculated position is beyond boundary
+ if is_outside_top_boundary(axis_pos):
+ axis_pos = 0.0
+ axis_velocity = 0.0
+ elif is_outside_bottom_boundary(axis_pos):
+ axis_pos = -get_child_size_y_diff(content_node, true)
+ axis_velocity = 0.0
+
+ content_node.position.y = axis_pos
+ pos.y = axis_pos
+ velocity.y = axis_velocity
+ else:
+ if not allow_overdragging:
+ # Clamp if calculated position is beyond boundary
+ if is_outside_left_boundary(axis_pos):
+ axis_pos = 0.0
+ axis_velocity = 0.0
+ elif is_outside_right_boundary(axis_pos):
+ axis_pos = -get_child_size_x_diff(content_node, true)
+ axis_velocity = 0.0
+
+ content_node.position.x = axis_pos
+ pos.x = axis_pos
+ velocity.x = axis_velocity
+
+func handle_overdrag(vertical: bool, axis_velocity: float, axis_pos: float, delta: float) -> float:
+ if !scroll_damper: return 0.0
+ # Calculate the size difference between this container and content_node
+ var size_diff = get_child_size_y_diff(content_node, true) \
+ if vertical else get_child_size_x_diff(content_node, true)
+ # Calculate distance to left and right or top and bottom
+ var dist1 = get_child_top_dist(axis_pos, size_diff) \
+ if vertical else get_child_left_dist(axis_pos, size_diff)
+ var dist2 = get_child_bottom_dist(axis_pos, size_diff) \
+ if vertical else get_child_right_dist(axis_pos, size_diff)
+ # Calculate velocity to left and right or top and bottom
+ var target_vel1 = scroll_damper._calculate_velocity_to_dest(dist1, 0.0)
+ var target_vel2 = scroll_damper._calculate_velocity_to_dest(dist2, 0.0)
+ # Bounce when out of boundary. When velocity is not fast enough to go back,
+ # apply a opposite force and get a new velocity. If the new velocity is too fast,
+ # apply a velocity that makes it scroll back exactly.
+ if axis_pos > 0.0:
+ if axis_velocity > target_vel1:
+ axis_velocity = scroll_damper.attract(
+ dist1,
+ 0.0,
+ axis_velocity,
+ delta
+ )
+ if axis_pos < -size_diff:
+ if axis_velocity < target_vel2:
+ axis_velocity = scroll_damper.attract(
+ dist2,
+ 0.0,
+ axis_velocity,
+ delta
+ )
+
+ return axis_velocity
+
+# Snap to boundary if close enough in next frame
+func snap(vertical: bool, axis_velocity: float, axis_pos: float) -> Array:
+ # Calculate the size difference between this container and content_node
+ var size_diff = get_child_size_y_diff(content_node, true) \
+ if vertical else get_child_size_x_diff(content_node, true)
+ # Calculate distance to left and right or top and bottom
+ var dist1 = get_child_top_dist(axis_pos, size_diff) \
+ if vertical else get_child_left_dist(axis_pos, size_diff)
+ var dist2 = get_child_bottom_dist(axis_pos, size_diff) \
+ if vertical else get_child_right_dist(axis_pos, size_diff)
+ if (
+ dist1 > 0.0 \
+ and abs(dist1) < just_snap_under \
+ and abs(axis_velocity) < just_snap_under \
+ ):
+ axis_pos -= dist1
+ axis_velocity = 0.0
+ elif (
+ dist2 < 0.0 \
+ and abs(dist2) < just_snap_under \
+ and abs(axis_velocity) < just_snap_under \
+ ):
+ axis_pos -= dist2
+ axis_velocity = 0.0
+
+ return [axis_velocity, axis_pos]
+
+## Returns true when scrollbar was dragged
+func handle_scrollbar_drag() -> bool:
+ if h_scrollbar_dragging:
+ velocity.x = 0.0
+ pos.x = -get_h_scroll_bar().value
+ return true
+
+ if v_scrollbar_dragging:
+ velocity.y = 0.0
+ pos.y = -get_v_scroll_bar().value
+ return true
+ return false
+
+func handle_content_dragging() -> void:
+ if !dragging_scroll_damper: return
+
+ if(
+ Vector2(drag_temp_data[0], drag_temp_data[1]).length() < scroll_deadzone \
+ and is_in_deadzone
+ ):
+ return
+ elif is_in_deadzone == true:
+ is_in_deadzone = false
+ drag_temp_data[0] = 0.0
+ drag_temp_data[1] = 0.0
+
+ content_dragging_moved = true
+
+ var calculate_dest = func(delta: float, damping: float) -> float:
+ if delta >= 0.0:
+ return delta / (1 + delta * damping * 0.00001)
+ else:
+ return delta
+
+ var calculate_position = func(
+ temp_dist1: float, # Temp distance
+ temp_dist2: float,
+ temp_relative: float # Event's relative movement accumulation
+ ) -> float:
+ if temp_relative + temp_dist1 > 0.0:
+ var delta = min(temp_relative, temp_relative + temp_dist1)
+ var dest = calculate_dest.call(delta, dragging_scroll_damper._attract_factor)
+ return dest - min(0.0, temp_dist1)
+ elif temp_relative + temp_dist2 < 0.0:
+ var delta = max(temp_relative, temp_relative + temp_dist2)
+ var dest = -calculate_dest.call(-delta, dragging_scroll_damper._attract_factor)
+ return dest - max(0.0, temp_dist2)
+ else: return temp_relative
+
+ if should_scroll_vertical():
+ var y_pos = calculate_position.call(
+ drag_temp_data[6], # Temp top_distance
+ drag_temp_data[7], # Temp bottom_distance
+ drag_temp_data[1] # Temp y relative accumulation
+ ) + drag_temp_data[3]
+ velocity.y = (y_pos - pos.y) / get_process_delta_time()
+ pos.y = y_pos
+ if should_scroll_horizontal():
+ var x_pos = calculate_position.call(
+ drag_temp_data[4], # Temp left_distance
+ drag_temp_data[5], # Temp right_distance
+ drag_temp_data[0] # Temp x relative accumulation
+ ) + drag_temp_data[2]
+ velocity.x = (x_pos - pos.x) / get_process_delta_time()
+ pos.x = x_pos
+
+func remove_all_children_focus(node: Node) -> void:
+ if node is Control:
+ var control = node as Control
+ control.release_focus()
+
+ for child in node.get_children():
+ remove_all_children_focus(child)
+
+func update_is_scrolling() -> void:
+ if(
+ (content_dragging and not is_in_deadzone)
+ or any_scroll_bar_dragged()
+ or velocity != Vector2.ZERO
+ ):
+ is_scrolling = true
+ else:
+ is_scrolling = false
+
+func update_scrollbars() -> void:
+ # Update vertical scroll bar
+ if get_v_scroll_bar().value != -pos.y:
+ get_v_scroll_bar().set_value_no_signal(-pos.y)
+ get_v_scroll_bar().queue_redraw()
+ # Update horizontal scroll bar
+ if get_h_scroll_bar().value != -pos.x:
+ get_h_scroll_bar().set_value_no_signal(-pos.x)
+ get_h_scroll_bar().queue_redraw()
+
+ # Always show sroll bars when scrolling or mouse is on any scroll bar
+ if hide_scrollbar_over_time and (is_scrolling or mouse_on_scrollbar):
+ show_scrollbars()
+
+func init_drag_temp_data() -> void:
+ # Calculate the size difference between this container and content_node
+ var content_node_size_diff = get_child_size_diff(content_node, true, true)
+ # Calculate distance to left, right, top and bottom
+ var content_node_boundary_dist = get_child_boundary_dist(
+ content_node.position,
+ content_node_size_diff
+ )
+ drag_temp_data = [
+ 0.0,
+ 0.0,
+ content_node.position.x,
+ content_node.position.y,
+ content_node_boundary_dist.x,
+ content_node_boundary_dist.y,
+ content_node_boundary_dist.z,
+ content_node_boundary_dist.w,
+ ]
+
+# Get container size x without v scroll bar 's width
+func get_spare_size_x() -> float:
+ var size_x = size.x
+ if get_v_scroll_bar().visible:
+ size_x -= get_v_scroll_bar().size.x
+ return max(size_x, 0.0)
+
+# Get container size y without h scroll bar 's height
+func get_spare_size_y() -> float:
+ var size_y = size.y
+ if get_h_scroll_bar().visible:
+ size_y -= get_h_scroll_bar().size.y
+ return max(size_y, 0.0)
+
+# Get container size without scroll bars' size
+func get_spare_size() -> Vector2:
+ return Vector2(get_spare_size_x(), get_spare_size_y())
+
+# Calculate the size x difference between this container and child node
+func get_child_size_x_diff(child: Control, clamp: bool) -> float:
+ var child_size_x = child.size.x * child.scale.x
+ # Falsify the size of the child node to avoid errors
+ # when its size is smaller than this container 's
+ if clamp:
+ child_size_x = max(child_size_x, get_spare_size_x())
+ return child_size_x - get_spare_size_x()
+
+# Calculate the size y difference between this container and child node
+func get_child_size_y_diff(child: Control, clamp: bool) -> float:
+ var child_size_y = child.size.y * child.scale.y
+ # Falsify the size of the child node to avoid errors
+ # when its size is smaller than this container 's
+ if clamp:
+ child_size_y = max(child_size_y, get_spare_size_y())
+ return child_size_y - get_spare_size_y()
+
+# Calculate the size difference between this container and child node
+func get_child_size_diff(child: Control, clamp_x: bool, clamp_y: bool) -> Vector2:
+ return Vector2(
+ get_child_size_x_diff(child, clamp_x),
+ get_child_size_y_diff(child, clamp_y)
+ )
+
+# Calculate distance to left
+func get_child_left_dist(child_pos_x: float, child_size_diff_x: float) -> float:
+ return child_pos_x
+
+# Calculate distance to right
+func get_child_right_dist(child_pos_x: float, child_size_diff_x: float) -> float:
+ return child_pos_x + child_size_diff_x
+
+# Calculate distance to top
+func get_child_top_dist(child_pos_y: float, child_size_diff_y: float) -> float:
+ return child_pos_y
+
+# Calculate distance to bottom
+func get_child_bottom_dist(child_pos_y: float, child_size_diff_y: float) -> float:
+ return child_pos_y + child_size_diff_y
+
+# Calculate distance to left, right, top and bottom
+func get_child_boundary_dist(child_pos: Vector2, child_size_diff: Vector2) -> Vector4:
+ return Vector4(
+ get_child_left_dist(child_pos.x, child_size_diff.x),
+ get_child_right_dist(child_pos.x, child_size_diff.x),
+ get_child_top_dist(child_pos.y, child_size_diff.y),
+ get_child_bottom_dist(child_pos.y, child_size_diff.y),
+ )
+
+func kill_scroll_x_to_tween() -> void:
+ if scroll_x_to_tween: scroll_x_to_tween.kill()
+
+func kill_scroll_y_to_tween() -> void:
+ if scroll_y_to_tween: scroll_y_to_tween.kill()
+
+func kill_scroll_to_tweens() -> void:
+ kill_scroll_x_to_tween()
+ kill_scroll_y_to_tween()
+
+#endregion
+
+#region Debug Drawing
+
+var debug_gradient := Gradient.new()
+
+func setup_debug_drawing() -> void:
+ debug_gradient.set_color(0.0, Color.GREEN)
+ debug_gradient.set_color(1.0, Color.RED)
+
+func draw_debug() -> void:
+ # Calculate the size difference between this container and content_node
+ var size_diff = get_child_size_diff(content_node, false, false)
+ # Calculate distance to left, right, top and bottom
+ var boundary_dist = get_child_boundary_dist(
+ content_node.position,
+ size_diff
+ )
+ var bottom_distance = boundary_dist.w
+ var top_distance = boundary_dist.z
+ var right_distance = boundary_dist.y
+ var left_distance = boundary_dist.x
+ # Overdrag lines
+ # Top + Bottom
+ draw_line(Vector2(0.0, 0.0), Vector2(0.0, top_distance), debug_gradient.sample(clamp(top_distance / size.y, 0.0, 1.0)), 5.0)
+ draw_line(Vector2(0.0, size.y), Vector2(0.0, size.y+bottom_distance), debug_gradient.sample(clamp(-bottom_distance / size.y, 0.0, 1.0)), 5.0)
+ # Left + Right
+ draw_line(Vector2(0.0, size.y), Vector2(left_distance, size.y), debug_gradient.sample(clamp(left_distance / size.y, 0.0, 1.0)), 5.0)
+ draw_line(Vector2(size.x, size.y), Vector2(size.x+right_distance, size.y), debug_gradient.sample(clamp(-right_distance / size.y, 0.0, 1.0)), 5.0)
+
+ # Velocity lines
+ var origin := Vector2(5.0, size.y/2)
+ draw_line(origin, origin + Vector2(0.0, velocity.y*0.01), debug_gradient.sample(clamp(velocity.y*2 / size.y, 0.0, 1.0)), 5.0)
+ draw_line(origin, origin + Vector2(0.0, velocity.x*0.01), debug_gradient.sample(clamp(velocity.x*2 / size.x, 0.0, 1.0)), 5.0)
+
+#endregion
+
+#region API Functions
+
+## Scrolls to specific x position
+func scroll_x_to(x_pos: float, duration := 0.5) -> void:
+ if not should_scroll_horizontal(): return
+ if content_dragging: return
+ velocity.x = 0.0
+ var size_x_diff = get_child_size_x_diff(content_node, true)
+ x_pos = clampf(x_pos, -size_x_diff, 0.0)
+ kill_scroll_x_to_tween()
+ scroll_x_to_tween = create_tween()
+ var tweener = scroll_x_to_tween.tween_property(self, "pos:x", x_pos, duration)
+ tweener.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUINT)
+
+## Scrolls to specific y position
+func scroll_y_to(y_pos: float, duration := 0.5) -> void:
+ if not should_scroll_vertical(): return
+ if content_dragging: return
+ velocity.y = 0.0
+ var size_y_diff = get_child_size_y_diff(content_node, true)
+ y_pos = clampf(y_pos, -size_y_diff, 0.0)
+ kill_scroll_y_to_tween()
+ scroll_y_to_tween = create_tween()
+ var tweener = scroll_y_to_tween.tween_property(self, "pos:y", y_pos, duration)
+ tweener.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUINT)
+
+## Scrolls up a page
+func scroll_page_up(duration := 0.5) -> void:
+ var destination = content_node.position.y + get_spare_size_y()
+ scroll_y_to(destination, duration)
+
+## Scrolls down a page
+func scroll_page_down(duration := 0.5) -> void:
+ var destination = content_node.position.y - get_spare_size_y()
+ scroll_y_to(destination, duration)
+
+## Scrolls left a page
+func scroll_page_left(duration := 0.5) -> void:
+ var destination = content_node.position.x + get_spare_size_x()
+ scroll_x_to(destination, duration)
+
+## Scrolls right a page
+func scroll_page_right(duration := 0.5) -> void:
+ var destination = content_node.position.x - get_spare_size_x()
+ scroll_x_to(destination, duration)
+
+## Adds velocity to the vertical scroll
+func scroll_vertically(amount: float) -> void:
+ velocity.y -= amount
+
+## Adds velocity to the horizontal scroll
+func scroll_horizontally(amount: float) -> void:
+ velocity.x -= amount
+
+## Scrolls to top
+func scroll_to_top(duration := 0.5) -> void:
+ scroll_y_to(0.0, duration)
+
+## Scrolls to bottom
+func scroll_to_bottom(duration := 0.5) -> void:
+ scroll_y_to(get_spare_size_y() - content_node.size.y, duration)
+
+## Scrolls to left
+func scroll_to_left(duration := 0.5) -> void:
+ scroll_x_to(0.0, duration)
+
+## Scrolls to right
+func scroll_to_right(duration := 0.5) -> void:
+ scroll_x_to(get_spare_size_x() - content_node.size.x, duration)
+
+func is_outside_top_boundary(y_pos: float = pos.y) -> bool:
+ var size_y_diff = get_child_size_y_diff(content_node,true)
+ var top_dist = get_child_top_dist(y_pos, size_y_diff)
+ return top_dist > 0.0
+
+func is_outside_bottom_boundary(y_pos: float = pos.y) -> bool:
+ var size_y_diff = get_child_size_y_diff(content_node,true)
+ var bottom_dist = get_child_bottom_dist(y_pos, size_y_diff)
+ return bottom_dist < 0.0
+
+func is_outside_left_boundary(x_pos: float = pos.x) -> bool:
+ var size_x_diff = get_child_size_x_diff(content_node,true)
+ var left_dist = get_child_left_dist(x_pos, size_x_diff)
+ return left_dist > 0.0
+
+func is_outside_right_boundary(x_pos: float = pos.x) -> bool:
+ var size_x_diff = get_child_size_x_diff(content_node,true)
+ var right_dist = get_child_right_dist(x_pos, size_x_diff)
+ return right_dist < 0.0
+
+## Returns true if any scroll bar is being dragged
+func any_scroll_bar_dragged() -> bool:
+ return h_scrollbar_dragging or v_scrollbar_dragging
+
+## Returns true if there is enough content height to scroll
+func should_scroll_vertical() -> bool:
+ var disable_scroll = (not allow_vertical_scroll) \
+ or (auto_allow_scroll and get_child_size_y_diff(content_node, false) <= 0) \
+ or !scroll_damper
+ if disable_scroll:
+ velocity.y = 0.0
+ return false
+ else:
+ return true
+
+## Returns true if there is enough content width to scroll
+func should_scroll_horizontal() -> bool:
+ var disable_scroll = (not allow_horizontal_scroll) \
+ or (auto_allow_scroll and get_child_size_x_diff(content_node, false) <= 0) \
+ or !scroll_damper
+ if disable_scroll:
+ velocity.x = 0.0
+ return false
+ else:
+ return true
+
+## Fades out scrollbars within given [param time].[br]
+## Default for [param time] is current [member scrollbar_fade_out_time]
+func hide_scrollbars(time: float = scrollbar_fade_out_time) -> void:
+ # Kill scrollbar_show_tween to avoid animation conflict
+ if scrollbar_show_tween != null and scrollbar_show_tween.is_valid():
+ scrollbar_show_tween.kill()
+ # Create new tweens if needed
+ if (
+ get_v_scroll_bar().modulate != Color.TRANSPARENT \
+ or get_h_scroll_bar().modulate != Color.TRANSPARENT
+ ):
+ if scrollbar_hide_tween and !scrollbar_hide_tween.is_running():
+ scrollbar_hide_tween.kill()
+ if scrollbar_hide_tween == null or !scrollbar_hide_tween.is_valid():
+ scrollbar_hide_tween = create_tween()
+ scrollbar_hide_tween.set_parallel(true)
+ scrollbar_hide_tween.tween_property(get_v_scroll_bar(), 'modulate', Color.TRANSPARENT, time)
+ scrollbar_hide_tween.tween_property(get_h_scroll_bar(), 'modulate', Color.TRANSPARENT, time)
+
+## Fades in scrollbars within given [param time].[br]
+## Default for [param time] is current [member scrollbar_fade_in_time]
+func show_scrollbars(time: float = scrollbar_fade_in_time) -> void:
+ # Restart timer
+ scrollbar_hide_timer.start(scrollbar_hide_time)
+ # Kill scrollbar_hide_tween to avoid animation conflict
+ if scrollbar_hide_tween != null and scrollbar_hide_tween.is_valid():
+ scrollbar_hide_tween.kill()
+ # Create new tweens if needed
+ if (
+ get_v_scroll_bar().modulate != Color.WHITE \
+ or get_h_scroll_bar().modulate != Color.WHITE \
+ ):
+ if scrollbar_show_tween and !scrollbar_show_tween.is_running():
+ scrollbar_show_tween.kill()
+ if scrollbar_show_tween == null or !scrollbar_show_tween.is_valid():
+ scrollbar_show_tween = create_tween()
+ scrollbar_show_tween.set_parallel(true)
+ scrollbar_show_tween.tween_property(get_v_scroll_bar(), 'modulate', Color.WHITE, time)
+ scrollbar_show_tween.tween_property(get_h_scroll_bar(), 'modulate', Color.WHITE, time)
+
+## Scroll to position to ensure the given control node is visible
+func ensure_control_visible(control: Control) -> void:
+ if !content_node: return
+ if !content_node.is_ancestor_of(control): return
+ if !scroll_damper: return
+
+ var size_diff = (
+ control.get_global_rect().size - get_global_rect().size
+ ) / (get_global_rect().size / size)
+ var boundary_dist = get_child_boundary_dist(
+ (control.global_position - global_position) \
+ / (get_global_rect().size / size),
+ size_diff
+ )
+ var content_node_position = content_node.position
+ if boundary_dist.x < 0 + follow_focus_margin:
+ scroll_x_to(content_node_position.x - boundary_dist.x + follow_focus_margin)
+ elif boundary_dist.y > 0 - follow_focus_margin:
+ scroll_x_to(content_node_position.x - boundary_dist.y - follow_focus_margin)
+ if boundary_dist.z < 0 + follow_focus_margin:
+ scroll_y_to(content_node_position.y - boundary_dist.z + follow_focus_margin)
+ elif boundary_dist.w > 0 - follow_focus_margin:
+ scroll_y_to(content_node_position.y - boundary_dist.w - follow_focus_margin)
+
+#endregion
diff --git a/addons/SmoothScroll/SmoothScrollContainer.gd.uid b/addons/SmoothScroll/SmoothScrollContainer.gd.uid
new file mode 100644
index 0000000..305c994
--- /dev/null
+++ b/addons/SmoothScroll/SmoothScrollContainer.gd.uid
@@ -0,0 +1 @@
+uid://bgqglerkcylxx
diff --git a/addons/SmoothScroll/class-icon.svg b/addons/SmoothScroll/class-icon.svg
new file mode 100644
index 0000000..30862c1
--- /dev/null
+++ b/addons/SmoothScroll/class-icon.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/addons/SmoothScroll/class-icon.svg.import b/addons/SmoothScroll/class-icon.svg.import
new file mode 100644
index 0000000..488e0a4
--- /dev/null
+++ b/addons/SmoothScroll/class-icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dorhyoghxkay6"
+path="res://.godot/imported/class-icon.svg-c17de51589a7d30572bf401526524f64.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/SmoothScroll/class-icon.svg"
+dest_files=["res://.godot/imported/class-icon.svg-c17de51589a7d30572bf401526524f64.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/SmoothScroll/debug_font.tres b/addons/SmoothScroll/debug_font.tres
new file mode 100644
index 0000000..3871a65
--- /dev/null
+++ b/addons/SmoothScroll/debug_font.tres
@@ -0,0 +1,3 @@
+[gd_resource type="SystemFont" format=3 uid="uid://cw8c0p3b5mv5y"]
+
+[resource]
diff --git a/addons/SmoothScroll/icon.afdesign b/addons/SmoothScroll/icon.afdesign
new file mode 100644
index 0000000000000000000000000000000000000000..1b54598f495cee0592598511294de751b7dbc329
GIT binary patch
literal 49347
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 RX`;&+{w-zBBWJrpVAHT`{cpNAVX
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 |