tab animations
This commit is contained in:
@@ -8,6 +8,7 @@ TODO:
|
|||||||
7. **Video** support via [GDE GoZen](https://github.com/VoylinsGamedevJourney/gde_gozen)
|
7. **Video** support via [GDE GoZen](https://github.com/VoylinsGamedevJourney/gde_gozen)
|
||||||
8. **More input types** (url, tel, date, time, etc.)
|
8. **More input types** (url, tel, date, time, etc.)
|
||||||
9. **Required** attribute for inputs
|
9. **Required** attribute for inputs
|
||||||
|
10. Installer should register **gurt://** as a valid protocol thru the registry.
|
||||||
|
|
||||||
Issues:
|
Issues:
|
||||||
1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing
|
1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing
|
||||||
|
|||||||
116
Scenes/Tab.tscn
116
Scenes/Tab.tscn
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=12 format=3 uid="uid://sqhcxhcre081"]
|
[gd_scene load_steps=14 format=3 uid="uid://sqhcxhcre081"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://crpnnfqm3k5xv" path="res://Scripts/Tab.gd" id="1_q3baj"]
|
[ext_resource type="Script" uid="uid://crpnnfqm3k5xv" path="res://Scripts/Tab.gd" id="1_q3baj"]
|
||||||
[ext_resource type="Texture2D" uid="uid://gq8g7t4s3ryg" path="res://Assets/Icons/x.svg" id="2_pisds"]
|
[ext_resource type="Texture2D" uid="uid://gq8g7t4s3ryg" path="res://Assets/Icons/x.svg" id="2_pisds"]
|
||||||
@@ -14,25 +14,95 @@
|
|||||||
|
|
||||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_344ge"]
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_344ge"]
|
||||||
|
|
||||||
|
[sub_resource type="Animation" id="Animation_ib6pj"]
|
||||||
|
resource_name = "appear"
|
||||||
|
length = 0.3
|
||||||
|
tracks/0/type = "bezier"
|
||||||
|
tracks/0/imported = false
|
||||||
|
tracks/0/enabled = true
|
||||||
|
tracks/0/path = NodePath(".:custom_minimum_size:x")
|
||||||
|
tracks/0/interp = 1
|
||||||
|
tracks/0/loop_wrap = true
|
||||||
|
tracks/0/keys = {
|
||||||
|
"handle_modes": PackedInt32Array(0, 0),
|
||||||
|
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 350, -0.25, 0, 0.25, 0),
|
||||||
|
"times": PackedFloat32Array(0, 0.3)
|
||||||
|
}
|
||||||
|
tracks/1/type = "bezier"
|
||||||
|
tracks/1/imported = false
|
||||||
|
tracks/1/enabled = true
|
||||||
|
tracks/1/path = NodePath("Button:size:x")
|
||||||
|
tracks/1/interp = 1
|
||||||
|
tracks/1/loop_wrap = true
|
||||||
|
tracks/1/keys = {
|
||||||
|
"handle_modes": PackedInt32Array(0, 0),
|
||||||
|
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 350, -0.25, 0, 0.25, 0),
|
||||||
|
"times": PackedFloat32Array(0, 0.3)
|
||||||
|
}
|
||||||
|
tracks/2/type = "bezier"
|
||||||
|
tracks/2/imported = false
|
||||||
|
tracks/2/enabled = true
|
||||||
|
tracks/2/path = NodePath("Button/GradientTexture:position:x")
|
||||||
|
tracks/2/interp = 1
|
||||||
|
tracks/2/loop_wrap = true
|
||||||
|
tracks/2/keys = {
|
||||||
|
"handle_modes": PackedInt32Array(0, 0),
|
||||||
|
"points": PackedFloat32Array(-64, -0.25, 0, 0.25, 0, 278, -0.25, 0, 0.25, 0),
|
||||||
|
"times": PackedFloat32Array(0, 0.3)
|
||||||
|
}
|
||||||
|
tracks/3/type = "bezier"
|
||||||
|
tracks/3/imported = false
|
||||||
|
tracks/3/enabled = true
|
||||||
|
tracks/3/path = NodePath("Button/CloseButton:position:x")
|
||||||
|
tracks/3/interp = 1
|
||||||
|
tracks/3/loop_wrap = true
|
||||||
|
tracks/3/keys = {
|
||||||
|
"handle_modes": PackedInt32Array(0, 0),
|
||||||
|
"points": PackedFloat32Array(-34, -0.25, 0, 0.25, 0, 319, -0.25, 0, 0.25, 0),
|
||||||
|
"times": PackedFloat32Array(0, 0.3)
|
||||||
|
}
|
||||||
|
tracks/4/type = "bezier"
|
||||||
|
tracks/4/imported = false
|
||||||
|
tracks/4/enabled = true
|
||||||
|
tracks/4/path = NodePath("Button/Icon:position:x")
|
||||||
|
tracks/4/interp = 1
|
||||||
|
tracks/4/loop_wrap = true
|
||||||
|
tracks/4/keys = {
|
||||||
|
"handle_modes": PackedInt32Array(0, 0),
|
||||||
|
"points": PackedFloat32Array(-23, -0.25, 0, 0.25, 0, 8, -0.25, 0, 0.25, 0),
|
||||||
|
"times": PackedFloat32Array(0, 0.3)
|
||||||
|
}
|
||||||
|
tracks/5/type = "value"
|
||||||
|
tracks/5/imported = false
|
||||||
|
tracks/5/enabled = true
|
||||||
|
tracks/5/path = NodePath("Button:clip_contents")
|
||||||
|
tracks/5/interp = 1
|
||||||
|
tracks/5/loop_wrap = true
|
||||||
|
tracks/5/keys = {
|
||||||
|
"times": PackedFloat32Array(0),
|
||||||
|
"transitions": PackedFloat32Array(1),
|
||||||
|
"update": 1,
|
||||||
|
"values": [true]
|
||||||
|
}
|
||||||
|
|
||||||
|
[sub_resource type="AnimationLibrary" id="AnimationLibrary_170sd"]
|
||||||
|
_data = {
|
||||||
|
&"appear": SubResource("Animation_ib6pj")
|
||||||
|
}
|
||||||
|
|
||||||
[node name="Tab1" type="Control"]
|
[node name="Tab1" type="Control"]
|
||||||
custom_minimum_size = Vector2(350, 50)
|
custom_minimum_size = Vector2(350, 50)
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 0
|
anchors_preset = 0
|
||||||
|
offset_right = 350.0
|
||||||
|
offset_bottom = 50.0
|
||||||
script = ExtResource("1_q3baj")
|
script = ExtResource("1_q3baj")
|
||||||
|
|
||||||
[node name="GradientTexture" type="TextureRect" parent="."]
|
|
||||||
z_index = 1
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 278.0
|
|
||||||
offset_top = 2.0
|
|
||||||
offset_right = 342.0
|
|
||||||
offset_bottom = 50.0
|
|
||||||
texture = ExtResource("3_q3baj")
|
|
||||||
|
|
||||||
[node name="Button" type="Button" parent="."]
|
[node name="Button" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
z_index = -2
|
z_index = -2
|
||||||
custom_minimum_size = Vector2(350, 50)
|
clip_contents = true
|
||||||
layout_mode = 2
|
layout_mode = 1
|
||||||
offset_right = 350.0
|
offset_right = 350.0
|
||||||
offset_bottom = 50.0
|
offset_bottom = 50.0
|
||||||
focus_mode = 0
|
focus_mode = 0
|
||||||
@@ -50,7 +120,18 @@ icon = ExtResource("5_ib6pj")
|
|||||||
alignment = 0
|
alignment = 0
|
||||||
text_overrun_behavior = 3
|
text_overrun_behavior = 3
|
||||||
|
|
||||||
[node name="Icon" type="TextureRect" parent="."]
|
[node name="GradientTexture" type="TextureRect" parent="Button"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
z_index = 1
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 278.0
|
||||||
|
offset_top = 2.0
|
||||||
|
offset_right = 342.0
|
||||||
|
offset_bottom = 50.0
|
||||||
|
texture = ExtResource("3_q3baj")
|
||||||
|
|
||||||
|
[node name="Icon" type="TextureRect" parent="Button"]
|
||||||
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(23, 23)
|
custom_minimum_size = Vector2(23, 23)
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_left = 8.0
|
offset_left = 8.0
|
||||||
@@ -60,7 +141,8 @@ offset_bottom = 36.0
|
|||||||
texture = ExtResource("6_ib6pj")
|
texture = ExtResource("6_ib6pj")
|
||||||
expand_mode = 1
|
expand_mode = 1
|
||||||
|
|
||||||
[node name="CloseButton" type="Button" parent="."]
|
[node name="CloseButton" type="Button" parent="Button"]
|
||||||
|
unique_name_in_owner = true
|
||||||
z_index = 2
|
z_index = 2
|
||||||
custom_minimum_size = Vector2(34, 34)
|
custom_minimum_size = Vector2(34, 34)
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
@@ -79,6 +161,12 @@ theme_override_styles/normal = ExtResource("7_1ohlo")
|
|||||||
icon = ExtResource("2_pisds")
|
icon = ExtResource("2_pisds")
|
||||||
icon_alignment = 1
|
icon_alignment = 1
|
||||||
|
|
||||||
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
|
libraries = {
|
||||||
|
&"": SubResource("AnimationLibrary_170sd")
|
||||||
|
}
|
||||||
|
speed_scale = 2.0
|
||||||
|
|
||||||
[connection signal="mouse_entered" from="Button" to="." method="_on_button_mouse_entered"]
|
[connection signal="mouse_entered" from="Button" to="." method="_on_button_mouse_entered"]
|
||||||
[connection signal="mouse_exited" from="Button" to="." method="_on_button_mouse_exited"]
|
[connection signal="mouse_exited" from="Button" to="." method="_on_button_mouse_exited"]
|
||||||
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
|
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ extends Control
|
|||||||
signal tab_pressed
|
signal tab_pressed
|
||||||
signal tab_closed
|
signal tab_closed
|
||||||
|
|
||||||
@onready var gradient_texture: TextureRect = $GradientTexture
|
@onready var gradient_texture: TextureRect = %GradientTexture
|
||||||
@onready var button: Button = $Button
|
@onready var button: Button = %Button
|
||||||
@onready var close_button: Button = $CloseButton
|
@onready var close_button: Button = %CloseButton
|
||||||
@onready var icon: TextureRect = $Icon
|
@onready var icon: TextureRect = %Icon
|
||||||
|
@onready var animation: AnimationPlayer = $AnimationPlayer
|
||||||
|
|
||||||
const TAB_GRADIENT: GradientTexture2D = preload("res://Scenes/Styles/TabGradient.tres")
|
const TAB_GRADIENT: GradientTexture2D = preload("res://Scenes/Styles/TabGradient.tres")
|
||||||
const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/TabGradientDefault.tres")
|
const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/TabGradientDefault.tres")
|
||||||
@@ -20,6 +21,7 @@ const CLOSE_BUTTON_NORMAL: StyleBoxFlat = preload("res://Scenes/Styles/CloseButt
|
|||||||
|
|
||||||
var is_active := false
|
var is_active := false
|
||||||
var mouse_over_tab := false
|
var mouse_over_tab := false
|
||||||
|
var loading_tween: Tween
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
add_to_group("tabs")
|
add_to_group("tabs")
|
||||||
@@ -44,6 +46,32 @@ func set_icon(new_icon: Texture) -> void:
|
|||||||
icon.texture = new_icon
|
icon.texture = new_icon
|
||||||
icon.rotation = 0
|
icon.rotation = 0
|
||||||
|
|
||||||
|
func update_icon_from_url(icon_url: String) -> void:
|
||||||
|
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
|
||||||
|
|
||||||
|
loading_tween = create_tween()
|
||||||
|
|
||||||
|
set_icon(LOADER_CIRCLE)
|
||||||
|
|
||||||
|
loading_tween.set_loops()
|
||||||
|
|
||||||
|
icon.pivot_offset = Vector2(11.5, 11.5)
|
||||||
|
loading_tween.tween_method(func(angle):
|
||||||
|
if !is_instance_valid(icon):
|
||||||
|
if loading_tween: loading_tween.kill()
|
||||||
|
return
|
||||||
|
icon.rotation = angle
|
||||||
|
, 0.0, TAU, 1.0)
|
||||||
|
|
||||||
|
var icon_resource = await Network.fetch_image(icon_url)
|
||||||
|
|
||||||
|
# Only update if tab still exists
|
||||||
|
if is_instance_valid(self):
|
||||||
|
set_icon(icon_resource)
|
||||||
|
if loading_tween:
|
||||||
|
loading_tween.kill()
|
||||||
|
loading_tween = null
|
||||||
|
|
||||||
func _on_button_mouse_entered() -> void:
|
func _on_button_mouse_entered() -> void:
|
||||||
mouse_over_tab = true
|
mouse_over_tab = true
|
||||||
if is_active: return
|
if is_active: return
|
||||||
@@ -55,6 +83,9 @@ func _on_button_mouse_exited() -> void:
|
|||||||
gradient_texture.texture = TAB_GRADIENT_DEFAULT
|
gradient_texture.texture = TAB_GRADIENT_DEFAULT
|
||||||
|
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
|
if loading_tween:
|
||||||
|
loading_tween.kill()
|
||||||
|
loading_tween = null
|
||||||
remove_from_group("tabs")
|
remove_from_group("tabs")
|
||||||
|
|
||||||
func _on_button_pressed() -> void:
|
func _on_button_pressed() -> void:
|
||||||
@@ -70,4 +101,6 @@ func _on_button_pressed() -> void:
|
|||||||
|
|
||||||
func _on_close_button_pressed() -> void:
|
func _on_close_button_pressed() -> void:
|
||||||
tab_closed.emit()
|
tab_closed.emit()
|
||||||
|
animation.play("appear", -1, -1.0, true)
|
||||||
|
await animation.animation_finished
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ func create_tab() -> void:
|
|||||||
tabs.append(tab)
|
tabs.append(tab)
|
||||||
tab.tab_pressed.connect(_tab_pressed.bind(index))
|
tab.tab_pressed.connect(_tab_pressed.bind(index))
|
||||||
tab.tab_closed.connect(_tab_closed.bind(index))
|
tab.tab_closed.connect(_tab_closed.bind(index))
|
||||||
|
|
||||||
h_box_container.add_child(tab)
|
h_box_container.add_child(tab)
|
||||||
|
tab.animation.play("appear")
|
||||||
|
|
||||||
set_active_tab(index)
|
set_active_tab(index)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ extends Control
|
|||||||
@onready var tab_container: TabManager = $VBoxContainer/TabContainer
|
@onready var tab_container: TabManager = $VBoxContainer/TabContainer
|
||||||
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
|
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
|
||||||
|
|
||||||
var loading_tween: Tween
|
|
||||||
|
|
||||||
const P = preload("res://Scenes/Tags/p.tscn")
|
const P = preload("res://Scenes/Tags/p.tscn")
|
||||||
const IMG = preload("res://Scenes/Tags/img.tscn")
|
const IMG = preload("res://Scenes/Tags/img.tscn")
|
||||||
const SEPARATOR = preload("res://Scenes/Tags/separator.tscn")
|
const SEPARATOR = preload("res://Scenes/Tags/separator.tscn")
|
||||||
@@ -122,8 +120,7 @@ line breaks
|
|||||||
tab.set_title(title)
|
tab.set_title(title)
|
||||||
|
|
||||||
var icon = parser.get_icon()
|
var icon = parser.get_icon()
|
||||||
set_loading_icon(tab)
|
tab.update_icon_from_url(icon)
|
||||||
call_deferred("update_tab_icon", tab, icon)
|
|
||||||
|
|
||||||
var body = parser.find_first("body")
|
var body = parser.find_first("body")
|
||||||
var i = 0
|
var i = 0
|
||||||
@@ -165,25 +162,6 @@ line breaks
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
func set_loading_icon(tab: Tab) -> void:
|
|
||||||
tab.set_icon(LOADER_CIRCLE)
|
|
||||||
|
|
||||||
loading_tween = create_tween()
|
|
||||||
loading_tween.set_loops()
|
|
||||||
|
|
||||||
var icon = tab.icon
|
|
||||||
icon.pivot_offset = Vector2(11.5, 11.5)
|
|
||||||
loading_tween.tween_method(func(angle): icon.rotation = angle, 0.0, TAU, 1.0)
|
|
||||||
|
|
||||||
func stop_loading_icon() -> void:
|
|
||||||
if loading_tween:
|
|
||||||
loading_tween.kill()
|
|
||||||
loading_tween = null
|
|
||||||
|
|
||||||
func update_tab_icon(tab: Tab, icon: String) -> void:
|
|
||||||
tab.set_icon(await Network.fetch_image(icon))
|
|
||||||
stop_loading_icon()
|
|
||||||
|
|
||||||
func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
|
func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
|
||||||
if element.tag_name == "a":
|
if element.tag_name == "a":
|
||||||
return true
|
return true
|
||||||
|
|||||||
Reference in New Issue
Block a user