p, pre, br, b, img, separator, i, u, small, mark, code tags
This commit is contained in:
@@ -23,6 +23,18 @@ class HTMLElement:
|
||||
|
||||
func get_id() -> String:
|
||||
return get_attribute("id")
|
||||
|
||||
func get_collapsed_text() -> String:
|
||||
# Collapse whitespace: replace multiple spaces, tabs, newlines with single space
|
||||
var collapsed = text_content.strip_edges()
|
||||
# Replace multiple whitespace characters with single space
|
||||
var regex = RegEx.new()
|
||||
regex.compile("\\s+")
|
||||
return regex.sub(collapsed, " ", true)
|
||||
|
||||
func get_preserved_text() -> String:
|
||||
# For pre tags - preserve all whitespace
|
||||
return text_content
|
||||
|
||||
class ParseResult:
|
||||
var root: HTMLElement
|
||||
@@ -149,6 +161,10 @@ func get_title() -> String:
|
||||
var title_element = find_first("title")
|
||||
return title_element.text_content if title_element != null else ""
|
||||
|
||||
func get_icon() -> String:
|
||||
var icon_element = find_first("icon")
|
||||
return icon_element.get_attribute("src")
|
||||
|
||||
func get_meta_content(name_: String) -> String:
|
||||
var meta_elements = find_all("meta", "name")
|
||||
for element in meta_elements:
|
||||
|
||||
61
Scripts/Network.gd
Normal file
61
Scripts/Network.gd
Normal file
@@ -0,0 +1,61 @@
|
||||
extends Node
|
||||
|
||||
func fetch_image(url: String) -> ImageTexture:
|
||||
var http_request = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
|
||||
var error = http_request.request(url)
|
||||
if error != OK:
|
||||
print("Error making HTTP request: ", error)
|
||||
http_request.queue_free()
|
||||
return null
|
||||
|
||||
var response = await http_request.request_completed
|
||||
|
||||
var result = response[0] # HTTPClient.Result
|
||||
var response_code = response[1] # int
|
||||
var headers = response[2] # PackedStringArray
|
||||
var body = response[3] # PackedByteArray
|
||||
|
||||
http_request.queue_free()
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
|
||||
print("Failed to fetch image. Result: ", result, " Response code: ", response_code)
|
||||
return null
|
||||
|
||||
# Get content type from headers
|
||||
var content_type = ""
|
||||
for header in headers:
|
||||
if header.to_lower().begins_with("content-type:"):
|
||||
content_type = header.split(":")[1].strip_edges().to_lower()
|
||||
break
|
||||
|
||||
var image: Image = Image.new()
|
||||
var load_error
|
||||
|
||||
# Load image based on content type
|
||||
if content_type.contains("png") or url.to_lower().ends_with(".png"):
|
||||
load_error = image.load_png_from_buffer(body)
|
||||
elif content_type.contains("jpeg") or content_type.contains("jpg") or url.to_lower().ends_with(".jpg") or url.to_lower().ends_with(".jpeg"):
|
||||
load_error = image.load_jpg_from_buffer(body)
|
||||
elif content_type.contains("webp") or url.to_lower().ends_with(".webp"):
|
||||
load_error = image.load_webp_from_buffer(body)
|
||||
elif content_type.contains("bmp"):
|
||||
load_error = image.load_bmp_from_buffer(body)
|
||||
elif content_type.contains("tga"):
|
||||
load_error = image.load_tga_from_buffer(body)
|
||||
else:
|
||||
print("Unknown or missing content-type. Attempting bruteforce converting across PNG, JPG and WebP...")
|
||||
load_error = image.load_png_from_buffer(body)
|
||||
if load_error != OK:
|
||||
load_error = image.load_jpg_from_buffer(body)
|
||||
if load_error != OK:
|
||||
load_error = image.load_webp_from_buffer(body)
|
||||
|
||||
if load_error != OK:
|
||||
print("Failed to load image from buffer. Content-Type: ", content_type, " Error: ", load_error)
|
||||
return null
|
||||
|
||||
var texture = ImageTexture.create_from_image(image)
|
||||
|
||||
return texture
|
||||
1
Scripts/Network.gd.uid
Normal file
1
Scripts/Network.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bbfpng3opsnyp
|
||||
@@ -7,6 +7,7 @@ signal tab_closed
|
||||
@onready var gradient_texture: TextureRect = $GradientTexture
|
||||
@onready var button: Button = $Button
|
||||
@onready var close_button: Button = $CloseButton
|
||||
@onready var icon: TextureRect = $Icon
|
||||
|
||||
const TAB_GRADIENT: GradientTexture2D = preload("res://Scenes/Styles/TabGradient.tres")
|
||||
const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/TabGradientDefault.tres")
|
||||
@@ -36,6 +37,13 @@ func _process(_delta):
|
||||
else:
|
||||
close_button.add_theme_stylebox_override("normal", CLOSE_BUTTON_NORMAL)
|
||||
|
||||
func set_title(title: String) -> void:
|
||||
button.text = title
|
||||
|
||||
func set_icon(new_icon: Texture) -> void:
|
||||
icon.texture = new_icon
|
||||
icon.rotation = 0
|
||||
|
||||
func _on_button_mouse_entered() -> void:
|
||||
mouse_over_tab = true
|
||||
if is_active: return
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
class_name TabManager
|
||||
extends HFlowContainer
|
||||
|
||||
var tabs: Array[Tab] = []
|
||||
var active_tab := 0
|
||||
|
||||
@onready var main: Main = $"../.."
|
||||
|
||||
const TAB = preload("res://Scenes/Tab.tscn")
|
||||
|
||||
const TAB_NORMAL: StyleBoxFlat = preload("res://Scenes/Styles/TabNormal.tres")
|
||||
@@ -79,6 +82,9 @@ func create_tab() -> void:
|
||||
h_box_container.add_child(tab)
|
||||
|
||||
set_active_tab(index)
|
||||
|
||||
# WARNING: temporary
|
||||
main.render()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if Input.is_action_just_pressed("NewTab"):
|
||||
|
||||
5
Scripts/Tags/bold.gd
Normal file
5
Scripts/Tags/bold.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=24][b]%s[/b][/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/bold.gd.uid
Normal file
1
Scripts/Tags/bold.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://chdftsciuecfk
|
||||
4
Scripts/Tags/br.gd
Normal file
4
Scripts/Tags/br.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
extends Control
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
custom_minimum_size.y = 24
|
||||
1
Scripts/Tags/br.gd.uid
Normal file
1
Scripts/Tags/br.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csd2kcqixac65
|
||||
5
Scripts/Tags/code.gd
Normal file
5
Scripts/Tags/code.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=20][code]%s[/code][/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/code.gd.uid
Normal file
1
Scripts/Tags/code.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c3dnmqsnj0akr
|
||||
11
Scripts/Tags/img.gd
Normal file
11
Scripts/Tags/img.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
extends TextureRect
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var src = element.get_attribute("src")
|
||||
if !src: return print("Ignoring <img/> tag without \"src\" attribute.")
|
||||
|
||||
texture = await Network.fetch_image(src)
|
||||
|
||||
var texture_size = texture.get_size()
|
||||
custom_minimum_size = texture_size
|
||||
size = texture_size
|
||||
1
Scripts/Tags/img.gd.uid
Normal file
1
Scripts/Tags/img.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgakysfyq773t
|
||||
5
Scripts/Tags/italic.gd
Normal file
5
Scripts/Tags/italic.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=24][i]%s[/i][/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/italic.gd.uid
Normal file
1
Scripts/Tags/italic.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bamty87whxwxi
|
||||
5
Scripts/Tags/mark.gd
Normal file
5
Scripts/Tags/mark.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=24][bgcolor=#FFFF00]%s[/bgcolor][/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/mark.gd.uid
Normal file
1
Scripts/Tags/mark.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c2i8dwyyuufff
|
||||
5
Scripts/Tags/p.gd
Normal file
5
Scripts/Tags/p.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends Control
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=24]%s[/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/p.gd.uid
Normal file
1
Scripts/Tags/p.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cg6kjvlx3an1j
|
||||
5
Scripts/Tags/pre.gd
Normal file
5
Scripts/Tags/pre.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=20][code]%s[/code][/font_size]" % element.get_preserved_text()
|
||||
1
Scripts/Tags/pre.gd.uid
Normal file
1
Scripts/Tags/pre.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://8mam5rvtx72x
|
||||
26
Scripts/Tags/separator.gd
Normal file
26
Scripts/Tags/separator.gd
Normal file
@@ -0,0 +1,26 @@
|
||||
extends Control
|
||||
|
||||
var separator_node: Separator
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var direction = element.get_attribute("direction")
|
||||
|
||||
if direction == "vertical":
|
||||
separator_node = VSeparator.new()
|
||||
separator_node.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
separator_node.custom_minimum_size.x = 2
|
||||
separator_node.layout_mode = 1
|
||||
separator_node.anchors_preset = Control.PRESET_LEFT_WIDE
|
||||
else:
|
||||
separator_node = HSeparator.new()
|
||||
separator_node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
separator_node.custom_minimum_size.y = 2
|
||||
separator_node.layout_mode = 1
|
||||
separator_node.anchors_preset = Control.PRESET_FULL_RECT
|
||||
|
||||
add_child(separator_node)
|
||||
|
||||
# Make the parent control also expand to fill available space
|
||||
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if direction == "vertical":
|
||||
size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
1
Scripts/Tags/separator.gd.uid
Normal file
1
Scripts/Tags/separator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://rol353cupbbf
|
||||
5
Scripts/Tags/small.gd
Normal file
5
Scripts/Tags/small.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=18]%s[/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/small.gd.uid
Normal file
1
Scripts/Tags/small.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://xr7n1lat501x
|
||||
5
Scripts/Tags/underline.gd
Normal file
5
Scripts/Tags/underline.gd
Normal file
@@ -0,0 +1,5 @@
|
||||
extends VBoxContainer
|
||||
|
||||
func init(element: HTMLParser.HTMLElement) -> void:
|
||||
var label: RichTextLabel = $RichTextLabel
|
||||
label.text = "[font_size=24][u]%s[/u][/font_size]" % element.get_collapsed_text()
|
||||
1
Scripts/Tags/underline.gd.uid
Normal file
1
Scripts/Tags/underline.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://x0hphtm8kf12
|
||||
123
Scripts/main.gd
123
Scripts/main.gd
@@ -1,12 +1,32 @@
|
||||
class_name Main
|
||||
extends Control
|
||||
|
||||
func _ready():
|
||||
render()
|
||||
@onready var website_container: Control = %WebsiteContainer
|
||||
@onready var tab_container: TabManager = $VBoxContainer/TabContainer
|
||||
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
|
||||
|
||||
var loading_tween: Tween
|
||||
|
||||
const P = preload("res://Scenes/Tags/p.tscn")
|
||||
const IMG = preload("res://Scenes/Tags/img.tscn")
|
||||
const SEPARATOR = preload("res://Scenes/Tags/separator.tscn")
|
||||
const BOLD = preload("res://Scenes/Tags/bold.tscn")
|
||||
const ITALIC = preload("res://Scenes/Tags/italic.tscn")
|
||||
const UNDERLINE = preload("res://Scenes/Tags/underline.tscn")
|
||||
const SMALL = preload("res://Scenes/Tags/small.tscn")
|
||||
const MARK = preload("res://Scenes/Tags/mark.tscn")
|
||||
const CODE = preload("res://Scenes/Tags/code.tscn")
|
||||
const PRE = preload("res://Scenes/Tags/pre.tscn")
|
||||
const BR = preload("res://Scenes/Tags/br.tscn")
|
||||
|
||||
func render():
|
||||
# Clear existing content
|
||||
for child in website_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
var html_bytes = "<head>
|
||||
<title>My cool web</title>
|
||||
<icon href=\"https://buss.log/icon.ico\"> <!--This image will be the page's icon-->
|
||||
<icon src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png\"> <!--This image will be the page's icon-->
|
||||
|
||||
<meta name=\"theme-color\" content=\"#000000\">
|
||||
<meta name=\"description\" content=\"My cool web\">
|
||||
@@ -16,14 +36,29 @@ func render():
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Hey there!</h1>
|
||||
<img href=\"https://buss.log/rick-astley.png\" />
|
||||
<p>Hey there! this is a test</p>
|
||||
<b>This is bold</b>
|
||||
<i>This is italic</i>
|
||||
<u>This is underline</u>
|
||||
<small>this is small</small>
|
||||
<mark>this is marked</mark>
|
||||
<code>this is code</code>
|
||||
|
||||
<pre>
|
||||
Text in a pre element
|
||||
is displayed in a fixed-width
|
||||
font, and it preserves
|
||||
both spaces and
|
||||
line breaks
|
||||
</pre>
|
||||
|
||||
<script src=\"script2.lua\" />
|
||||
<separator direction=\"horizontal\" />
|
||||
<img src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
|
||||
<separator direction=\"vertical\" />
|
||||
</body>".to_utf8_buffer()
|
||||
|
||||
# Create parser and parse
|
||||
var parser = HTMLParser.new(html_bytes)
|
||||
var parser: HTMLParser = HTMLParser.new(html_bytes)
|
||||
var parse_result = parser.parse()
|
||||
|
||||
print("Total elements found: " + str(parse_result.all_elements.size()))
|
||||
@@ -32,3 +67,77 @@ func render():
|
||||
print("Parse errors: " + str(parse_result.errors))
|
||||
|
||||
# TODO: render the shit on the screen
|
||||
var tab = tab_container.tabs[tab_container.active_tab]
|
||||
|
||||
var title = parser.get_title()
|
||||
tab.set_title(title)
|
||||
|
||||
var icon = parser.get_icon()
|
||||
set_loading_icon(tab)
|
||||
tab.set_icon(await Network.fetch_image(icon))
|
||||
stop_loading_icon()
|
||||
|
||||
var body = parser.find_first("body")
|
||||
for element: HTMLParser.HTMLElement in body.children:
|
||||
match element.tag_name:
|
||||
"p":
|
||||
var p = P.instantiate()
|
||||
p.init(element)
|
||||
website_container.add_child(p)
|
||||
"pre":
|
||||
var pre = PRE.instantiate()
|
||||
pre.init(element)
|
||||
website_container.add_child(pre)
|
||||
"br":
|
||||
var br = BR.instantiate()
|
||||
br.init(element)
|
||||
website_container.add_child(br)
|
||||
"b":
|
||||
var bold = BOLD.instantiate()
|
||||
bold.init(element)
|
||||
website_container.add_child(bold)
|
||||
"img":
|
||||
var img = IMG.instantiate()
|
||||
img.init(element)
|
||||
website_container.add_child(img)
|
||||
"separator":
|
||||
var separator = SEPARATOR.instantiate()
|
||||
separator.init(element)
|
||||
website_container.add_child(separator)
|
||||
"i":
|
||||
var italic = ITALIC.instantiate()
|
||||
italic.init(element)
|
||||
website_container.add_child(italic)
|
||||
"u":
|
||||
var underline = UNDERLINE.instantiate()
|
||||
underline.init(element)
|
||||
website_container.add_child(underline)
|
||||
"small":
|
||||
var small = SMALL.instantiate()
|
||||
small.init(element)
|
||||
website_container.add_child(small)
|
||||
"mark":
|
||||
var mark = MARK.instantiate()
|
||||
mark.init(element)
|
||||
website_container.add_child(mark)
|
||||
"code":
|
||||
var code = CODE.instantiate()
|
||||
code.init(element)
|
||||
website_container.add_child(code)
|
||||
_:
|
||||
print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name)
|
||||
|
||||
func set_loading_icon(tab: Tab) -> void:
|
||||
tab.set_icon(LOADER_CIRCLE)
|
||||
|
||||
loading_tween = create_tween()
|
||||
loading_tween.set_loops()
|
||||
|
||||
var icon = tab.icon
|
||||
icon.pivot_offset = Vector2(11.5, 11.5)
|
||||
loading_tween.tween_method(func(angle): icon.rotation = angle, 0.0, TAU, 1.0)
|
||||
|
||||
func stop_loading_icon() -> void:
|
||||
if loading_tween:
|
||||
loading_tween.kill()
|
||||
loading_tween = null
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,34 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dioe63fr47oq7"
|
||||
path="res://.godot/imported/youtube.png-5feacc6342e9a438cbbd6a63702ea1ce.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Scripts/youtube.png"
|
||||
dest_files=["res://.godot/imported/youtube.png-5feacc6342e9a438cbbd6a63702ea1ce.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Reference in New Issue
Block a user