diff --git a/flumi/Scenes/Tab.tscn b/flumi/Scenes/Tab.tscn index d51e6cc..45fd895 100644 --- a/flumi/Scenes/Tab.tscn +++ b/flumi/Scenes/Tab.tscn @@ -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"] diff --git a/flumi/Scenes/main.tscn b/flumi/Scenes/main.tscn index 9b8ed90..57e0c98 100644 --- a/flumi/Scenes/main.tscn +++ b/flumi/Scenes/main.tscn @@ -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 diff --git a/flumi/Scripts/OptionButton.gd b/flumi/Scripts/OptionButton.gd index 258aeb6..10e2a0b 100644 --- a/flumi/Scripts/OptionButton.gd +++ b/flumi/Scripts/OptionButton.gd @@ -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 diff --git a/flumi/Scripts/Tab.gd b/flumi/Scripts/Tab.gd index b422344..df7832a 100644 --- a/flumi/Scripts/Tab.gd +++ b/flumi/Scripts/Tab.gd @@ -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() diff --git a/flumi/Scripts/TabContainer.gd b/flumi/Scripts/TabContainer.gd index 3cf8012..397ca70 100644 --- a/flumi/Scripts/TabContainer.gd +++ b/flumi/Scripts/TabContainer.gd @@ -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() diff --git a/flumi/Scripts/main.gd b/flumi/Scripts/main.gd index 8cc1369..36f1a5b 100644 --- a/flumi/Scripts/main.gd +++ b/flumi/Scripts/main.gd @@ -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 TEXT) 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 diff --git a/flumi/project.godot b/flumi/project.godot index ccf6adc..df340cc 100644 --- a/flumi/project.godot +++ b/flumi/project.godot @@ -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) +] +}