Merge branch 'outpoot:main' into main
This commit is contained in:
@@ -215,7 +215,8 @@ func get_element_styles_with_inheritance(element: HTMLElement, event: String = "
|
||||
if element in visited_elements:
|
||||
return {}
|
||||
|
||||
visited_elements.append(element)
|
||||
var new_visited_for_styles = visited_elements.duplicate()
|
||||
new_visited_for_styles.append(element)
|
||||
|
||||
var styles = {}
|
||||
|
||||
@@ -403,7 +404,7 @@ func get_icon() -> String:
|
||||
var icon_element = find_first("icon")
|
||||
return icon_element.get_attribute("src") if icon_element != null else ""
|
||||
|
||||
func process_fonts() -> void:
|
||||
func process_fonts(base_url: String = "") -> void:
|
||||
var font_elements = find_all("font")
|
||||
|
||||
for font_element in font_elements:
|
||||
@@ -412,7 +413,8 @@ func process_fonts() -> void:
|
||||
var weight = font_element.get_attribute("weight", "400")
|
||||
|
||||
if name_str and src:
|
||||
FontManager.register_font(name_str, src, weight)
|
||||
var resolved_src = URLUtils.resolve_url(base_url, src)
|
||||
FontManager.register_font(name_str, resolved_src, weight)
|
||||
|
||||
func get_meta_content(name_: String) -> String:
|
||||
var meta_elements = find_all("meta", "name")
|
||||
@@ -449,7 +451,7 @@ func process_scripts(lua_api: LuaAPI, _lua_vm) -> void:
|
||||
parse_result.external_scripts = []
|
||||
parse_result.external_scripts.append(src)
|
||||
elif not inline_code.is_empty():
|
||||
lua_api.execute_lua_script(inline_code)
|
||||
lua_api.execute_lua_script(inline_code, "<inline script>")
|
||||
|
||||
func process_external_scripts(lua_api: LuaAPI, _lua_vm, base_url: String = "") -> void:
|
||||
if not lua_api or not parse_result.external_scripts or parse_result.external_scripts.is_empty():
|
||||
@@ -460,7 +462,7 @@ func process_external_scripts(lua_api: LuaAPI, _lua_vm, base_url: String = "") -
|
||||
for script_url in parse_result.external_scripts:
|
||||
var script_content = await Network.fetch_external_resource(script_url, base_url)
|
||||
if not script_content.is_empty():
|
||||
lua_api.execute_lua_script(script_content)
|
||||
lua_api.execute_lua_script(script_content, script_url)
|
||||
|
||||
func process_postprocess() -> HTMLParser.HTMLElement:
|
||||
var postprocess_elements = find_all("postprocess")
|
||||
@@ -561,7 +563,7 @@ static func get_bbcode_with_styles(element: HTMLElement, styles: Dictionary, par
|
||||
for child in element.children:
|
||||
var child_styles = styles
|
||||
if parser != null:
|
||||
child_styles = parser.get_element_styles_with_inheritance(child, "", new_visited)
|
||||
child_styles = parser.get_element_styles_with_inheritance(child, "", [])
|
||||
var child_content = HTMLParser.get_bbcode_with_styles(child, child_styles, parser, new_visited)
|
||||
child_content = apply_element_bbcode_formatting(child, child_styles, child_content)
|
||||
text += child_content
|
||||
|
||||
@@ -359,7 +359,8 @@ func _on_gui_input_click(event: InputEvent, subscription: EventSubscription) ->
|
||||
if event is InputEventMouseButton:
|
||||
var mouse_event = event as InputEventMouseButton
|
||||
if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed:
|
||||
_execute_lua_callback(subscription)
|
||||
var mouse_info = _get_element_relative_mouse_position(mouse_event, subscription.element_id)
|
||||
_execute_lua_callback(subscription, [mouse_info])
|
||||
|
||||
func _on_gui_input_mouse_universal(event: InputEvent, signal_node: Node) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
@@ -376,7 +377,8 @@ func _on_gui_input_mouse_universal(event: InputEvent, signal_node: Node) -> void
|
||||
should_trigger = true
|
||||
|
||||
if should_trigger:
|
||||
_execute_lua_callback(subscription)
|
||||
var mouse_info = _get_element_relative_mouse_position(mouse_event, subscription.element_id)
|
||||
_execute_lua_callback(subscription, [mouse_info])
|
||||
|
||||
# Event callback handlers
|
||||
func _on_gui_input_mousemove(event: InputEvent, subscription: EventSubscription) -> void:
|
||||
@@ -441,6 +443,31 @@ func _input(event: InputEvent) -> void:
|
||||
}
|
||||
_execute_lua_callback(subscription, [key_info])
|
||||
|
||||
elif event is InputEventMouseButton:
|
||||
var mouse_event = event as InputEventMouseButton
|
||||
for subscription_id in event_subscriptions:
|
||||
var subscription = event_subscriptions[subscription_id]
|
||||
if subscription.element_id == "body" and subscription.connected_signal == "input":
|
||||
var should_trigger = false
|
||||
match subscription.event_name:
|
||||
"mousedown":
|
||||
should_trigger = mouse_event.pressed
|
||||
"mouseup":
|
||||
should_trigger = not mouse_event.pressed
|
||||
|
||||
if should_trigger:
|
||||
var mouse_info = {"x": 0, "y": 0, "button": mouse_event.button_index}
|
||||
var body_container = _get_body_container()
|
||||
|
||||
if body_container:
|
||||
var control = body_container as Control
|
||||
var global_pos = mouse_event.global_position
|
||||
var element_rect = control.get_global_rect()
|
||||
mouse_info["x"] = global_pos.x - element_rect.position.x
|
||||
mouse_info["y"] = global_pos.y - element_rect.position.y
|
||||
|
||||
_execute_lua_callback(subscription, [mouse_info])
|
||||
|
||||
elif event is InputEventMouseMotion:
|
||||
var mouse_event = event as InputEventMouseMotion
|
||||
for subscription_id in event_subscriptions:
|
||||
@@ -449,29 +476,67 @@ func _input(event: InputEvent) -> void:
|
||||
if subscription.event_name == "mousemove":
|
||||
_handle_mousemove_event(mouse_event, subscription)
|
||||
|
||||
func _handle_mousemove_event(mouse_event: InputEventMouseMotion, subscription: EventSubscription) -> void:
|
||||
# TODO: pass reference instead of hardcoded path
|
||||
var body_container = Engine.get_main_loop().current_scene.website_container
|
||||
|
||||
if body_container.get_parent() is MarginContainer:
|
||||
body_container = body_container.get_parent()
|
||||
func _get_element_relative_mouse_position(mouse_event: InputEvent, element_id: String) -> Dictionary:
|
||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if not dom_node or not dom_node is Control:
|
||||
return {"x": 0, "y": 0}
|
||||
|
||||
var control = dom_node as Control
|
||||
var global_pos: Vector2
|
||||
|
||||
if mouse_event is InputEventMouseButton:
|
||||
global_pos = (mouse_event as InputEventMouseButton).global_position
|
||||
elif mouse_event is InputEventMouseMotion:
|
||||
global_pos = (mouse_event as InputEventMouseMotion).global_position
|
||||
else:
|
||||
return {"x": 0, "y": 0}
|
||||
|
||||
var element_rect = control.get_global_rect()
|
||||
var local_x = global_pos.x - element_rect.position.x
|
||||
var local_y = global_pos.y - element_rect.position.y
|
||||
|
||||
return {
|
||||
"x": local_x,
|
||||
"y": local_y
|
||||
}
|
||||
|
||||
func _handle_mousemove_event(mouse_event: InputEventMouseMotion, subscription: EventSubscription) -> void:
|
||||
var body_container = _get_body_container()
|
||||
if not body_container:
|
||||
return
|
||||
|
||||
var container_rect = body_container.get_global_rect()
|
||||
var local_x = mouse_event.global_position.x - container_rect.position.x
|
||||
var local_y = mouse_event.global_position.y - container_rect.position.y
|
||||
var control = body_container as Control
|
||||
var global_pos = mouse_event.global_position
|
||||
var element_rect = control.get_global_rect()
|
||||
var local_x = global_pos.x - element_rect.position.x
|
||||
var local_y = global_pos.y - element_rect.position.y
|
||||
|
||||
# Only provide coordinates if mouse is within the container bounds
|
||||
if local_x >= 0 and local_y >= 0 and local_x <= container_rect.size.x and local_y <= container_rect.size.y:
|
||||
var mouse_info = {
|
||||
"x": local_x,
|
||||
"y": local_y,
|
||||
"deltaX": mouse_event.relative.x,
|
||||
"deltaY": mouse_event.relative.y
|
||||
}
|
||||
_execute_lua_callback(subscription, [mouse_info])
|
||||
var mouse_info = {
|
||||
"x": local_x,
|
||||
"y": local_y,
|
||||
"deltaX": mouse_event.relative.x,
|
||||
"deltaY": mouse_event.relative.y
|
||||
}
|
||||
_execute_lua_callback(subscription, [mouse_info])
|
||||
|
||||
func _get_body_container() -> Control:
|
||||
# Try to get body from DOM registry first
|
||||
var body_container = dom_parser.parse_result.dom_nodes.get("body", null)
|
||||
|
||||
# We fallback to finding the active website container, as it seems theres a bug where body can be null in this context
|
||||
if not body_container:
|
||||
var main_scene = Engine.get_main_loop().current_scene
|
||||
if main_scene and main_scene.has_method("get_active_website_container"):
|
||||
body_container = main_scene.get_active_website_container()
|
||||
else:
|
||||
body_container = Engine.get_main_loop().current_scene.website_container
|
||||
if body_container and body_container.get_parent() is MarginContainer:
|
||||
body_container = body_container.get_parent()
|
||||
|
||||
if body_container and body_container is Control:
|
||||
return body_container as Control
|
||||
|
||||
return null
|
||||
|
||||
# Input event handlers
|
||||
func _on_input_text_changed(new_text: String, subscription: EventSubscription) -> void:
|
||||
@@ -634,13 +699,13 @@ func get_dom_node(node: Node, purpose: String = "general") -> Node:
|
||||
return node
|
||||
|
||||
# Main execution function
|
||||
func execute_lua_script(code: String):
|
||||
func execute_lua_script(code: String, chunk_name: String = "dostring"):
|
||||
if not threaded_vm.lua_thread or not threaded_vm.lua_thread.is_alive():
|
||||
# Start the thread if it's not running
|
||||
threaded_vm.start_lua_thread(dom_parser, self)
|
||||
|
||||
script_start_time = Time.get_ticks_msec() / 1000.0
|
||||
threaded_vm.execute_script_async(code)
|
||||
threaded_vm.execute_script_async(code, chunk_name)
|
||||
|
||||
func _on_threaded_script_completed(_result: Dictionary):
|
||||
pass
|
||||
@@ -649,7 +714,14 @@ func _on_threaded_script_error(error_message: String):
|
||||
Trace.trace_error("RuntimeError: " + error_message)
|
||||
|
||||
func _on_print_output(message: Dictionary):
|
||||
Trace.get_instance().log_message.emit(message, "lua", Time.get_ticks_msec() / 1000.0)
|
||||
var message_strings: Array[String] = []
|
||||
for part in message.parts:
|
||||
if part.type == "table":
|
||||
message_strings.append(str(part.data))
|
||||
else:
|
||||
message_strings.append(part.data)
|
||||
var formatted_message = "\t".join(message_strings)
|
||||
Trace.get_instance().log_message.emit(formatted_message, "lua", Time.get_ticks_msec() / 1000.0)
|
||||
|
||||
func kill_script_execution():
|
||||
threaded_vm.stop_lua_thread()
|
||||
@@ -1003,3 +1075,31 @@ func _handle_download_request(operation: Dictionary):
|
||||
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
main_node.download_manager.handle_download_request(download_data)
|
||||
|
||||
func _get_element_size_sync(result: Array, element_id: String):
|
||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if dom_node and dom_node is Control:
|
||||
var control = dom_node as Control
|
||||
result[0] = control.size.x
|
||||
result[1] = control.size.y
|
||||
result[2] = true # completion flag
|
||||
return
|
||||
|
||||
# Fallback
|
||||
result[0] = 0.0
|
||||
result[1] = 0.0
|
||||
result[2] = true # completion flag
|
||||
|
||||
func _get_element_position_sync(result: Array, element_id: String):
|
||||
var dom_node = dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if dom_node and dom_node is Control:
|
||||
var control = dom_node as Control
|
||||
result[0] = control.position.x
|
||||
result[1] = control.position.y
|
||||
result[2] = true # completion flag
|
||||
return
|
||||
|
||||
# Fallback
|
||||
result[0] = 0.0
|
||||
result[1] = 0.0
|
||||
result[2] = true # completion flag
|
||||
|
||||
@@ -245,9 +245,9 @@ func execute_lua_command(code: String) -> void:
|
||||
var is_expression = is_likely_expression(code)
|
||||
if is_expression:
|
||||
var wrapped_code = "print(" + code + ")"
|
||||
lua_api.execute_lua_script(wrapped_code)
|
||||
lua_api.execute_lua_script(wrapped_code, "<console>")
|
||||
else:
|
||||
lua_api.execute_lua_script(code)
|
||||
lua_api.execute_lua_script(code, "<console>")
|
||||
return
|
||||
|
||||
add_log_entry("No Lua context available", "error", Time.get_ticks_msec() / 1000.0)
|
||||
|
||||
@@ -153,7 +153,7 @@ func _exit_tree():
|
||||
|
||||
if background_panel and is_instance_valid(background_panel):
|
||||
if background_panel.get_parent():
|
||||
background_panel.get_parent().remove_child(background_panel)
|
||||
background_panel.get_parent().remove_child.call_deferred(background_panel)
|
||||
background_panel.queue_free()
|
||||
|
||||
if dev_tools and is_instance_valid(dev_tools):
|
||||
|
||||
@@ -249,6 +249,8 @@ func _input(_event: InputEvent) -> void:
|
||||
if Input.is_action_just_pressed("FocusSearch"):
|
||||
main.search_bar.grab_focus()
|
||||
main.search_bar.select_all()
|
||||
if Input.is_action_just_pressed("ReloadPage"):
|
||||
main.reload_current_page()
|
||||
|
||||
func _on_new_tab_button_pressed() -> void:
|
||||
create_tab()
|
||||
|
||||
@@ -27,6 +27,10 @@ static func load_font(font_info: Dictionary) -> void:
|
||||
|
||||
if src.begins_with("http://") or src.begins_with("https://"):
|
||||
load_web_font(font_info)
|
||||
elif src.begins_with("gurt://"):
|
||||
load_gurt_font(font_info)
|
||||
else:
|
||||
load_local_font(font_info)
|
||||
|
||||
static func load_web_font(font_info: Dictionary) -> void:
|
||||
var src = font_info["src"]
|
||||
@@ -48,13 +52,8 @@ static func load_web_font(font_info: Dictionary) -> void:
|
||||
font_info["font_resource"] = font
|
||||
loaded_fonts[name] = font
|
||||
|
||||
# Trigger font refresh if callback is available
|
||||
if refresh_callback.is_valid():
|
||||
refresh_callback.call(name)
|
||||
else:
|
||||
print("FontManager: Empty font data received for ", name)
|
||||
else:
|
||||
print("FontManager: Failed to load font ", name, " - HTTP ", response_code)
|
||||
|
||||
if is_instance_valid(temp_parent):
|
||||
temp_parent.queue_free()
|
||||
@@ -65,6 +64,50 @@ static func load_web_font(font_info: Dictionary) -> void:
|
||||
|
||||
http_request.request(src, headers)
|
||||
|
||||
static func load_local_font(font_info: Dictionary) -> void:
|
||||
var src = font_info["src"]
|
||||
var name = font_info["name"]
|
||||
|
||||
if not FileAccess.file_exists(src):
|
||||
return
|
||||
|
||||
var file = FileAccess.open(src, FileAccess.READ)
|
||||
if file == null:
|
||||
return
|
||||
|
||||
var font_data = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
|
||||
if font_data.size() == 0:
|
||||
return
|
||||
|
||||
var font = FontFile.new()
|
||||
font.data = font_data
|
||||
|
||||
font_info["font_resource"] = font
|
||||
loaded_fonts[name] = font
|
||||
|
||||
if refresh_callback.is_valid():
|
||||
refresh_callback.call(name)
|
||||
|
||||
static func load_gurt_font(font_info: Dictionary) -> void:
|
||||
var src = font_info["src"]
|
||||
var name = font_info["name"]
|
||||
|
||||
var font_data = Network.fetch_gurt_resource(src, true)
|
||||
|
||||
if font_data.size() == 0:
|
||||
return
|
||||
|
||||
var font = FontFile.new()
|
||||
font.data = font_data
|
||||
|
||||
font_info["font_resource"] = font
|
||||
loaded_fonts[name] = font
|
||||
|
||||
if refresh_callback.is_valid():
|
||||
refresh_callback.call(name)
|
||||
|
||||
static func get_font(family_name: String) -> Font:
|
||||
if family_name == "sans-serif":
|
||||
var sys_font = SystemFont.new()
|
||||
|
||||
@@ -68,13 +68,18 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
|
||||
if width == "100%":
|
||||
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
node.custom_minimum_size.x = 0
|
||||
if node is PanelContainer and node.get_child_count() > 0:
|
||||
var vbox = node.get_child(0)
|
||||
if vbox is VBoxContainer:
|
||||
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
else:
|
||||
# For other percentages, convert to viewport-relative size
|
||||
var percent = float(width.replace("%", "")) / 100.0
|
||||
var viewport_width = node.get_viewport().get_visible_rect().size.x if node.get_viewport() else 800
|
||||
node.custom_minimum_size.x = viewport_width * percent
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
else:
|
||||
node.custom_minimum_size.x = width
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
@@ -402,7 +407,7 @@ static func apply_margin_styles_to_container(margin_container: MarginContainer,
|
||||
if margin_val != null:
|
||||
margin_container.add_theme_constant_override(theme_key, margin_val)
|
||||
|
||||
static func apply_styles_to_label(label: Control, styles: Dictionary, element: HTMLParser.HTMLElement, parser, text_override: String = "") -> void:
|
||||
static func apply_styles_to_label(label: Control, styles: Dictionary, element: HTMLParser.HTMLElement, parser, text_override: String = "", is_refresh: bool = false) -> void:
|
||||
if label is Button:
|
||||
apply_font_to_button(label, styles)
|
||||
return
|
||||
@@ -410,6 +415,10 @@ static func apply_styles_to_label(label: Control, styles: Dictionary, element: H
|
||||
if not label is RichTextLabel:
|
||||
return
|
||||
|
||||
if not is_refresh and styles.has("font-family") and styles["font-family"] not in ["sans-serif", "serif", "monospace"]:
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
main_node.register_font_dependent_element(label, styles, element, parser)
|
||||
|
||||
var text = text_override if text_override != "" else (element.get_preserved_text() if element.tag_name == "pre" else element.get_bbcode_formatted_text(parser))
|
||||
|
||||
var font_size = 24 # default
|
||||
@@ -418,15 +427,15 @@ static func apply_styles_to_label(label: Control, styles: Dictionary, element: H
|
||||
var font_family = styles["font-family"]
|
||||
var font_resource = FontManager.get_font(font_family)
|
||||
|
||||
# set a sans-serif fallback first
|
||||
if font_family not in ["sans-serif", "serif", "monospace"]:
|
||||
if not FontManager.loaded_fonts.has(font_family):
|
||||
# Font not loaded yet, use sans-serif as fallback
|
||||
if FontManager.loaded_fonts.has(font_family) and font_resource:
|
||||
apply_font_to_label(label, font_resource, styles)
|
||||
else:
|
||||
var fallback_font = FontManager.get_font("sans-serif")
|
||||
apply_font_to_label(label, fallback_font, styles)
|
||||
|
||||
if font_resource:
|
||||
apply_font_to_label(label, font_resource, styles)
|
||||
else:
|
||||
if font_resource:
|
||||
apply_font_to_label(label, font_resource, styles)
|
||||
else:
|
||||
# No custom font family, but check if we need to apply font weight
|
||||
if styles.has("font-thin") or styles.has("font-extralight") or styles.has("font-light") or styles.has("font-normal") or styles.has("font-medium") or styles.has("font-semibold") or styles.has("font-extrabold") or styles.has("font-black"):
|
||||
@@ -567,50 +576,64 @@ static func parse_radius(radius_str: String) -> int:
|
||||
return SizeUtils.parse_radius(radius_str)
|
||||
|
||||
static func apply_font_to_label(label: RichTextLabel, font_resource: Font, styles: Dictionary = {}) -> void:
|
||||
# Create normal font with appropriate weight
|
||||
var normal_font = SystemFont.new()
|
||||
normal_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
if font_resource is FontFile:
|
||||
label.add_theme_font_override("normal_font", font_resource)
|
||||
label.add_theme_font_override("bold_font", font_resource)
|
||||
label.add_theme_font_override("italics_font", font_resource)
|
||||
|
||||
# Set weight based on styles
|
||||
var font_weight = 400 # Default normal weight
|
||||
if styles.has("font-thin"):
|
||||
font_weight = 100
|
||||
elif styles.has("font-extralight"):
|
||||
font_weight = 200
|
||||
elif styles.has("font-light"):
|
||||
font_weight = 300
|
||||
elif styles.has("font-normal"):
|
||||
font_weight = 400
|
||||
elif styles.has("font-medium"):
|
||||
font_weight = 500
|
||||
elif styles.has("font-semibold"):
|
||||
font_weight = 600
|
||||
elif styles.has("font-bold"):
|
||||
font_weight = 700
|
||||
elif styles.has("font-extrabold"):
|
||||
font_weight = 800
|
||||
elif styles.has("font-black"):
|
||||
font_weight = 900
|
||||
elif font_resource is SystemFont:
|
||||
var font_weight = 400
|
||||
if styles.has("font-thin"):
|
||||
font_weight = 100
|
||||
elif styles.has("font-extralight"):
|
||||
font_weight = 200
|
||||
elif styles.has("font-light"):
|
||||
font_weight = 300
|
||||
elif styles.has("font-normal"):
|
||||
font_weight = 400
|
||||
elif styles.has("font-medium"):
|
||||
font_weight = 500
|
||||
elif styles.has("font-semibold"):
|
||||
font_weight = 600
|
||||
elif styles.has("font-bold"):
|
||||
font_weight = 700
|
||||
elif styles.has("font-extrabold"):
|
||||
font_weight = 800
|
||||
elif styles.has("font-black"):
|
||||
font_weight = 900
|
||||
|
||||
var normal_font = SystemFont.new()
|
||||
normal_font.font_names = font_resource.font_names
|
||||
normal_font.font_weight = font_weight
|
||||
label.add_theme_font_override("normal_font", normal_font)
|
||||
|
||||
# Create bold variant
|
||||
var bold_font = SystemFont.new()
|
||||
bold_font.font_names = font_resource.font_names
|
||||
bold_font.font_weight = 700
|
||||
label.add_theme_font_override("bold_font", bold_font)
|
||||
|
||||
# Create italic variant
|
||||
var italic_font = SystemFont.new()
|
||||
italic_font.font_names = font_resource.font_names
|
||||
italic_font.font_italic = true
|
||||
italic_font.font_weight = font_weight
|
||||
label.add_theme_font_override("italics_font", italic_font)
|
||||
|
||||
else:
|
||||
label.add_theme_font_override("normal_font", font_resource)
|
||||
label.add_theme_font_override("bold_font", font_resource)
|
||||
label.add_theme_font_override("italics_font", font_resource)
|
||||
|
||||
normal_font.font_weight = font_weight
|
||||
|
||||
label.add_theme_font_override("normal_font", normal_font)
|
||||
|
||||
var bold_font = SystemFont.new()
|
||||
bold_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
bold_font.font_weight = 700 # Bold weight
|
||||
label.add_theme_font_override("bold_font", bold_font)
|
||||
|
||||
var italic_font = SystemFont.new()
|
||||
italic_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
italic_font.font_italic = true
|
||||
label.add_theme_font_override("italics_font", italic_font)
|
||||
|
||||
var bold_italic_font = SystemFont.new()
|
||||
bold_italic_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
bold_italic_font.font_weight = 700 # Bold weight
|
||||
bold_italic_font.font_italic = true
|
||||
label.add_theme_font_override("bold_italics_font", bold_italic_font)
|
||||
# Handle bold_italics_font
|
||||
if font_resource is FontFile:
|
||||
label.add_theme_font_override("bold_italics_font", font_resource)
|
||||
elif font_resource is SystemFont:
|
||||
var bold_italic_font = SystemFont.new()
|
||||
bold_italic_font.font_names = font_resource.font_names
|
||||
bold_italic_font.font_weight = 700
|
||||
bold_italic_font.font_italic = true
|
||||
label.add_theme_font_override("bold_italics_font", bold_italic_font)
|
||||
|
||||
static func apply_font_to_button(button: Button, styles: Dictionary) -> void:
|
||||
if styles.has("font-family"):
|
||||
|
||||
@@ -142,9 +142,9 @@ func fetch_external_resource(url: String, base_url: String = "") -> String:
|
||||
else:
|
||||
return ""
|
||||
|
||||
func fetch_gurt_resource(url: String) -> String:
|
||||
func fetch_gurt_resource(url: String, as_binary: bool = false):
|
||||
if not GurtProtocol.is_gurt_domain(url):
|
||||
return ""
|
||||
return PackedByteArray() if as_binary else ""
|
||||
|
||||
var gurt_url = url
|
||||
if not gurt_url.begins_with("gurt://"):
|
||||
@@ -184,11 +184,17 @@ func fetch_gurt_resource(url: String) -> String:
|
||||
status_code = response.status_code
|
||||
error_msg += ": " + str(response.status_code) + " " + response.status_message
|
||||
NetworkManager.complete_request(network_request.id, status_code, error_msg, {}, "")
|
||||
return ""
|
||||
return PackedByteArray() if as_binary else ""
|
||||
|
||||
var response_headers = response.headers if response.headers else {}
|
||||
var response_body = response.body.get_string_from_utf8()
|
||||
|
||||
NetworkManager.complete_request(network_request.id, response.status_code, "OK", response_headers, response_body)
|
||||
var response_body = response.body
|
||||
|
||||
return response_body
|
||||
if as_binary:
|
||||
var size_info = "Binary data: " + str(response_body.size()) + " bytes"
|
||||
NetworkManager.complete_request(network_request.id, response.status_code, "OK", response_headers, size_info, response_body)
|
||||
return response_body
|
||||
else:
|
||||
var response_body_str = response_body.get_string_from_utf8()
|
||||
NetworkManager.complete_request(network_request.id, response.status_code, "OK", response_headers, response_body_str)
|
||||
return response_body_str
|
||||
|
||||
@@ -8,6 +8,8 @@ var canvas_height: int = 150
|
||||
var draw_commands: Array = []
|
||||
var context_2d: CanvasContext2D = null
|
||||
var context_shader: CanvasContextShader = null
|
||||
var pending_redraw: bool = false
|
||||
var max_draw_commands: int = 1000
|
||||
|
||||
class CanvasContext2D:
|
||||
var canvas: HTMLCanvas
|
||||
@@ -41,8 +43,7 @@ class CanvasContext2D:
|
||||
"color": color,
|
||||
"transform": current_transform
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
func strokeRect(x: float, y: float, width: float, height: float, color_hex: String = "", stroke_width: float = 0.0):
|
||||
var color = _parse_color(stroke_style if color_hex.is_empty() else color_hex)
|
||||
@@ -57,10 +58,14 @@ class CanvasContext2D:
|
||||
"stroke_width": width_val,
|
||||
"transform": current_transform
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
func clearRect(x: float, y: float, width: float, height: float):
|
||||
if x == 0 and y == 0 and width >= canvas.canvas_width and height >= canvas.canvas_height:
|
||||
canvas.draw_commands.clear()
|
||||
canvas._do_redraw()
|
||||
return
|
||||
|
||||
var cmd = {
|
||||
"type": "clearRect",
|
||||
"x": x,
|
||||
@@ -68,8 +73,7 @@ class CanvasContext2D:
|
||||
"width": width,
|
||||
"height": height
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
func drawCircle(x: float, y: float, radius: float, color_hex: String = "#000000", filled: bool = true):
|
||||
var cmd = {
|
||||
@@ -81,8 +85,7 @@ class CanvasContext2D:
|
||||
"filled": filled,
|
||||
"transform": current_transform
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
func drawText(x: float, y: float, text: String, color_hex: String = "#000000"):
|
||||
var color = _parse_color(fill_style if color_hex == "#000000" else color_hex)
|
||||
@@ -95,8 +98,7 @@ class CanvasContext2D:
|
||||
"font_size": _parse_font_size(font),
|
||||
"transform": current_transform
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
# Path-based drawing functions
|
||||
func beginPath():
|
||||
@@ -149,8 +151,7 @@ class CanvasContext2D:
|
||||
"line_cap": line_cap,
|
||||
"line_join": line_join
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
func fill():
|
||||
if current_path.size() < 3:
|
||||
@@ -161,8 +162,7 @@ class CanvasContext2D:
|
||||
"path": current_path.duplicate(),
|
||||
"color": _parse_color(fill_style)
|
||||
}
|
||||
canvas.draw_commands.append(cmd)
|
||||
canvas.queue_redraw()
|
||||
canvas._add_draw_command(cmd)
|
||||
|
||||
# Transformation functions
|
||||
func save():
|
||||
@@ -328,6 +328,10 @@ func withContext(context_type: String):
|
||||
func _draw():
|
||||
draw_rect(Rect2(Vector2.ZERO, size), Color.TRANSPARENT)
|
||||
|
||||
# Skip if too many commands to prevent frame drops
|
||||
if draw_commands.size() > max_draw_commands * 2:
|
||||
return
|
||||
|
||||
for cmd in draw_commands:
|
||||
match cmd.type:
|
||||
"fillRect":
|
||||
@@ -407,6 +411,31 @@ func _draw():
|
||||
if path.size() > 2:
|
||||
draw_colored_polygon(path, clr)
|
||||
|
||||
func _add_draw_command(cmd: Dictionary):
|
||||
_optimize_command(cmd)
|
||||
|
||||
draw_commands.append(cmd)
|
||||
|
||||
if draw_commands.size() > max_draw_commands:
|
||||
draw_commands = draw_commands.slice(draw_commands.size() - max_draw_commands)
|
||||
|
||||
if not pending_redraw:
|
||||
pending_redraw = true
|
||||
call_deferred("_do_redraw")
|
||||
|
||||
func _optimize_command(cmd: Dictionary):
|
||||
# Remove redundant consecutive clearRect commands
|
||||
if cmd.type == "clearRect" and draw_commands.size() > 0:
|
||||
var last_cmd = draw_commands[-1]
|
||||
if last_cmd.type == "clearRect" and \
|
||||
last_cmd.x == cmd.x and last_cmd.y == cmd.y and \
|
||||
last_cmd.width == cmd.width and last_cmd.height == cmd.height:
|
||||
draw_commands.pop_back()
|
||||
|
||||
func _do_redraw():
|
||||
pending_redraw = false
|
||||
queue_redraw()
|
||||
|
||||
func clear():
|
||||
draw_commands.clear()
|
||||
queue_redraw()
|
||||
_do_redraw()
|
||||
|
||||
@@ -3,8 +3,35 @@ extends RefCounted
|
||||
|
||||
# This file mainly creates operations that are handled by canvas.gd
|
||||
|
||||
static var pending_operations: Dictionary = {}
|
||||
static var batch_timer: SceneTreeTimer = null
|
||||
|
||||
static func emit_canvas_operation(lua_api: LuaAPI, operation: Dictionary) -> void:
|
||||
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
|
||||
var element_id = operation.get("element_id", "")
|
||||
|
||||
if not pending_operations.has(element_id):
|
||||
pending_operations[element_id] = []
|
||||
|
||||
pending_operations[element_id].append(operation)
|
||||
|
||||
if not batch_timer or batch_timer.time_left <= 0:
|
||||
var scene_tree = lua_api.get_tree() if lua_api else Engine.get_main_loop()
|
||||
if scene_tree:
|
||||
batch_timer = scene_tree.create_timer(0.001) # 1ms batch window
|
||||
batch_timer.timeout.connect(_flush_pending_operations.bind(lua_api))
|
||||
|
||||
static func _flush_pending_operations(lua_api: LuaAPI) -> void:
|
||||
if not lua_api or not lua_api.is_inside_tree():
|
||||
pending_operations.clear()
|
||||
return
|
||||
|
||||
for element_id in pending_operations:
|
||||
var operations = pending_operations[element_id]
|
||||
for operation in operations:
|
||||
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
|
||||
|
||||
pending_operations.clear()
|
||||
batch_timer = null
|
||||
|
||||
static func _element_withContext_wrapper(vm: LuauVM) -> int:
|
||||
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||
|
||||
@@ -1054,6 +1054,60 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
|
||||
# Fallback to true (visible by default)
|
||||
vm.lua_pushboolean(true)
|
||||
return 1
|
||||
"size":
|
||||
if lua_api:
|
||||
vm.lua_getfield(1, "_element_id")
|
||||
var element_id: String = vm.lua_tostring(-1)
|
||||
vm.lua_pop(1)
|
||||
|
||||
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if dom_node and dom_node is Control:
|
||||
var result = [0.0, 0.0, false]
|
||||
lua_api.call_deferred("_get_element_size_sync", result, element_id)
|
||||
while not result[2]: # wait for completion flag
|
||||
OS.delay_msec(1)
|
||||
|
||||
vm.lua_newtable()
|
||||
vm.lua_pushnumber(result[0])
|
||||
vm.lua_setfield(-2, "width")
|
||||
vm.lua_pushnumber(result[1])
|
||||
vm.lua_setfield(-2, "height")
|
||||
return 1
|
||||
|
||||
# Fallback to zero size
|
||||
vm.lua_newtable()
|
||||
vm.lua_pushnumber(0)
|
||||
vm.lua_setfield(-2, "width")
|
||||
vm.lua_pushnumber(0)
|
||||
vm.lua_setfield(-2, "height")
|
||||
return 1
|
||||
"position":
|
||||
if lua_api:
|
||||
vm.lua_getfield(1, "_element_id")
|
||||
var element_id: String = vm.lua_tostring(-1)
|
||||
vm.lua_pop(1)
|
||||
|
||||
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
|
||||
if dom_node and dom_node is Control:
|
||||
var result = [0.0, 0.0, false]
|
||||
lua_api.call_deferred("_get_element_position_sync", result, element_id)
|
||||
while not result[2]: # wait for completion flag
|
||||
OS.delay_msec(1)
|
||||
|
||||
vm.lua_newtable()
|
||||
vm.lua_pushnumber(result[0])
|
||||
vm.lua_setfield(-2, "x")
|
||||
vm.lua_pushnumber(result[1])
|
||||
vm.lua_setfield(-2, "y")
|
||||
return 1
|
||||
|
||||
# Fallback to zero position
|
||||
vm.lua_newtable()
|
||||
vm.lua_pushnumber(0)
|
||||
vm.lua_setfield(-2, "x")
|
||||
vm.lua_pushnumber(0)
|
||||
vm.lua_setfield(-2, "y")
|
||||
return 1
|
||||
_:
|
||||
# Check for DOM traversal properties first
|
||||
if lua_api:
|
||||
|
||||
@@ -17,7 +17,7 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
|
||||
var wrapper = func():
|
||||
LuaAudioUtils.mark_user_event()
|
||||
LuaDownloadUtils.mark_user_event()
|
||||
subscription.lua_api._on_event_triggered(subscription)
|
||||
subscription.lua_api._execute_lua_callback(subscription, [{}])
|
||||
signal_node.pressed.connect(wrapper)
|
||||
subscription.connected_signal = "pressed"
|
||||
subscription.connected_node = signal_node if signal_node != subscription.lua_api.get_dom_node(signal_node.get_parent(), "signal") else null
|
||||
@@ -189,6 +189,11 @@ static func connect_body_event(event_name: String, subscription, lua_api) -> boo
|
||||
subscription.connected_signal = "input_mousemove"
|
||||
subscription.connected_node = lua_api
|
||||
return true
|
||||
"mousedown", "mouseup":
|
||||
lua_api.set_process_input(true)
|
||||
subscription.connected_signal = "input"
|
||||
subscription.connected_node = lua_api
|
||||
return true
|
||||
"mouseenter", "mouseexit":
|
||||
var main_container = lua_api.dom_parser.parse_result.dom_nodes.get("body", null)
|
||||
if main_container:
|
||||
|
||||
@@ -56,11 +56,12 @@ func stop_lua_thread():
|
||||
lua_thread.wait_to_finish()
|
||||
lua_thread = null
|
||||
|
||||
func execute_script_async(script_code: String):
|
||||
func execute_script_async(script_code: String, chunk_name: String = "dostring"):
|
||||
queue_mutex.lock()
|
||||
command_queue.append({
|
||||
"type": "execute_script",
|
||||
"code": script_code
|
||||
"code": script_code,
|
||||
"chunk_name": chunk_name
|
||||
})
|
||||
queue_mutex.unlock()
|
||||
thread_semaphore.post()
|
||||
@@ -143,21 +144,28 @@ func _process_command_queue():
|
||||
for command in commands_to_process:
|
||||
match command.type:
|
||||
"execute_script":
|
||||
_execute_script_in_thread(command.code)
|
||||
_execute_script_in_thread(command.code, command.get("chunk_name", "dostring"))
|
||||
"execute_callback":
|
||||
_execute_callback_in_thread(command.callback_ref, command.args)
|
||||
"execute_timeout":
|
||||
_execute_timeout_in_thread(command.timeout_id)
|
||||
|
||||
func _execute_script_in_thread(script_code: String):
|
||||
func _execute_script_in_thread(script_code: String, chunk_name: String = "dostring"):
|
||||
if not lua_vm:
|
||||
call_deferred("_emit_script_error", "Lua VM not initialized")
|
||||
return
|
||||
|
||||
var result = lua_vm.lua_dostring(script_code)
|
||||
# Use load_string with custom chunk name, then lua_pcall
|
||||
var load_result = lua_vm.load_string(script_code, chunk_name)
|
||||
|
||||
if result == lua_vm.LUA_OK:
|
||||
call_deferred("_emit_script_completed", {"success": true})
|
||||
if load_result == lua_vm.LUA_OK:
|
||||
var call_result = lua_vm.lua_pcall(0, 0, 0)
|
||||
if call_result == lua_vm.LUA_OK:
|
||||
call_deferred("_emit_script_completed", {"success": true})
|
||||
else:
|
||||
var error_msg = lua_vm.lua_tostring(-1)
|
||||
lua_vm.lua_pop(1)
|
||||
call_deferred("_emit_script_error", error_msg)
|
||||
else:
|
||||
var error_msg = lua_vm.lua_tostring(-1)
|
||||
lua_vm.lua_pop(1)
|
||||
@@ -257,6 +265,16 @@ func _print_handler(vm: LuauVM) -> int:
|
||||
"count": message_parts.size()
|
||||
}
|
||||
|
||||
# Also call trace.log with the formatted message
|
||||
var message_strings: Array[String] = []
|
||||
for part in message_parts:
|
||||
if part.type == "table":
|
||||
message_strings.append(str(part.data))
|
||||
else:
|
||||
message_strings.append(part.data)
|
||||
var final_message = "\t".join(message_strings)
|
||||
call_deferred("_emit_trace_message", final_message, "log")
|
||||
|
||||
call_deferred("_emit_print_output", print_data)
|
||||
|
||||
return 0
|
||||
@@ -359,6 +377,12 @@ func _setup_threaded_gurt_api():
|
||||
lua_vm.lua_setfield(-2, "on")
|
||||
lua_vm.lua_setfield(-2, "body")
|
||||
|
||||
lua_vm.lua_pushcallable(_gurt_get_width_handler, "gurt.width")
|
||||
lua_vm.lua_setfield(-2, "width")
|
||||
|
||||
lua_vm.lua_pushcallable(_gurt_get_height_handler, "gurt.height")
|
||||
lua_vm.lua_setfield(-2, "height")
|
||||
|
||||
lua_vm.lua_setglobal("gurt")
|
||||
|
||||
func _setup_additional_lua_apis():
|
||||
@@ -499,9 +523,49 @@ func _set_interval_handler(vm: LuauVM) -> int:
|
||||
func get_current_href() -> String:
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
|
||||
if main_node == null:
|
||||
return ""
|
||||
|
||||
return main_node.current_domain
|
||||
|
||||
func _gurt_get_width_handler(vm: LuauVM) -> int:
|
||||
var result = [0.0]
|
||||
call_deferred("_get_width_sync", result)
|
||||
while result[0] == 0.0:
|
||||
OS.delay_msec(1)
|
||||
vm.lua_pushnumber(result[0])
|
||||
return 1
|
||||
|
||||
func _gurt_get_height_handler(vm: LuauVM) -> int:
|
||||
var result = [0.0]
|
||||
call_deferred("_get_height_sync", result)
|
||||
while result[0] == 0.0:
|
||||
OS.delay_msec(1)
|
||||
vm.lua_pushnumber(result[0])
|
||||
return 1
|
||||
|
||||
func _get_width_sync(result: Array):
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
if main_node:
|
||||
return main_node.current_domain
|
||||
return ""
|
||||
var active_tab = main_node.get_active_tab()
|
||||
if active_tab and active_tab.website_container:
|
||||
result[0] = active_tab.website_container.size.x
|
||||
else:
|
||||
result[0] = 1024.0
|
||||
else:
|
||||
result[0] = 1024.0
|
||||
|
||||
func _get_height_sync(result: Array):
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
if main_node:
|
||||
var active_tab = main_node.get_active_tab()
|
||||
if active_tab and active_tab.website_container:
|
||||
result[0] = active_tab.website_container.size.y
|
||||
else:
|
||||
result[0] = 768.0
|
||||
else:
|
||||
result[0] = 768.0
|
||||
|
||||
|
||||
func _gurt_select_handler(vm: LuauVM) -> int:
|
||||
var selector: String = vm.luaL_checkstring(1)
|
||||
|
||||
@@ -52,21 +52,21 @@ static func clear_messages() -> void:
|
||||
_messages.clear()
|
||||
|
||||
static func _lua_trace_log_handler(vm: LuauVM) -> int:
|
||||
var message = vm.luaL_checkstring(1)
|
||||
var message = LuaPrintUtils.lua_value_to_string(vm, 1)
|
||||
vm.lua_getglobal("_trace_log")
|
||||
vm.lua_pushstring(message)
|
||||
vm.lua_call(1, 0)
|
||||
return 0
|
||||
|
||||
static func _lua_trace_warn_handler(vm: LuauVM) -> int:
|
||||
var message = vm.luaL_checkstring(1)
|
||||
var message = LuaPrintUtils.lua_value_to_string(vm, 1)
|
||||
vm.lua_getglobal("_trace_warning")
|
||||
vm.lua_pushstring(message)
|
||||
vm.lua_call(1, 0)
|
||||
return 0
|
||||
|
||||
static func _lua_trace_error_handler(vm: LuauVM) -> int:
|
||||
var message = vm.luaL_checkstring(1)
|
||||
var message = LuaPrintUtils.lua_value_to_string(vm, 1)
|
||||
vm.lua_getglobal("_trace_error")
|
||||
vm.lua_pushstring(message)
|
||||
vm.lua_call(1, 0)
|
||||
|
||||
@@ -388,7 +388,7 @@ func render_content(html_bytes: PackedByteArray) -> void:
|
||||
await parser.process_external_styles(current_domain)
|
||||
|
||||
# Process and load all custom fonts defined in <font> tags
|
||||
parser.process_fonts()
|
||||
parser.process_fonts(current_domain)
|
||||
FontManager.load_all_fonts()
|
||||
|
||||
if parse_result.errors.size() > 0:
|
||||
@@ -810,7 +810,6 @@ func register_font_dependent_element(label: Control, styles: Dictionary, element
|
||||
})
|
||||
|
||||
func refresh_fonts(font_name: String) -> void:
|
||||
# Find all elements that should use this font and refresh them
|
||||
for element_info in font_dependent_elements:
|
||||
var label = element_info["label"]
|
||||
var styles = element_info["styles"]
|
||||
@@ -819,7 +818,7 @@ func refresh_fonts(font_name: String) -> void:
|
||||
|
||||
if styles.has("font-family") and styles["font-family"] == font_name:
|
||||
if is_instance_valid(label):
|
||||
StyleManager.apply_styles_to_label(label, styles, element, parser)
|
||||
StyleManager.apply_styles_to_label(label, styles, element, parser, "", true)
|
||||
|
||||
func get_current_url() -> String:
|
||||
return current_domain if not current_domain.is_empty() else ""
|
||||
|
||||
Reference in New Issue
Block a user