multi-tabs

This commit is contained in:
Face
2025-08-29 16:24:53 +03:00
parent 5769982bbc
commit 9344c7818a
7 changed files with 380 additions and 97 deletions

View File

@@ -4,7 +4,6 @@
[ext_resource type="Texture2D" uid="uid://gq8g7t4s3ryg" path="res://Assets/Icons/x.svg" id="2_pisds"]
[ext_resource type="StyleBox" uid="uid://fd1nftmqox32" path="res://Scenes/Styles/TabHoverDefault.tres" id="3_kjcxk"]
[ext_resource type="Texture2D" uid="uid://c7u7a1u1v04bx" path="res://Scenes/Styles/TabGradientDefault.tres" id="3_q3baj"]
[ext_resource type="StyleBox" uid="uid://bx3sgro1ageff" path="res://Scenes/Styles/TabDefault.tres" id="4_ib6pj"]
[ext_resource type="Texture2D" uid="uid://dglkjumm1q4lo" path="res://Assets/Icons/23x23-empty.svg" id="5_ib6pj"]
[ext_resource type="Texture2D" uid="uid://bqpx2lgo0yecb" path="res://Assets/Icons/globe.svg" id="6_ib6pj"]
[ext_resource type="StyleBox" uid="uid://dn8exdnk8tjce" path="res://Scenes/Styles/CloseButtonHover.tres" id="6_pisds"]
@@ -12,6 +11,10 @@
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6bp64"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1ohlo"]
content_margin_left = 8.0
content_margin_right = 8.0
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_344ge"]
[sub_resource type="Animation" id="Animation_ib6pj"]
@@ -103,7 +106,7 @@ unique_name_in_owner = true
z_index = -2
clip_contents = true
layout_mode = 1
offset_right = 47.0
offset_right = 350.0
offset_bottom = 50.0
focus_mode = 0
mouse_default_cursor_shape = 2
@@ -113,7 +116,7 @@ theme_override_constants/icon_max_width = 23
theme_override_styles/focus = SubResource("StyleBoxEmpty_6bp64")
theme_override_styles/hover = ExtResource("3_kjcxk")
theme_override_styles/pressed = ExtResource("3_kjcxk")
theme_override_styles/normal = ExtResource("4_ib6pj")
theme_override_styles/normal = SubResource("StyleBoxEmpty_1ohlo")
action_mode = 0
text = "New Tab"
icon = ExtResource("5_ib6pj")
@@ -124,17 +127,18 @@ text_overrun_behavior = 3
unique_name_in_owner = true
z_index = 1
layout_mode = 0
offset_left = -64.0
offset_top = 2.0
offset_bottom = 50.0
offset_left = 252.25
offset_top = 0.995
offset_right = 316.25
offset_bottom = 48.995
texture = ExtResource("3_q3baj")
[node name="Icon" type="TextureRect" parent="Button"]
unique_name_in_owner = true
custom_minimum_size = Vector2(23, 23)
layout_mode = 0
offset_left = -23.0
offset_left = 10.03
offset_top = 13.0
offset_right = 33.03
offset_bottom = 36.0
texture = ExtResource("6_ib6pj")
expand_mode = 1
@@ -144,12 +148,12 @@ unique_name_in_owner = true
z_index = 2
custom_minimum_size = Vector2(34, 34)
layout_mode = 0
offset_left = -34.0
offset_left = 315.39
offset_top = 12.0
offset_right = 349.39
offset_bottom = 46.0
scale = Vector2(0.75, 0.75)
focus_mode = 0
mouse_filter = 2
mouse_default_cursor_shape = 2
theme_override_styles/focus = SubResource("StyleBoxEmpty_344ge")
theme_override_styles/hover = ExtResource("6_pisds")
@@ -167,3 +171,4 @@ speed_scale = 2.0
[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="pressed" from="Button" to="." method="_on_button_pressed"]
[connection signal="pressed" from="Button/CloseButton" to="." method="_on_close_button_pressed"]

View File

@@ -1,4 +1,4 @@
[gd_scene load_steps=36 format=3 uid="uid://bytm7bt2s4ak8"]
[gd_scene load_steps=35 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"]
@@ -85,9 +85,6 @@ expand_margin_left = 40.0
LineEdit/styles/focus = SubResource("StyleBoxFlat_ee4r6")
LineEdit/styles/normal = SubResource("StyleBoxFlat_cbgmd")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_white"]
bg_color = Color(1, 1, 1, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6iyac"]
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
@@ -114,7 +111,7 @@ grow_vertical = 2
[node name="Spacer" type="Control" parent="VBoxContainer"]
layout_mode = 2
[node name="TabContainer" type="HFlowContainer" parent="VBoxContainer"]
[node name="TabContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
script = ExtResource("2_hptm8")
@@ -147,6 +144,10 @@ theme_override_styles/normal = SubResource("StyleBoxFlat_6bp64")
icon = ExtResource("5_ynf5e")
icon_alignment = 1
[node name="Spacer3" type="Control" parent="VBoxContainer/TabContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Spacer2" type="Control" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
@@ -284,19 +285,6 @@ layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="WebsiteBackground" type="Panel" parent="."]
unique_name_in_owner = true
z_index = -1
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 122.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_white")
[node name="Panel" type="Panel" parent="."]
z_index = -5
layout_mode = 1

View File

@@ -1,7 +1,6 @@
extends Button
@onready var tab_container: TabManager = $"../../TabContainer"
@onready var website_background: Panel = %WebsiteBackground
func _on_pressed() -> void:
%OptionsMenu.show()
@@ -15,4 +14,4 @@ func _on_options_menu_id_pressed(id: int) -> void:
# TODO: handle incognito
OS.create_process(OS.get_executable_path(), ["--incognito"])
if id == 4: # history
website_background.modulate = Constants.SECONDARY_COLOR
modulate = Constants.SECONDARY_COLOR

View File

@@ -9,6 +9,7 @@ signal tab_closed
@onready var close_button: Button = %CloseButton
@onready var icon: TextureRect = %Icon
@onready var animation: AnimationPlayer = $AnimationPlayer
var appear_tween: Tween
const TAB_GRADIENT: GradientTexture2D = preload("res://Scenes/Styles/TabGradient.tres")
const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/TabGradientDefault.tres")
@@ -19,10 +20,25 @@ const TAB_DEFAULT: StyleBoxFlat = preload("res://Scenes/Styles/TabDefault.tres")
const CLOSE_BUTTON_HOVER: StyleBoxFlat = preload("res://Scenes/Styles/CloseButtonHover.tres")
const CLOSE_BUTTON_NORMAL: StyleBoxFlat = preload("res://Scenes/Styles/CloseButtonNormal.tres")
const CLOSE_BUTTON_HIDE_THRESHOLD := 100
const TEXT_HIDE_THRESHOLD := 50
const GRADIENT_WIDTH := 64
const GRADIENT_OFFSET := 72
const CLOSE_BUTTON_OFFSET := 34
const ICON_OFFSET := 8
const APPEAR_ANIMATION_DURATION := 0.25
var is_active := false
var mouse_over_tab := false
var loading_tween: Tween
var scroll_container: ScrollContainer = null
var website_container: VBoxContainer = null
var background_panel: PanelContainer = null
var lua_apis: Array[LuaAPI] = []
var current_url: String = ""
var has_content: bool = false
func _ready():
add_to_group("tabs")
gradient_texture.texture = gradient_texture.texture.duplicate()
@@ -41,6 +57,7 @@ func _process(_delta):
func set_title(title: String) -> void:
button.text = title
button.set_meta("original_text", title)
func set_icon(new_icon: Texture) -> void:
icon.texture = new_icon
@@ -52,7 +69,6 @@ func update_icon_from_url(icon_url: String) -> void:
set_icon(GLOBE_ICON)
return
# Load the icon in the background
var icon_resource = await Network.fetch_image(icon_url)
if is_instance_valid(self) and icon_resource:
@@ -98,24 +114,104 @@ func _exit_tree():
if loading_tween:
loading_tween.kill()
loading_tween = null
for lua_api in lua_apis:
if is_instance_valid(lua_api):
lua_api.kill_script_execution()
lua_api.queue_free()
lua_apis.clear()
if scroll_container and is_instance_valid(scroll_container):
if scroll_container.get_parent():
scroll_container.get_parent().remove_child(scroll_container)
scroll_container.queue_free()
if background_panel and is_instance_valid(background_panel):
if background_panel.get_parent():
background_panel.get_parent().remove_child(background_panel)
background_panel.queue_free()
remove_from_group("tabs")
func _enter_tree() -> void:
$AnimationPlayer.play("appear")
func init_scene(parent_container: Control) -> void:
if not scroll_container:
background_panel = PanelContainer.new()
background_panel.name = "Tab_Background_" + str(get_instance_id())
background_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
background_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
var style_box = StyleBoxFlat.new()
style_box.bg_color = Color(1, 1, 1, 1) # White background
background_panel.add_theme_stylebox_override("panel", style_box)
scroll_container = ScrollContainer.new()
scroll_container.name = "Tab_ScrollContainer_" + str(get_instance_id())
scroll_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
website_container = VBoxContainer.new()
website_container.name = "Tab_WebsiteContainer_" + str(get_instance_id())
website_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
website_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
website_container.add_theme_constant_override("separation", 22)
parent_container.call_deferred("add_child", background_panel)
background_panel.call_deferred("add_child", scroll_container)
scroll_container.call_deferred("add_child", website_container)
background_panel.visible = is_active
func show_content() -> void:
if background_panel:
background_panel.visible = true
func play_appear_animation(target_width: float) -> void:
var should_hide_close = target_width < CLOSE_BUTTON_HIDE_THRESHOLD
var should_hide_text = target_width < TEXT_HIDE_THRESHOLD
close_button.visible = not should_hide_close
button.text = "" if should_hide_text else button.get_meta("original_text", "New Tab")
var should_show_gradient = not should_hide_text and not should_hide_close
gradient_texture.visible = should_show_gradient
if should_show_gradient:
gradient_texture.size.x = GRADIENT_WIDTH
gradient_texture.position.x = target_width - GRADIENT_OFFSET
if not should_hide_close:
close_button.position.x = target_width - CLOSE_BUTTON_OFFSET
icon.position.x = ICON_OFFSET
custom_minimum_size.x = 0.0
size.x = 0.0
button.custom_minimum_size.x = 0.0
button.size.x = 0.0
if appear_tween:
appear_tween.kill()
appear_tween = create_tween()
appear_tween.set_ease(Tween.EASE_OUT)
appear_tween.set_trans(Tween.TRANS_CUBIC)
appear_tween.parallel().tween_property(self, "custom_minimum_size:x", target_width, APPEAR_ANIMATION_DURATION)
appear_tween.parallel().tween_property(self, "size:x", target_width, APPEAR_ANIMATION_DURATION)
appear_tween.parallel().tween_property(button, "custom_minimum_size:x", target_width, APPEAR_ANIMATION_DURATION)
appear_tween.parallel().tween_property(button, "size:x", target_width, APPEAR_ANIMATION_DURATION)
func _on_button_pressed() -> void:
# Check if click was on close button area
var mouse_pos = get_global_mouse_position()
var close_button_rect = Rect2(close_button.global_position, close_button.size * close_button.scale)
if close_button_rect.has_point(mouse_pos):
_on_close_button_pressed()
else:
# Handle tab button click
tab_pressed.emit()
tab_pressed.emit()
func _on_close_button_pressed() -> void:
var close_tween = create_tween()
close_tween.set_ease(Tween.EASE_IN)
close_tween.set_trans(Tween.TRANS_CUBIC)
close_tween.parallel().tween_property(self, "custom_minimum_size:x", 0.0, 0.15)
close_tween.parallel().tween_property(self, "size:x", 0.0, 0.15)
close_tween.parallel().tween_property(button, "custom_minimum_size:x", 0.0, 0.15)
close_tween.parallel().tween_property(button, "size:x", 0.0, 0.15)
await close_tween.finished
tab_closed.emit()
animation.play("appear", -1, -1.0, true)
await animation.animation_finished
queue_free()

View File

@@ -1,5 +1,5 @@
class_name TabManager
extends HFlowContainer
extends HBoxContainer
var tabs: Array[Tab] = []
var active_tab := 0
@@ -19,13 +19,38 @@ const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/Tab
@onready var h_box_container: HBoxContainer = $HBoxContainer
const MIN_TAB_WIDTH = 24 # Minimum width (icon only)
const MIN_TAB_WIDTH_WITH_CLOSE = 60 # Minimum width when close button is visible
const MAX_TAB_WIDTH = 320 # Max width
const CLOSE_BUTTON_HIDE_THRESHOLD = 100 # When to hide close button
const TEXT_HIDE_THRESHOLD = 50 # When to hide text
const POPUP_BUTTON_WIDTH = 50 # Width of + button
const NEW_TAB_BUTTON_WIDTH = 50 # Width of new tab button
const OTHER_UI_PADDING = 200 # Space for other UI elements
func _ready() -> void:
tabs.assign(get_tree().get_nodes_in_group("tabs"))
call_deferred("_initialize_tab_containers")
set_active_tab(0)
for i in tabs.size():
tabs[i].tab_pressed.connect(_tab_pressed.bind(i))
tabs[i].tab_closed.connect(_tab_closed.bind(i))
get_viewport().size_changed.connect(_on_viewport_resized)
call_deferred("update_tab_widths")
call_deferred("_delayed_update")
func _initialize_tab_containers() -> void:
for tab in tabs:
trigger_init_scene(tab)
func trigger_init_scene(tab: Tab) -> void:
var main_vbox = main.get_node("VBoxContainer")
tab.init_scene(main_vbox)
func _tab_pressed(index: int) -> void:
set_active_tab(index)
@@ -56,34 +81,150 @@ func _tab_closed(index: int) -> void:
tabs[i].tab_closed.connect(_tab_closed.bind(i))
set_active_tab(active_tab)
update_tab_widths()
func _on_viewport_resized() -> void:
update_tab_widths()
func _delayed_update() -> void:
update_tab_widths()
func update_tab_widths() -> void:
if tabs.is_empty():
return
var viewport_width = get_viewport().get_visible_rect().size.x
var available_width = viewport_width - POPUP_BUTTON_WIDTH - NEW_TAB_BUTTON_WIDTH - OTHER_UI_PADDING
var tab_width = available_width / float(tabs.size())
tab_width = clamp(tab_width, MIN_TAB_WIDTH, MAX_TAB_WIDTH)
var should_hide_close = tab_width < CLOSE_BUTTON_HIDE_THRESHOLD
var should_hide_text = tab_width < TEXT_HIDE_THRESHOLD
h_box_container.custom_minimum_size.x = 0
h_box_container.size.x = 0
for tab in tabs:
if tab.appear_tween and tab.appear_tween.is_valid():
continue
tab.custom_minimum_size.x = tab_width
tab.size.x = tab_width
tab.button.custom_minimum_size.x = tab_width
tab.button.size.x = tab_width
tab.close_button.visible = not should_hide_close
tab.button.text = "" if should_hide_text else tab.button.get_meta("original_text", tab.button.text)
if not tab.button.has_meta("original_text"):
tab.button.set_meta("original_text", tab.button.text)
update_tab_internal_elements(tab, tab_width, should_hide_close, should_hide_text)
func calculate_visible_tab_count(available_width: float) -> int:
var all_tabs_width = calculate_tab_width(available_width, tabs.size())
if all_tabs_width >= MIN_TAB_WIDTH:
return tabs.size()
for tab_count in range(tabs.size(), 0, -1):
var tab_width = calculate_tab_width(available_width, tab_count)
if tab_width >= MIN_TAB_WIDTH:
return tab_count
return max(1, tabs.size())
func calculate_tab_width(available_width: float, tab_count: int) -> float:
if tab_count == 0:
return MAX_TAB_WIDTH
var ideal_width = available_width / tab_count
return clamp(ideal_width, MIN_TAB_WIDTH, MAX_TAB_WIDTH)
func get_hidden_tabs() -> Array[Tab]:
var hidden_tabs: Array[Tab] = []
for tab in tabs:
if not tab.visible:
hidden_tabs.append(tab)
return hidden_tabs
func has_hidden_tabs() -> bool:
return get_hidden_tabs().size() > 0
func update_tab_internal_elements(tab: Tab, width: float, hide_close_button: bool = false, hide_text: bool = false) -> void:
var should_show_gradient = not hide_text and not hide_close_button
tab.gradient_texture.visible = should_show_gradient
if should_show_gradient:
var gradient_start_offset = 72
var gradient_width = 64
var gradient_start_x = width - gradient_start_offset
tab.gradient_texture.position.x = gradient_start_x
tab.gradient_texture.size.x = gradient_width
if not hide_close_button:
var close_button_x = width - 34
tab.close_button.position.x = close_button_x
func set_active_tab(index: int) -> void:
# old tab
tabs[active_tab].is_active = false
tabs[active_tab].button.add_theme_stylebox_override("normal", TAB_DEFAULT)
tabs[active_tab].button.add_theme_stylebox_override("pressed", TAB_DEFAULT)
tabs[active_tab].button.add_theme_stylebox_override("hover", TAB_HOVER_DEFAULT)
tabs[active_tab].gradient_texture.texture = TAB_GRADIENT_DEFAULT
# new tab
if index < 0 or index >= tabs.size():
return
if active_tab >= 0 and active_tab < tabs.size():
tabs[active_tab].is_active = false
tabs[active_tab].button.add_theme_stylebox_override("normal", TAB_DEFAULT)
tabs[active_tab].button.add_theme_stylebox_override("pressed", TAB_DEFAULT)
tabs[active_tab].button.add_theme_stylebox_override("hover", TAB_HOVER_DEFAULT)
tabs[active_tab].gradient_texture.texture = TAB_GRADIENT_DEFAULT
if tabs[active_tab].background_panel:
tabs[active_tab].background_panel.visible = false
tabs[index].is_active = true
tabs[index].button.add_theme_stylebox_override("normal", TAB_NORMAL)
tabs[index].button.add_theme_stylebox_override("pressed", TAB_NORMAL)
tabs[index].button.add_theme_stylebox_override("hover", TAB_NORMAL)
tabs[index].gradient_texture.texture = TAB_GRADIENT
tabs[index].show_content()
if not tabs[index].website_container:
if main:
trigger_init_scene(tabs[index])
active_tab = index
if main and main.search_bar:
if tabs[index].has_content:
main.current_domain = tabs[index].current_url
var display_text = main.current_domain
if display_text.begins_with("gurt://"):
display_text = display_text.substr(7)
main.search_bar.text = display_text
else:
main.current_domain = ""
main.search_bar.text = ""
main.search_bar.grab_focus()
func create_tab() -> void:
var index = tabs.size();
var tab = TAB.instantiate()
tabs.append(tab)
tab.tab_pressed.connect(_tab_pressed.bind(index))
tab.tab_closed.connect(_tab_closed.bind(index))
var viewport_width = get_viewport().get_visible_rect().size.x
var available_width = viewport_width - POPUP_BUTTON_WIDTH - NEW_TAB_BUTTON_WIDTH - OTHER_UI_PADDING
var visible_count = calculate_visible_tab_count(available_width)
var tab_width = calculate_tab_width(available_width, visible_count)
h_box_container.add_child(tab)
tab.play_appear_animation(tab_width)
set_active_tab(index)
await get_tree().process_frame
update_tab_widths()
trigger_init_scene(tab)
# WARNING: temporary
main.render()
@@ -98,6 +239,9 @@ func _input(_event: InputEvent) -> void:
if Input.is_action_just_pressed("PreviousTab"):
var prev_tab = (active_tab - 1 + tabs.size()) % tabs.size()
set_active_tab(prev_tab - 1)
if Input.is_action_just_pressed("FocusSearch"):
main.search_bar.grab_focus()
main.search_bar.select_all()
func _on_new_tab_button_pressed() -> void:
create_tab()

View File

@@ -2,7 +2,6 @@ class_name Main
extends Control
@onready var website_container: Control = %WebsiteContainer
@onready var website_background: Control = %WebsiteBackground
@onready var tab_container: TabManager = $VBoxContainer/TabContainer
@onready var search_bar: LineEdit = $VBoxContainer/HBoxContainer/LineEdit
@@ -55,6 +54,10 @@ func _ready():
CertificateManager.initialize()
var original_scroll = website_container.get_parent()
if original_scroll:
original_scroll.visible = false
call_deferred("render")
var current_domain = "" # Store current domain for display
@@ -170,34 +173,49 @@ func render() -> void:
render_content(Constants.HTML_CONTENT)
func render_content(html_bytes: PackedByteArray) -> void:
var active_tab = get_active_tab()
var target_container: Control
var existing_lua_apis = []
for child in get_children():
if child is LuaAPI:
existing_lua_apis.append(child)
if active_tab and active_tab.website_container:
target_container = active_tab.website_container
else:
target_container = website_container
if not target_container:
print("Error: No container available for rendering")
return
for lua_api in existing_lua_apis:
lua_api.kill_script_execution()
remove_child(lua_api)
lua_api.queue_free()
if active_tab:
var existing_tab_lua_apis = active_tab.lua_apis
for lua_api in existing_tab_lua_apis:
if is_instance_valid(lua_api):
lua_api.kill_script_execution()
remove_child(lua_api)
lua_api.queue_free()
active_tab.lua_apis.clear()
else:
var existing_lua_apis = []
for child in get_children():
if child is LuaAPI:
existing_lua_apis.append(child)
for lua_api in existing_lua_apis:
lua_api.kill_script_execution()
remove_child(lua_api)
lua_api.queue_free()
# Clear existing content
for child in website_container.get_children():
if target_container.get_parent() and target_container.get_parent().name == "BodyMarginContainer":
var body_margin_container = target_container.get_parent()
var scroll_container = body_margin_container.get_parent()
if scroll_container:
body_margin_container.remove_child(target_container)
scroll_container.remove_child(body_margin_container)
body_margin_container.queue_free()
scroll_container.add_child(target_container)
for child in target_container.get_children():
child.queue_free()
var current_parent = website_container.get_parent()
if current_parent and current_parent.name == "BodyMarginContainer":
var original_parent = current_parent.get_parent()
var container_index = current_parent.get_index()
current_parent.remove_child(website_container)
original_parent.remove_child(current_parent)
current_parent.queue_free()
original_parent.add_child(website_container)
original_parent.move_child(website_container, container_index)
font_dependent_elements.clear()
FontManager.clear_fonts()
FontManager.set_refresh_callback(refresh_fonts)
@@ -217,7 +235,7 @@ func render_content(html_bytes: PackedByteArray) -> void:
if parse_result.errors.size() > 0:
print("Parse errors: " + str(parse_result.errors))
var tab = tab_container.tabs[tab_container.active_tab]
var tab = active_tab
var title = parser.get_title()
tab.set_title(title)
@@ -228,15 +246,19 @@ func render_content(html_bytes: PackedByteArray) -> void:
var body = parser.find_first("body")
if body:
StyleManager.apply_body_styles(body, parser, website_container, website_background)
var background_panel = active_tab.background_panel
parser.register_dom_node(body, website_container)
StyleManager.apply_body_styles(body, parser, target_container, background_panel)
parser.register_dom_node(body, target_container)
var scripts = parser.find_all("script")
var lua_api = null
if scripts.size() > 0:
lua_api = LuaAPI.new()
add_child(lua_api)
if active_tab:
active_tab.lua_apis.append(lua_api)
var i = 0
if body:
@@ -255,7 +277,7 @@ func render_content(html_bytes: PackedByteArray) -> void:
hbox.add_theme_constant_override("separation", 4)
for inline_element in inline_elements:
var inline_node = await create_element_node(inline_element, parser)
var inline_node = await create_element_node(inline_element, parser, target_container)
if inline_node:
# Input elements register their own DOM nodes in their init() function
@@ -269,10 +291,10 @@ func render_content(html_bytes: PackedByteArray) -> void:
else:
print("Failed to create inline element node: ", inline_element.tag_name)
safe_add_child(website_container, hbox)
safe_add_child(target_container, hbox)
continue
var element_node = await create_element_node(element, parser)
var element_node = await create_element_node(element, parser, target_container)
if element_node:
# Input elements register their own DOM nodes in their init() function
@@ -281,7 +303,7 @@ func render_content(html_bytes: PackedByteArray) -> void:
# ul/ol handle their own adding
if element.tag_name != "ul" and element.tag_name != "ol":
safe_add_child(website_container, element_node)
safe_add_child(target_container, element_node)
if contains_hyperlink(element):
if element_node is RichTextLabel:
@@ -297,6 +319,9 @@ func render_content(html_bytes: PackedByteArray) -> void:
parser.process_scripts(lua_api, null)
if parse_result.external_scripts and not parse_result.external_scripts.is_empty():
await parser.process_external_scripts(lua_api, null, current_domain)
active_tab.current_url = current_domain
active_tab.has_content = true
static func safe_add_child(parent: Node, child: Node) -> void:
if child.get_parent():
@@ -321,7 +346,7 @@ func is_text_only_element(element: HTMLParser.HTMLElement) -> bool:
return false
func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser, container: Control = null) -> Control:
var styles = parser.get_element_styles_with_inheritance(element, "", [])
var hover_styles = parser.get_element_styles_with_inheritance(element, "hover", [])
var is_flex_container = styles.has("display") and ("flex" in styles["display"])
@@ -333,7 +358,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
# If this is an inline element AND not a flex or grid container, do NOT recursively add child nodes for its children.
# Only create a node for the outermost inline group; nested inline tags are handled by BBCode.
if element.is_inline_element() and not is_flex_container and not is_grid_container:
final_node = await create_element_node_internal(element, parser)
final_node = await create_element_node_internal(element, parser, container if container else get_active_website_container())
if not final_node:
return null
final_node = StyleManager.apply_element_styles(final_node, element, parser)
@@ -384,28 +409,29 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
if element.tag_name == "ul" or element.tag_name == "ol":
final_node.flex_direction = FlexContainer.FlexDirection.Column
website_container.add_child(final_node)
var active_container = container if container else get_active_website_container()
active_container.add_child(final_node)
var temp_list = UL.instantiate() if element.tag_name == "ul" else OL.instantiate()
website_container.add_child(temp_list)
active_container.add_child(temp_list)
await temp_list.init(element, parser)
for child in temp_list.get_children():
temp_list.remove_child(child)
container_for_children.add_child(child)
website_container.remove_child(temp_list)
active_container.remove_child(temp_list)
temp_list.queue_free()
# If the element itself has text (like <span style="flex">TEXT</span>)
elif not element.text_content.is_empty():
var new_node = await create_element_node_internal(element, parser)
var new_node = await create_element_node_internal(element, parser, container if container else get_active_website_container())
if new_node:
container_for_children.add_child(new_node)
# For flex divs, we're done - no additional node creation needed
elif element.tag_name == "div":
pass
else:
final_node = await create_element_node_internal(element, parser)
final_node = await create_element_node_internal(element, parser, container if container else get_active_website_container())
if not final_node:
return null # Unsupported tag
@@ -456,7 +482,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
# Only add child nodes if the child is NOT an inline element
# UNLESS the parent is a flex or grid container (inline elements become flex/grid items)
if not child_element.is_inline_element() or is_flex_container or is_grid_container:
var child_node = await create_element_node(child_element, parser)
var child_node = await create_element_node(child_element, parser, container)
if child_node and is_instance_valid(container_for_children):
# Input elements register their own DOM nodes in their init() function
if child_element.tag_name not in ["input", "textarea", "select", "button", "audio"]:
@@ -471,7 +497,7 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
return final_node
func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLParser, container: Control = null) -> Control:
var node: Control = null
match element.tag_name:
@@ -512,7 +538,7 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP
# Manually process children for non-flex forms
for child_element in element.children:
var child_node = await create_element_node(child_element, parser)
var child_node = await create_element_node(child_element, parser, container)
if child_node:
safe_add_child(node, child_node)
"input":
@@ -526,12 +552,14 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP
node.init(element, parser)
"ul":
node = UL.instantiate()
website_container.add_child(node)
var ul_container = container if container else website_container
ul_container.add_child(node)
await node.init(element, parser)
return node
"ol":
node = OL.instantiate()
website_container.add_child(node)
var ol_container = container if container else website_container
ol_container.add_child(node)
await node.init(element, parser)
return node
"li":
@@ -612,3 +640,21 @@ func reload_current_page() -> void:
func navigate_to_url(url: String) -> void:
var resolved_url = resolve_url(url)
_on_search_submitted(resolved_url)
func update_search_bar_from_current_domain() -> void:
if not search_bar.has_focus() and not current_domain.is_empty():
var display_text = current_domain
if display_text.begins_with("gurt://"):
display_text = display_text.substr(7)
search_bar.text = display_text
func get_active_tab() -> Tab:
if tab_container.active_tab >= 0 and tab_container.active_tab < tab_container.tabs.size():
return tab_container.tabs[tab_container.active_tab]
return null
func get_active_website_container() -> Control:
var active_tab = get_active_tab()
if active_tab:
return active_tab.website_container
return website_container # fallback to original container

View File

@@ -67,3 +67,8 @@ PreviousTab={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
FocusSearch={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}