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 +
+ Name: + + Surname: + + Smart: + + + +
+ @@ -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 0000000..1b54598 Binary files /dev/null and b/addons/SmoothScroll/icon.afdesign differ diff --git a/addons/SmoothScroll/plugin.cfg b/addons/SmoothScroll/plugin.cfg new file mode 100644 index 0000000..2c42343 --- /dev/null +++ b/addons/SmoothScroll/plugin.cfg @@ -0,0 +1,8 @@ +[plugin] + +name="SmoothScroll" +description="""This plugin adds a new scroll container class +with additional smooth scroll options.""" +author="Fabian Keßler (SpyrexDE)" +version="1.3" +script="plugin.gd" diff --git a/addons/SmoothScroll/plugin.gd b/addons/SmoothScroll/plugin.gd new file mode 100644 index 0000000..de14735 --- /dev/null +++ b/addons/SmoothScroll/plugin.gd @@ -0,0 +1,12 @@ +@tool +extends EditorPlugin + + +func _enter_tree(): + add_custom_type("ScrollDamper", "Resource", preload("scroll_damper/scroll_damper.gd"), preload("scroll_damper/icon.svg")) + add_custom_type("SmoothScrollContainer", "ScrollContainer", preload("SmoothScrollContainer.gd"), preload("class-icon.svg")) + + +func _exit_tree(): + remove_custom_type("ScrollDamper") + remove_custom_type("SmoothScrollContainer") diff --git a/addons/SmoothScroll/plugin.gd.uid b/addons/SmoothScroll/plugin.gd.uid new file mode 100644 index 0000000..ca7fc96 --- /dev/null +++ b/addons/SmoothScroll/plugin.gd.uid @@ -0,0 +1 @@ +uid://b7svd7w4nwpch diff --git a/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd b/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd new file mode 100644 index 0000000..3c40815 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd @@ -0,0 +1,33 @@ +extends ScrollDamper +class_name CubicScrollDamper + + +## Friction, not physical. +## The higher the value, the more obvious the deceleration. +@export_range(0.001, 10000.0, 0.001, "or_greater", "hide_slider") +var friction := 4.0: + set(val): + friction = max(val, 0.001) + _factor = pow(10.0, friction) - 1.0 + +## Factor to use in formula +var _factor := 10000.0: + set(val): _factor = max(val, 0.000000000001) + + +func _calculate_velocity_by_time(time: float) -> float: + if time <= 0.0: return 0.0 + return time*time*time * _factor + + +func _calculate_time_by_velocity(velocity: float) -> float: + return pow(abs(velocity) / _factor, 1.0/3.0) + + +func _calculate_offset_by_time(time: float) -> float: + time = max(time, 0.0) + return 1.0/4.0 * _factor * time*time*time*time + + +func _calculate_time_by_offset(offset: float) -> float: + return pow(abs(offset) * 4.0 / _factor, 1.0/4.0) diff --git a/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd.uid b/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd.uid new file mode 100644 index 0000000..5917f98 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/cubic_scroll_damper.gd.uid @@ -0,0 +1 @@ +uid://dlagxcnf11lr4 diff --git a/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd b/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd new file mode 100644 index 0000000..c6d9b9d --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd @@ -0,0 +1,47 @@ +extends ScrollDamper +class_name ExpoScrollDamper + + +## Friction, not physical. +## The higher the value, the more obvious the deceleration. +@export_range(0.001, 10000.0, 0.001, "or_greater", "hide_slider") +var friction := 4.0: + set(val): + friction = max(val, 0.001) + _factor = pow(10.0, friction) + +## Factor to use in formula +var _factor := 10000.0: + set(val): _factor = max(val, 1.000000000001) + +## Minumun velocity. +@export_range(0.001, 100000.0, 0.001, "or_greater", "hide_slider") +var minimum_velocity := 0.4: + set(val): minimum_velocity = max(val, 0.001) + + +func _calculate_velocity_by_time(time: float) -> float: + var minimum_time = _calculate_time_by_velocity(minimum_velocity) + if time <= minimum_time: return 0.0 + return pow(_factor, time) + + +func _calculate_time_by_velocity(velocity: float) -> float: + return log(abs(velocity)) / log(_factor) + + +func _calculate_offset_by_time(time: float) -> float: + return pow(_factor, time) / log(_factor) + + +func _calculate_time_by_offset(offset: float) -> float: + return log(offset * log(_factor)) / log(_factor) + + +func _calculate_velocity_to_dest(from: float, to: float) -> float: + var dist = to - from + var min_time = _calculate_time_by_velocity(minimum_velocity) + var min_offset = _calculate_offset_by_time(min_time) + var time = _calculate_time_by_offset(abs(dist) + min_offset) + var vel = _calculate_velocity_by_time(time) * sign(dist) + return vel diff --git a/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd.uid b/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd.uid new file mode 100644 index 0000000..bd22621 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/expo_scroll_damper.gd.uid @@ -0,0 +1 @@ +uid://b7h0k2h2qwlqv diff --git a/addons/SmoothScroll/scroll_damper/icon.afdesign b/addons/SmoothScroll/scroll_damper/icon.afdesign new file mode 100644 index 0000000..2bd817e Binary files /dev/null and b/addons/SmoothScroll/scroll_damper/icon.afdesign differ diff --git a/addons/SmoothScroll/scroll_damper/icon.svg b/addons/SmoothScroll/scroll_damper/icon.svg new file mode 100644 index 0000000..54a8de1 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/icon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/SmoothScroll/scroll_damper/icon.svg.import b/addons/SmoothScroll/scroll_damper/icon.svg.import new file mode 100644 index 0000000..240f33d --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4ok12qtgl7xq" +path="res://.godot/imported/icon.svg-5b01e8115f19d6d63dc265815ce29c75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/SmoothScroll/scroll_damper/icon.svg" +dest_files=["res://.godot/imported/icon.svg-5b01e8115f19d6d63dc265815ce29c75.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/scroll_damper/linear_scroll_damper.gd b/addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd new file mode 100644 index 0000000..c563513 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd @@ -0,0 +1,33 @@ +extends ScrollDamper +class_name LinearScrollDamper + + +## Friction, not physical. +## The higher the value, the more obvious the deceleration. +@export_range(0.001, 10000.0, 0.001, "or_greater", "hide_slider") +var friction := 4.0: + set(val): + friction = max(val, 0.001) + _factor = pow(10.0, friction) - 1.0 + +## Factor to use in formula +var _factor := 10000.0: + set(val): _factor = max(val, 0.000000000001) + + +func _calculate_velocity_by_time(time: float) -> float: + if time <= 0.0: return 0.0 + return time * _factor + + +func _calculate_time_by_velocity(velocity: float) -> float: + return abs(velocity) / _factor + + +func _calculate_offset_by_time(time: float) -> float: + time = max(time, 0.0) + return 1.0/2.0 * _factor * time*time + + +func _calculate_time_by_offset(offset: float) -> float: + return sqrt(abs(offset) * 2.0 / _factor) diff --git a/addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd.uid b/addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd.uid new file mode 100644 index 0000000..632707a --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/linear_scroll_damper.gd.uid @@ -0,0 +1 @@ +uid://dvvc75dvopgcg diff --git a/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd b/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd new file mode 100644 index 0000000..308c0e0 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd @@ -0,0 +1,33 @@ +extends ScrollDamper +class_name QuadScrollDamper + + +## Friction, not physical. +## The higher the value, the more obvious the deceleration. +@export_range(0.001, 10000.0, 0.001, "or_greater", "hide_slider") +var friction := 4.0: + set(val): + friction = max(val, 0.001) + _factor = pow(10.0, friction) - 1.0 + +## Factor to use in formula +var _factor := 10000.0: + set(val): _factor = max(val, 0.000000000001) + + +func _calculate_velocity_by_time(time: float) -> float: + if time <= 0.0: return 0.0 + return time*time * _factor + + +func _calculate_time_by_velocity(velocity: float) -> float: + return sqrt(abs(velocity) / _factor) + + +func _calculate_offset_by_time(time: float) -> float: + time = max(time, 0.0) + return 1.0/3.0 * _factor * time*time*time + + +func _calculate_time_by_offset(offset: float) -> float: + return pow(abs(offset) * 3.0 / _factor, 1.0/3.0) diff --git a/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd.uid b/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd.uid new file mode 100644 index 0000000..51ad970 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/quad_scroll_damper.gd.uid @@ -0,0 +1 @@ +uid://ccgjrqkk3oksk diff --git a/addons/SmoothScroll/scroll_damper/scroll_damper.gd b/addons/SmoothScroll/scroll_damper/scroll_damper.gd new file mode 100644 index 0000000..448f0ee --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/scroll_damper.gd @@ -0,0 +1,74 @@ +@icon("icon.svg") +extends Resource +class_name ScrollDamper + +## Abstract class + +## Rebound strength. The higher the value, the faster it attracts. +@export_range(0.0, 1.0, 0.001, "or_greater", "hide_slider") +var rebound_strength := 7.0: + set(val): + rebound_strength= max(val, 0.0) + _attract_factor = rebound_strength * rebound_strength * rebound_strength + +## Factor for attracting. +var _attract_factor := 400.0: + set(val): + _attract_factor = max(val, 0.0) + + +# Abstract method +func _calculate_velocity_by_time(time: float) -> float: + return 0.0 + +# Abstract method +func _calculate_time_by_velocity(velocity: float) -> float: + return 0.0 + +# Abstract method +func _calculate_offset_by_time(time: float) -> float: + return 0.0 + +# Abstract method +func _calculate_time_by_offset(offset: float) -> float: + return 0.0 + + +func _calculate_velocity_to_dest(from: float, to: float) -> float: + var dist = to - from + var time = _calculate_time_by_offset(abs(dist)) + var vel = _calculate_velocity_by_time(time) * sign(dist) + return vel + + +func _calculate_next_velocity(present_time: float, delta_time: float) -> float: + return _calculate_velocity_by_time(present_time - delta_time) + + +func _calculate_next_offset(present_time: float, delta_time: float) -> float: + return _calculate_offset_by_time(present_time) \ + - _calculate_offset_by_time(present_time - delta_time) + + +## Return the result of next velocity and position according to delta time +func slide(velocity: float, delta_time: float) -> Array: + var present_time = _calculate_time_by_velocity(velocity) + return [ + _calculate_next_velocity(present_time, delta_time) * sign(velocity), + _calculate_next_offset(present_time, delta_time) * sign(velocity) + ] + + +## Emulate force that attracts something to destination. +## Return the result of next velocity according to delta time +func attract(from: float, to: float, velocity: float, delta_time: float) -> float: + var dist = to - from + var target_vel = _calculate_velocity_to_dest(from, to) + velocity += _attract_factor * dist * delta_time \ + + _calculate_velocity_by_time(delta_time) * sign(dist) + if ( + (dist > 0 and velocity >= target_vel) \ + or (dist < 0 and velocity <= target_vel) \ + ): + velocity = target_vel + return velocity diff --git a/addons/SmoothScroll/scroll_damper/scroll_damper.gd.uid b/addons/SmoothScroll/scroll_damper/scroll_damper.gd.uid new file mode 100644 index 0000000..8f446a0 --- /dev/null +++ b/addons/SmoothScroll/scroll_damper/scroll_damper.gd.uid @@ -0,0 +1 @@ +uid://bcy471w0pi8af diff --git a/project.godot b/project.godot index ed9f754..9315199 100644 --- a/project.godot +++ b/project.godot @@ -26,6 +26,10 @@ window/size/viewport_width=1920 window/size/viewport_height=1080 window/stretch/aspect="ignore" +[editor_plugins] + +enabled=PackedStringArray("res://addons/SmoothScroll/plugin.cfg") + [file_customization] folder_colors={