diff --git a/addons/SIsilicon.3d.text/3d_text_plugin.gd b/addons/SIsilicon.3d.text/3d_text_plugin.gd
deleted file mode 100644
index 240a584..0000000
--- a/addons/SIsilicon.3d.text/3d_text_plugin.gd
+++ /dev/null
@@ -1,64 +0,0 @@
-tool
-extends EditorPlugin
-
-const Label3D = preload("label_3d.gd")
-
-var converter_button : Button
-var edited_node : Label3D
-
-func _enter_tree():
- yield(get_tree(), "idle_frame")
-
- add_custom_type(
- "Label3D", "Spatial",
- Label3D,
- preload("icon_label_3d.svg")
- )
-
- if not converter_button:
- converter_button = preload("label_3d_converter.tscn").instance()
- add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, converter_button)
- converter_button.connect("mesh_generated", self, "generate_mesh")
- converter_button.hide()
-
- print("3d text plugin added to project.")
-
-
-func _exit_tree():
- remove_custom_type("Label3D")
- remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, converter_button)
-
- print("3d text plugin removed from project.")
-
-
-func handles(object : Object) -> bool:
- var handle = object is Label3D
-
- if not handle:
- converter_button.hide()
-
- return handle
-
-
-func edit(object):
- edited_node = object
- if edited_node is Label3D:
- converter_button.show()
- converter_button.label3d = object
- else:
- converter_button.hide()
-
-
-func clear():
- edited_node = null
- converter_button.hide()
-
-func generate_mesh(mesh_inst):
- var undo_redo = get_undo_redo()
- undo_redo.create_action("Convert Text")
-
- undo_redo.add_do_method(edited_node.get_parent(), "add_child", mesh_inst)
- undo_redo.add_undo_method(edited_node.get_parent(), "remove_child", mesh_inst)
- undo_redo.commit_action()
-
- mesh_inst.set_owner(get_editor_interface().get_edited_scene_root())
diff --git a/addons/SIsilicon.3d.text/LICENSE b/addons/SIsilicon.3d.text/LICENSE
deleted file mode 100644
index e3f824b..0000000
--- a/addons/SIsilicon.3d.text/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 Roujel Williams
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/addons/SIsilicon.3d.text/array_utils.gd b/addons/SIsilicon.3d.text/array_utils.gd
deleted file mode 100644
index 257e53a..0000000
--- a/addons/SIsilicon.3d.text/array_utils.gd
+++ /dev/null
@@ -1,47 +0,0 @@
-class_name ArrayUtils
-
-# Shifts the array's values by a specified amount.
-# Positive amount shifts right while negative shifts left.
-static func shift_array(array : Array, amount : int):
- var right = bool(sign(amount) * 0.5 + 0.5)
- for i in abs(amount):
- var value
- if right:
- value = array.pop_back()
- array.push_front(value)
- else:
- value = array.pop_front()
- array.push_back(value)
-
-# Combines the array with the other array.
-static func append_array(array : Array, other : Array):
- for i in other:
- array.append(i)
-
-# Splits the array between a and b at index.
-static func split_array(array : Array, index : int, a : Array, b : Array):
- for i in range(0, index):
- a.append(array[i])
-
- for i in range(index, array.size()):
- b.append(array[i])
-
-# Converts array to dictionary
-static func to_dictionary(array : Array) -> Dictionary:
- var dict = {}
- for i in array.size():
- dict[i] = array[i]
- return dict
-
-# Calls a function per object in the array with variable arguments.
-static func call_per_element(array : Array, function : String, vars = null):
- match typeof(vars):
- TYPE_NIL:
- for e in array:
- e.call(function)
- TYPE_ARRAY:
- for e in array:
- e.callv(function, vars)
- _:
- for e in array:
- e.call(function, vars)
diff --git a/addons/SIsilicon.3d.text/default_font.tres b/addons/SIsilicon.3d.text/default_font.tres
deleted file mode 100644
index 34c9b2b..0000000
--- a/addons/SIsilicon.3d.text/default_font.tres
+++ /dev/null
@@ -1,8 +0,0 @@
-[gd_resource type="DynamicFont" load_steps=2 format=2]
-
-[ext_resource path="res://addons/SIsilicon.3d.text/default_font.ttf" type="DynamicFontData" id=1]
-
-[resource]
-size = 100
-use_filter = true
-font_data = ExtResource( 1 )
diff --git a/addons/SIsilicon.3d.text/default_font.ttf b/addons/SIsilicon.3d.text/default_font.ttf
deleted file mode 100644
index e5f34b2..0000000
Binary files a/addons/SIsilicon.3d.text/default_font.ttf and /dev/null differ
diff --git a/addons/SIsilicon.3d.text/icon_label_3d.svg b/addons/SIsilicon.3d.text/icon_label_3d.svg
deleted file mode 100644
index 4cf4522..0000000
--- a/addons/SIsilicon.3d.text/icon_label_3d.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/addons/SIsilicon.3d.text/icon_label_3d.svg.import b/addons/SIsilicon.3d.text/icon_label_3d.svg.import
deleted file mode 100644
index 6a1752e..0000000
--- a/addons/SIsilicon.3d.text/icon_label_3d.svg.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="StreamTexture"
-path="res://.import/icon_label_3d.svg-66ebb0fa17446da110a5ed153702e3c3.stex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://addons/SIsilicon.3d.text/icon_label_3d.svg"
-dest_files=[ "res://.import/icon_label_3d.svg-66ebb0fa17446da110a5ed153702e3c3.stex" ]
-
-[params]
-
-compress/mode=0
-compress/lossy_quality=0.7
-compress/hdr_mode=0
-compress/bptc_ldr=0
-compress/normal_map=0
-flags/repeat=0
-flags/filter=true
-flags/mipmaps=false
-flags/anisotropic=false
-flags/srgb=2
-process/fix_alpha_border=true
-process/premult_alpha=false
-process/HDR_as_SRGB=false
-process/invert_color=false
-stream=false
-size_limit=0
-detect_3d=true
-svg/scale=1.0
diff --git a/addons/SIsilicon.3d.text/label_3d.gd b/addons/SIsilicon.3d.text/label_3d.gd
deleted file mode 100644
index 95788b6..0000000
--- a/addons/SIsilicon.3d.text/label_3d.gd
+++ /dev/null
@@ -1,148 +0,0 @@
-tool
-extends Spatial
-
-export(String, MULTILINE) var text = "Text" setget set_text
-export(float) var text_scale = 0.01 setget set_text_scale
-export(float) var extrude = 0.0 setget set_extrude
-export(Font) var font setget set_font;
-
-export(int, "Left", "Right", "Center", "Fill") var align setget set_align
-
-export(Color) var color = Color(0.6, 0.6, 0.6) setget set_color
-export(float, 0, 1) var metallic = 0.0 setget set_metallic
-export(float, 0, 1) var roughness = 0.5 setget set_roughness
-
-export(int) var max_steps = 256 setget set_max_steps
-export(float) var step_size = 1.0 setget set_step_size
-
-var label
-var viewport
-var proxy
-var material
-
-func _ready():
- for i in range(get_child_count()):
- remove_child(get_child(0))
-
- viewport = preload("text_viewport.tscn").instance()
- label = viewport.get_node("Label")
- add_child(viewport)
-
- proxy = MeshInstance.new()
- proxy.mesh = CubeMesh.new()
- proxy.material_override = preload("label_3d.material").duplicate()
- material = proxy.material_override
-
- var view_texture = viewport.get_texture()
- view_texture.flags = Texture.FLAG_FILTER
- material.set_shader_param("text", view_texture)
- add_child(proxy)
-
- set_align(align)
- set_font(font)
- set_text(text)
- set_text_scale(text_scale)
- set_extrude(extrude)
-
- set_color(color)
- set_metallic(metallic)
- set_roughness(roughness)
-
- set_max_steps(max_steps)
- set_step_size(step_size)
-
-
-func set_text(string):
- text = string;
- if label:
- label.text = text
- label.rect_size = Vector2()
- label.force_update_transform()
-
- var size = label.rect_size
- viewport.size = size
-
- viewport.render_target_update_mode = Viewport.UPDATE_ALWAYS
- yield(get_tree(), "idle_frame")
-
- label.rect_size = Vector2()
- label.force_update_transform()
-
- size = label.rect_size
- viewport.size = size
-
- yield(get_tree(), "idle_frame")
- viewport.render_target_update_mode = Viewport.UPDATE_DISABLED
-
- proxy.scale.x = size.x * text_scale
- proxy.scale.y = size.y * text_scale
-
-func set_text_scale(scale):
- text_scale = scale
- if label:
- var size = label.rect_size
- if proxy:
- proxy.scale.x = size.x * text_scale
- proxy.scale.y = size.y * text_scale
-
-func set_extrude(ext):
- extrude = ext
-
- if proxy:
- proxy.scale.z = extrude if extrude != 0 else 1
- material.set_shader_param("extrude", extrude != 0)
-
- if extrude == 0 and proxy.mesh is CubeMesh:
- proxy.mesh = QuadMesh.new()
- proxy.mesh.size = Vector2(2, 2)
- elif proxy.mesh is QuadMesh:
- proxy.mesh = CubeMesh.new()
-
-func set_font(f):
- font = f
- if label:
- if font:
- label.add_font_override("font", font)
- else:
- label.add_font_override("font", preload("default_font.tres"))
- set_text(text)
-
-func set_align(al):
- align = al
- if label:
- match align:
- 0:
- label.align = Label.ALIGN_LEFT
- 1:
- label.align = Label.ALIGN_RIGHT
- 2:
- label.align = Label.ALIGN_CENTER
- 3:
- label.align = Label.ALIGN_FILL
-
- set_text(text)
-
-func set_color(col):
- color = col
- if material:
- material.set_shader_param("albedo", color)
-
-func set_metallic(metal):
- metallic = metal
- if material:
- material.set_shader_param("metallic", metallic)
-
-func set_roughness(rough):
- roughness = rough
- if material:
- material.set_shader_param("roughness", roughness)
-
-func set_max_steps(max_s):
- max_steps = max(max_s, 8)
- if material:
- material.set_shader_param("maxSteps", max_steps)
-
-func set_step_size(step_s):
- step_size = max(step_s, 0)
- if material:
- material.set_shader_param("stepSize", step_size)
diff --git a/addons/SIsilicon.3d.text/label_3d.material b/addons/SIsilicon.3d.text/label_3d.material
deleted file mode 100644
index b9c6f1e..0000000
Binary files a/addons/SIsilicon.3d.text/label_3d.material and /dev/null differ
diff --git a/addons/SIsilicon.3d.text/label_3d_converter.gd b/addons/SIsilicon.3d.text/label_3d_converter.gd
deleted file mode 100644
index fdbffae..0000000
--- a/addons/SIsilicon.3d.text/label_3d_converter.gd
+++ /dev/null
@@ -1,461 +0,0 @@
-tool
-extends Button
-
-signal mesh_generated(mesh_inst)
-
-const Label3D = preload("label_3d.gd")
-
-var label3d : Label3D
-
-var image_cache = PoolRealArray()
-
-# These variables are used to spread the marching_square function
-# across multiple frames.
-var finished_marching = false
-var os_time
-
-func _on_Button_pressed():
- generate_geometry()
-
-func generate_geometry():
- $PopupDialog.popup_centered()
- var text_scale = label3d.text_scale
- var extrude = label3d.extrude
- var viewport = label3d.get_node("Viewport")
-
- viewport.render_target_update_mode = Viewport.UPDATE_ALWAYS
- yield(get_tree(), "idle_frame")
- yield(get_tree(), "idle_frame")
- viewport.render_target_update_mode = Viewport.UPDATE_DISABLED
-
- var image = viewport.get_texture().get_data()
- image.lock()
-
- var edges = Dictionary()
- finished_marching = false
- os_time = OS.get_ticks_msec()
- do_marching_squares(image, edges)
- while not finished_marching:
- yield(get_tree(), "idle_frame")
-
- # Contours are the edges and holes of the text.
- # The paths are said edges combined with the holes for easier triangulation.
- var contours = []
- collect_contours(edges.duplicate(), contours)
- var paths = contours.duplicate(true)
- decimate_holes(edges, paths, Rect2(0, 0, image.get_width(), image.get_height()))
-
- var triangle_data = []
- var vertex_data = []
- for path in paths:
- path.points = douglas_peucker(path.points, 0.125)
- # Edge data beyond this point will no longer be valid
-
- var triangles = []
- ear_clipping(path, triangles)
-
- # points are converted into vector3 here.
- for p in path.points.size():
- var point = path.points[p]
- point -= Vector2(image.get_width(), image.get_height()) / 2.0
- point *= text_scale * 2.0
- path.points[p] = Vector3(point.x, point.y, extrude)
-
- ArrayUtils.call_per_element(triangles, "offset_indices", vertex_data.size())
-
- ArrayUtils.append_array(vertex_data, path.points)
- ArrayUtils.append_array(triangle_data, triangles)
-
- if extrude != 0:
- # Generate the back triangles
- var original_vert_size = vertex_data.size()
- for v in original_vert_size:
- var vertex = vertex_data[v]
- vertex_data.append(vertex * Vector3(1, 1, -1))
-
- var tri_size = triangle_data.size()
- for t in tri_size:
- var triangle = triangle_data[t].duplicate()
- triangle.offset_indices(original_vert_size)
- triangle.reverse_order()
- triangle_data.append(triangle)
-
- # And now the triangles inbetween
- for path in paths:
- var vertices = path.points.duplicate()
- var points_size = path.points.size()
- for vert in points_size:
- var back_vertex = path.points[vert] * Vector3(1, 1, -1)
- vertices.append(back_vertex)
-
- var triangles = []
- for i in points_size:
- var index = i + vertex_data.size()
- var a = index
- var b = (index + 1) if i != points_size - 1 else vertex_data.size()
- var c = index + points_size
- var d = ((index + 1) if i != points_size - 1 else vertex_data.size()) + points_size
-
- triangles.append(Triangle.new(c, b, a))
- triangles.append(Triangle.new(d, b, c))
-
- ArrayUtils.append_array(vertex_data, vertices)
- ArrayUtils.append_array(triangle_data, triangles)
-
- var geom = SurfaceTool.new()
- geom.clear()
-
- geom.begin(Mesh.PRIMITIVE_TRIANGLES)
- for vert in vertex_data:
- geom.add_vertex(vert)
- for tri in triangle_data:
- geom.add_index(tri.a)
- geom.add_index(tri.b)
- geom.add_index(tri.c)
- geom.generate_normals()
-
- var material = SpatialMaterial.new()
- material.albedo_color = label3d.color
- material.metallic = label3d.metallic
- material.roughness = label3d.roughness
-
- var mesh_inst = MeshInstance.new()
- mesh_inst.mesh = geom.commit()
- mesh_inst.material_override = material
- mesh_inst.transform = label3d.transform
- mesh_inst.name = label3d.name + "-mesh"
-
- emit_signal("mesh_generated", mesh_inst)
-
- image_cache.resize(0)
- image.unlock()
- $PopupDialog.hide()
-
-
-func do_marching_squares(image, edges):
- for y in image.get_height() - 1:
- for x in image.get_width() - 1:
- var i = Vector2(x, y)
-
- var p_ul = get_pixel(image, x, y)
- var p_ll = get_pixel(image, x, y+1)
- var p_ur = get_pixel(image, x+1, y)
- var p_lr = get_pixel(image, x+1, y+1)
-
- var top = inverse_lerp(p_ul, p_ur, 1)
- var bottom = inverse_lerp(p_ll, p_lr, 1)
- var left = inverse_lerp(p_ul, p_ll, 1)
- var right = inverse_lerp(p_ur, p_lr, 1)
-
- var ul = int(p_ul > 1) * 1
- var ll = int(p_ll > 1) * 2
- var ur = int(p_ur > 1) * 4
- var lr = int(p_lr > 1) * 8
-
- var bit = ul | ll | ur | lr
-
- # Notice: cases 6 and 9 have not been implemented.
- match bit:
- # Corner Cases
- 1:
- create_edge(edges, i, x, y + left, x + top, y, Vector2.UP)
- 2:
- create_edge(edges, i, x + bottom, y + 1, x, y + left, Vector2.LEFT)
- 4:
- create_edge(edges, i, x + top, y, x + 1, y + right, Vector2.RIGHT)
- 8:
- create_edge(edges, i, x + 1, y + right, x + bottom, y + 1, Vector2.DOWN)
-
- # Edge Cases
- 3:
- create_edge(edges, i, x + bottom, y + 1, x + top, y, Vector2.UP)
- 5:
- create_edge(edges, i, x, y + left, x + 1, y + right, Vector2.RIGHT)
- 10:
- create_edge(edges, i, x + 1, y + right, x, y + left, Vector2.LEFT)
- 12:
- create_edge(edges, i, x + top, y, x + bottom, y + 1, Vector2.DOWN)
-
- # Inner Corner cases
- 14:
- create_edge(edges, i, x + top, y, x, y + left, Vector2.LEFT)
- 13:
- create_edge(edges, i, x, y + left, x + bottom, y + 1, Vector2.DOWN)
- 11:
- create_edge(edges, i, x + 1, y + right, x + top, y, Vector2.UP)
- 7:
- create_edge(edges, i, x + bottom, y + 1, x + 1, y + right, Vector2.RIGHT)
-
- if OS.get_ticks_msec() - os_time > 20:
- var max_i = (image.get_width() - 1) * (image.get_height() - 1)
- var curr_i = x + (y * image.get_width() - 1)
- $PopupDialog/VBoxContainer/ProgressBar.value = float(curr_i) / max_i * 100
-
- yield(get_tree(), "idle_frame")
- os_time = OS.get_ticks_msec()
-
- finished_marching = true
-
-func collect_contours(edges, contours):
-
- var directions = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]
- var coord = edges.keys()[0]
-
- var start_edge = edges[coord]
- var contour = Contour.new(start_edge.direction == Vector2.UP || \
- start_edge.direction == Vector2.RIGHT)
- contours.append(contour)
-
- contour.points.append(edges[coord].end)
- contour.edges.append(edges[coord])
- var edge = edges[coord]
-
- edges.erase(coord)
-
- var loop_started = true
- while loop_started or edge != start_edge:
- var edge_found = false
-
- for dir in directions:
- if edges.has(coord + dir) and edges[coord + dir].begin == edge.end:
- edge_found = true
-
- coord += dir
- contour.points.append(edges[coord].end)
- contour.edges.append(edges[coord])
- edge = edges[coord]
- edges.erase(coord)
-
- break
-
- if not edge_found:
- break
-
- loop_started = false
-
- if not edges.empty():
- collect_contours(edges, contours)
-
-func decimate_holes(edges, contours, bounds):
- for c in range(contours.size()-1, -1, -1):
- var contour = contours[c]
- if contour.is_hole:
- for i in contour.edges.size():
- var edge = contour.edges[i]
- var edge_found = false
-
- var dir = edge.direction
- dir = Vector2(dir.y, -dir.x)
- var pos = ((edge.begin + edge.end) / 2.0).floor()
- var other_edge
-
- while not other_edge and bounds.has_point(pos):
- pos += dir
- if edges.has(pos):
- other_edge = edges[pos]
-
- if other_edge:
-
- for other_contour in contours:
- if other_contour == contour:
- continue
-
- for j in other_contour.edges.size():
- var other_point = other_contour.edges[j]
- if other_edge == other_point:
- edge_found = true
- other_contour.fuse_with(contour, j, i + 1)
- break
-
- if edge_found:
- break
-
- if edge_found:
- break
-
- contours.remove(c)
-
-func douglas_peucker(points, tolerance):
- var farthest = farthest_point(points)
-
-
- # Farthest point not existing must mean the points only made of two,
- # and cannot be simplified any further.
- if not farthest:
- return points
-
- if farthest.distance < tolerance:
- return [points[0], points[-1]]
- else:
- var left = []
- var right = []
- ArrayUtils.split_array(points, farthest.index, left, right)
-
- left = douglas_peucker(left, tolerance)
- right = douglas_peucker(right, tolerance)
-
- ArrayUtils.append_array(left, right)
- return left
-
-func ear_clipping(contour, triangles):
- var points = ArrayUtils.to_dictionary(contour.points)
-
- var i = 0
- var counter = 0
- while points.size() > 3:
- counter += 1
- if(counter > 4000):
- printerr("Hmmm... Infinite loop much?")
- break
-
- var keys = points.keys()
- var point_a = points[keys[i-1]]
- var point_b = points[keys[i]]
- var point_c = points[keys[(i+1) % keys.size()]]
-
- if (point_b - point_a).cross(point_c - point_b) < 0:
- var has_point = false
- for p in points:
- var point = points[p]
- if point == point_a or point == point_b or point == point_c:
- continue
- if point_inside_triangle(point, point_a, point_b, point_c):
- has_point = true
- break
-
- if not has_point:
- var tri = Triangle.new(keys[i-1], keys[i], keys[(i+1) % keys.size()])
- triangles.append(tri)
- points.erase(keys[i])
-
- i = wrapi(i + 1, 0, points.size())
-
- var keys = points.keys()
- triangles.append(Triangle.new(keys[0], keys[1], keys[2]))
-
-# This returns a dictionary containing the farthest point,
-# the distance associated with it, and its index in the array.
-func farthest_point(points):
- var first = points[0]
- var last = points[-1]
-
- if points.size() < 3:
- return
-
- var farthest
- var max_dist = -1
- var index
- for i in range(1, points.size() - 1):
- var distance = distance_to_segment(points[i], first, last)
- if distance > max_dist:
- farthest = points[i]
- max_dist = distance
- index = i
-
- return {"point": farthest, "distance": max_dist, "index": index}
-
-func distance_to_segment(point, a, b):
- # This is because I don't know how to snap a point onto a line,
- # so I'm relying on Plane in the meantime.
- var plane = Plane(Vector3(a.x, a.y, 0),
- Vector3(b.x, b.y, 0), Vector3(a.x, a.y, 1))
-
- var projected = plane.project(Vector3(point.x, point.y, 0))
- var t = inverse_lerp(a.x, b.x, projected.x) if a.x != b.x else \
- inverse_lerp(a.y, b.y, projected.y)
-
- var snapped = a.linear_interpolate(b, t)
- return point.distance_squared_to(snapped)
-
-func vec_sign(p1, p2, p3):
- return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
-
-func point_inside_triangle(point, a, b, c):
- var d1 = vec_sign(point, a, b)
- var d2 = vec_sign(point, b, c)
- var d3 = vec_sign(point, c, a)
-
- var has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
- var has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
- return not (has_neg and has_pos)
-
-func create_edge(edges, offset, p1x, p1y, p2x, p2y, dir):
- edges[offset] = MSEdge.new(Vector2(p1x, p1y), Vector2(p2x, p2y), dir)
-
-func get_pixel(image : Image, x : int, y : int):
- if x < 1 || y < 1 || x > image.get_width()-1 || y > image.get_height()-1:
- return 0
-
- var i = x + y * image.get_width()
- if image_cache.size() > i and image_cache[i] != null:
- return image_cache[i]
- else:
- var real = image.get_pixel(x, y).r;
- if image_cache.size() <= i:
- image_cache.resize(i+1)
- image_cache[i] = real
- return real
-
-
-class MSEdge:
- var begin : Vector2
- var end : Vector2
-
- var coordinate : Vector2
-
- # One of the four cardinal directions
- var direction : Vector2
-
- func _init(begin, end, direction):
- self.begin = begin
- self.end = end
- self.direction = direction
-
-
-class Contour:
- var points := []
- var edges := []
- var is_hole : bool
-
- func _init(is_hole):
- self.is_hole = is_hole
-
- func fuse_with(contour, self_point, other_point):
- var points_behind = []
- var points_after = []
-
- ArrayUtils.split_array(points, self_point + 1, points_behind, points_after)
-
- var shifted_contour = contour.points.duplicate()
- ArrayUtils.shift_array(shifted_contour, other_point)
-
- ArrayUtils.append_array(points_behind, shifted_contour)
- points_behind.append(shifted_contour[0])
- points_behind.append(points[self_point])
- ArrayUtils.append_array(points_behind, points_after)
-
- points = points_behind
-
-
-class Triangle:
- var a : int
- var b : int
- var c : int
-
- func _init(a, b, c):
- self.a = a
- self.b = b
- self.c = c
-
- func offset_indices(offset):
- a += offset
- b += offset
- c += offset
-
- func reverse_order():
- var temp = a
- a = b
- b = temp
-
- func duplicate():
- return Triangle.new(a, b, c)
diff --git a/addons/SIsilicon.3d.text/label_3d_converter.tscn b/addons/SIsilicon.3d.text/label_3d_converter.tscn
deleted file mode 100644
index 1c02ff0..0000000
--- a/addons/SIsilicon.3d.text/label_3d_converter.tscn
+++ /dev/null
@@ -1,40 +0,0 @@
-[gd_scene load_steps=2 format=2]
-
-[ext_resource path="res://addons/SIsilicon.3d.text/label_3d_converter.gd" type="Script" id=1]
-
-[node name="Button" type="Button"]
-margin_right = 172.0
-margin_bottom = 20.0
-text = "Convert to MeshInstance"
-flat = true
-script = ExtResource( 1 )
-
-[node name="PopupDialog" type="PopupDialog" parent="."]
-visible = true
-margin_left = 340.0
-margin_top = 200.0
-margin_right = 610.0
-margin_bottom = 268.0
-mouse_default_cursor_shape = 5
-popup_exclusive = true
-
-[node name="VBoxContainer" type="VBoxContainer" parent="PopupDialog"]
-anchor_right = 1.0
-anchor_bottom = 1.0
-margin_left = 6.0
-margin_top = 6.0
-margin_right = -6.0
-margin_bottom = -6.0
-
-[node name="Label" type="Label" parent="PopupDialog/VBoxContainer"]
-margin_right = 258.0
-margin_bottom = 14.0
-text = "Generating geometry"
-align = 1
-
-[node name="ProgressBar" type="ProgressBar" parent="PopupDialog/VBoxContainer"]
-margin_top = 18.0
-margin_right = 258.0
-margin_bottom = 32.0
-step = 1.0
-[connection signal="pressed" from="." to="." method="_on_Button_pressed"]
diff --git a/addons/SIsilicon.3d.text/plugin.cfg b/addons/SIsilicon.3d.text/plugin.cfg
deleted file mode 100644
index 8c20b54..0000000
--- a/addons/SIsilicon.3d.text/plugin.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-[plugin]
-
-name="3D Text"
-description="This plugin allows you to make 3D text within the Godot engine."
-author="SIsilicon"
-version="0.6.1"
-script="3d_text_plugin.gd"
\ No newline at end of file
diff --git a/addons/SIsilicon.3d.text/text_viewport.tscn b/addons/SIsilicon.3d.text/text_viewport.tscn
deleted file mode 100644
index 18e2279..0000000
--- a/addons/SIsilicon.3d.text/text_viewport.tscn
+++ /dev/null
@@ -1,74 +0,0 @@
-[gd_scene load_steps=7 format=2]
-
-[ext_resource path="res://addons/SIsilicon.3d.text/default_font.ttf" type="DynamicFontData" id=1]
-
-[sub_resource type="Environment" id=1]
-background_mode = 1
-background_energy = 0.0
-
-[sub_resource type="World" id=2]
-environment = SubResource( 1 )
-
-[sub_resource type="DynamicFont" id=3]
-size = 100
-font_data = ExtResource( 1 )
-
-[sub_resource type="Shader" id=4]
-code = "shader_type canvas_item;
-
-uniform int Max = 3;
-
-float getAlpha(sampler2D tex, ivec2 coord) {
- return texelFetch(tex, coord, 0).r;
-}
-
-void fragment() {
- ivec2 coord = ivec2(FRAGCOORD.xy);
- bool inside = getAlpha(SCREEN_TEXTURE, coord) > 0.5;
-
- float dist = float((Max+1) * (Max+1));
- for(int y = -Max; y <= Max; y++) {
- for(int x = -Max; x <= Max; x++) {
- if(x == 0 && y == 0) continue;
- ivec2 delta = ivec2(x,y);
-
- if(inside) {
- if(getAlpha(SCREEN_TEXTURE, coord + delta) < 0.5) {
- dist = min(dist, float(x*x + y*y));
- }
- } else {
- if(getAlpha(SCREEN_TEXTURE, coord + delta) > 0.5) {
- dist = min(dist, float(x*x + y*y));
- }
- }
- }
- }
- dist = sqrt(dist) * (float(!inside) * 2.0 - 1.0);
-
- COLOR = vec4(dist / float(Max+1));
-}"
-
-[sub_resource type="ShaderMaterial" id=5]
-shader = SubResource( 4 )
-shader_param/Max = 10
-
-[node name="Viewport" type="Viewport"]
-size = Vector2( 190, 114 )
-own_world = true
-world = SubResource( 2 )
-transparent_bg = true
-handle_input_locally = false
-render_target_update_mode = 1
-gui_disable_input = true
-
-[node name="Label" type="Label" parent="."]
-margin_right = 190.0
-margin_bottom = 114.0
-size_flags_horizontal = 4
-custom_fonts/font = SubResource( 3 )
-text = "Text"
-
-[node name="ColorRect" type="ColorRect" parent="Label"]
-material = SubResource( 5 )
-anchor_right = 1.0
-anchor_bottom = 1.0
diff --git a/addons/godot_xterm/import_plugins/asciicast_import_plugin.gd b/addons/godot_xterm/import_plugins/asciicast_import_plugin.gd
index 4830c1c..98897fd 100644
--- a/addons/godot_xterm/import_plugins/asciicast_import_plugin.gd
+++ b/addons/godot_xterm/import_plugins/asciicast_import_plugin.gd
@@ -45,7 +45,13 @@ func import(source_file, save_path, options, r_platform_variant, r_gen_files):
asciicast.add_track(Animation.TYPE_METHOD, 0)
asciicast.track_set_path(0, ".")
- var time: float
+ var frame = {
+ "time": 0.0,
+ "data": {
+ "method": "write",
+ "args": [PoolByteArray()]
+ }
+ }
while not file.eof_reached():
var line = file.get_line()
@@ -56,14 +62,25 @@ func import(source_file, save_path, options, r_platform_variant, r_gen_files):
if typeof(p.result) != TYPE_ARRAY:
continue
- time = p.result[0]
var event_type: String = p.result[1]
var event_data: PoolByteArray = p.result[2].to_utf8()
+ # Asciicast recordings have a resolution of 0.000001, however animation
+ # track keys only have a resolution of 0.01, therefore we must combine
+ # events that would occur in the same keyframe, otherwise only the last
+ # event is inserted and the previous events are overwritten.
+ var time = stepify(p.result[0], 0.01)
+
if event_type == "o":
- asciicast.track_insert_key(0, time, {"method": "write",
- "args": [event_data]})
+ if time == frame.time:
+ asciicast.track_remove_key_at_position(0, time)
+ frame.data.args[0] = frame.data.args[0] + event_data
+ else:
+ frame.time = time
+ frame.data.args = [event_data]
+
+ asciicast.track_insert_key(0, frame.time, frame.data)
- asciicast.length = time
+ asciicast.length = frame.time
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], asciicast)
diff --git a/addons/godot_xterm/native/src/pseudoterminal.cpp b/addons/godot_xterm/native/src/pseudoterminal.cpp
index 029ff31..52b6a57 100644
--- a/addons/godot_xterm/native/src/pseudoterminal.cpp
+++ b/addons/godot_xterm/native/src/pseudoterminal.cpp
@@ -1,6 +1,7 @@
#include "pseudoterminal.h"
#include
#include
+#include
#include
using namespace godot;
@@ -11,9 +12,11 @@ void Pseudoterminal::_register_methods()
register_method("_init", &Pseudoterminal::_init);
register_method("_ready", &Pseudoterminal::_ready);
- register_method("put_data", &Pseudoterminal::put_data);
+ register_method("write", &Pseudoterminal::write);
+ register_method("resize", &Pseudoterminal::resize);
- register_signal((char *)"data_received", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
+ register_signal((char *)"data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
+ register_signal((char *)"exited", "status", GODOT_VARIANT_TYPE_INT);
}
Pseudoterminal::Pseudoterminal()
@@ -22,18 +25,20 @@ Pseudoterminal::Pseudoterminal()
Pseudoterminal::~Pseudoterminal()
{
+ pty_thread.join();
}
void Pseudoterminal::_init()
{
- pty_thread = std::thread(&Pseudoterminal::process_pty, this);
bytes_to_write = 0;
+ pty_thread = std::thread(&Pseudoterminal::process_pty, this);
}
void Pseudoterminal::process_pty()
{
int fd;
char *name;
+ int status;
should_process_pty = true;
@@ -68,9 +73,30 @@ void Pseudoterminal::process_pty()
}
else
{
+ Vector2 zero = Vector2(0, 0);
+
/* Parent */
while (1)
{
+ {
+ std::lock_guard guard(size_mutex);
+ if (size != zero)
+ {
+ struct winsize ws;
+ memset(&ws, 0, sizeof(ws));
+ ws.ws_col = size.x;
+ ws.ws_row = size.y;
+
+ ioctl(fd, TIOCSWINSZ, &ws);
+ }
+ }
+
+ if (waitpid(pty_pid, &status, WNOHANG))
+ {
+ emit_signal("exited", status);
+ return;
+ }
+
int ready = -1;
fd_set read_fds;
fd_set write_fds;
@@ -93,10 +119,7 @@ void Pseudoterminal::process_pty()
if (bytes_to_write > 0)
{
- write(fd, write_buffer, bytes_to_write);
-
- Godot::print(String("wrote {0} bytes").format(Array::make(bytes_to_write)));
-
+ ::write(fd, write_buffer, bytes_to_write);
bytes_to_write = 0;
}
}
@@ -114,30 +137,11 @@ void Pseudoterminal::process_pty()
if (bytes_read <= 0)
continue;
- //while (1)
- //{
- // ret = read(fd, read_buffer, 1);
-
- // if (ret == -1 || ret == 0)
- // {
- // break;
- // }
- // else
- // {
- // bytes_read += ret;
- // }
- //}
-
PoolByteArray data = PoolByteArray();
data.resize(bytes_read);
memcpy(data.write().ptr(), read_buffer, bytes_read);
- emit_signal("data_received", PoolByteArray(data));
-
- if (bytes_read > 0)
- {
- //Godot::print(String("read {0} bytes").format(Array::make(bytes_read)));
- }
+ emit_signal("data_sent", PoolByteArray(data));
}
}
}
@@ -148,9 +152,15 @@ void Pseudoterminal::_ready()
{
}
-void Pseudoterminal::put_data(PoolByteArray data)
+void Pseudoterminal::write(PoolByteArray data)
{
std::lock_guard guard(write_buffer_mutex);
bytes_to_write = data.size();
memcpy(write_buffer, data.read().ptr(), bytes_to_write);
+}
+
+void Pseudoterminal::resize(Vector2 new_size)
+{
+ std::lock_guard guard(size_mutex);
+ size = new_size;
}
\ No newline at end of file
diff --git a/addons/godot_xterm/native/src/pseudoterminal.h b/addons/godot_xterm/native/src/pseudoterminal.h
index 98bd1a3..dd48cc3 100644
--- a/addons/godot_xterm/native/src/pseudoterminal.h
+++ b/addons/godot_xterm/native/src/pseudoterminal.h
@@ -29,6 +29,9 @@ namespace godot
int bytes_to_read;
std::mutex read_buffer_mutex;
+ Vector2 size;
+ std::mutex size_mutex;
+
void process_pty();
public:
@@ -40,7 +43,8 @@ namespace godot
void _init();
void _ready();
- void put_data(PoolByteArray data);
+ void write(PoolByteArray data);
+ void resize(Vector2 size);
};
} // namespace godot
diff --git a/addons/godot_xterm/native/src/terminal.cpp b/addons/godot_xterm/native/src/terminal.cpp
index 81e9c54..d690ef5 100644
--- a/addons/godot_xterm/native/src/terminal.cpp
+++ b/addons/godot_xterm/native/src/terminal.cpp
@@ -215,7 +215,17 @@ static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data
for (int i = 0; i < len; i++)
bytes.append(u8[i]);
- term->emit_signal("data_read", bytes);
+ if (len > 0)
+ {
+ if (term->input_event_key.is_valid())
+ {
+ // The callback was fired from a key press event so emit the "key_pressed" signal.
+ term->emit_signal("key_pressed", String(u8), term->input_event_key);
+ term->input_event_key.unref();
+ }
+
+ term->emit_signal("data_sent", bytes);
+ }
}
static int text_draw_cb(struct tsm_screen *con,
@@ -258,7 +268,6 @@ static int text_draw_cb(struct tsm_screen *con,
void Terminal::_register_methods()
{
-
register_method("_init", &Terminal::_init);
register_method("_ready", &Terminal::_ready);
register_method("_gui_input", &Terminal::_gui_input);
@@ -267,10 +276,12 @@ void Terminal::_register_methods()
register_method("write", &Terminal::write);
register_method("update_size", &Terminal::update_size);
- //register_property("rows", &Terminal::rows, 24);
- //register_property("cols", &Terminal::cols, 80);
+ register_property("rows", &Terminal::rows, 24);
+ register_property("cols", &Terminal::cols, 80);
- register_signal("data_read", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
+ register_signal("data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
+ register_signal("key_pressed", "data", GODOT_VARIANT_TYPE_STRING, "event", GODOT_VARIANT_TYPE_OBJECT);
+ register_signal("size_changed", "new_size", GODOT_VARIANT_TYPE_VECTOR2);
}
Terminal::Terminal()
@@ -349,6 +360,7 @@ void Terminal::_gui_input(Variant event)
auto iter = keymap.find({unicode, scancode});
uint32_t keysym = (iter != keymap.end() ? iter->second : XKB_KEY_NoSymbol);
+ input_event_key = k;
tsm_vte_handle_keyboard(vte, keysym, ascii, mods, unicode ? unicode : TSM_VTE_INVALID);
}
}
@@ -425,6 +437,9 @@ void Terminal::draw_foreground(int row, int col, Color fgcolor)
struct cell cell = cells[row][col];
+ if (cell.ch == nullptr)
+ return; // No foreground to draw
+
/* Set the font */
Ref fontref = get_font("");
@@ -448,9 +463,6 @@ void Terminal::draw_foreground(int row, int col, Color fgcolor)
/* Draw the foreground */
- if (cell.ch == nullptr)
- return; // No foreground to draw
-
if (cell.attr.blink)
; // TODO: Handle blink
@@ -523,7 +535,7 @@ void Terminal::update_size()
rows = std::max(2, (int)floor(get_rect().size.y / cell_size.y));
cols = std::max(1, (int)floor(get_rect().size.x / cell_size.x));
- Godot::print(String("resized_rows: {0}, resized_cols: {1}").format(Array::make(rows, cols)));
+ emit_signal("size_changed", Vector2(cols, rows));
Cells new_cells = {};
diff --git a/addons/godot_xterm/native/src/terminal.h b/addons/godot_xterm/native/src/terminal.h
index a6dec7e..4bd40c6 100644
--- a/addons/godot_xterm/native/src/terminal.h
+++ b/addons/godot_xterm/native/src/terminal.h
@@ -28,6 +28,8 @@ namespace godot
Cells cells;
+ Ref input_event_key;
+
protected:
tsm_screen *screen;
tsm_vte *vte;
diff --git a/addons/godot_xterm/nodes/terminal/README.md b/addons/godot_xterm/nodes/terminal/README.md
new file mode 100644
index 0000000..f12e3ba
--- /dev/null
+++ b/addons/godot_xterm/nodes/terminal/README.md
@@ -0,0 +1,131 @@
+# Terminal
+
+**Inherits:** [Control] < [CanvasItem] < [Node] < [Object]
+
+
+Terminal emulator.
+
+**IMPORTANT:**
+
+
+
+- If you are not seeing anything in the terminal check that a theme has been set. If there is no theme, everything will be drawn in black by default.
+- If the terminal isn't responding to keyboard or mouse input check that `focus_mode` is set to `All`, otherwise `_gui_input()` won't be called so no input will be processed.
+
+
+## Description
+
+![Flow Diagram](./docs/flow_diagram.svg)
+
+### (1) User Input
+
+The users enters some data into the terminal, typically by typing something on the keyboard or clicking (and possibly dragging) somewhere on the screen.
+This corresponds to the `_gui_input()` method which is implemented in [terminal.cpp](../native/src/terminal.cpp).
+
+### (2) Terminal Output
+
+The user input from (1) is processed by the terminal and converted.
+For example, if the user were to press the down key, the terminal would
+and the `data_sent` signal would be emitted with the value `"\u001b[A"`.
+For a full list of escape sequences ["XTerm Control Sequences"](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html).
+
+### (3) Terminal Input
+
+In the other direction, characters can be sent to the terminal. This corresponds to the `write()` method.
+
+### (4) Draw
+
+The input from (3) is then intepreted by the terminal and drawn to the screen.
+For example if the string "\u001b[39;m" was written to the terminal, then it would draw some colorful output to the screen.
+
+## Properties
+
+| Type | Name | Default |
+|-------|------|---------|
+| [int] | rows | 24 |
+| [int] | cols | 80 |
+
+## Methods
+
+## Theme Properties
+
+| Type | Name | Default |
+|---------|-------------------------------|------------------------------|
+| [Color] | Terminal/colors/Background | Color(0.0, 0.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/Black | Color(0.0, 0.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/Blue | Color(0.0, 0.0, 0.5, 1.0) |
+| [Color] | Terminal/colors/Cyan | Color(0.0, 0.5, 0.5, 1.0) |
+| [Color] | Terminal/colors/Dark Grey | Color(0.5, 0.5, 0.5, 1.0) |
+| [Color] | Terminal/colors/Foreground | Color(1.0, 1.0, 1.0, 1.0) |
+| [Color] | Terminal/colors/Green | Color(0.0, 0.5, 0.0, 1.0) |
+| [Color] | Terminal/colors/Light Blue | Color(0.0, 0.0, 1.0, 1.0) |
+| [Color] | Terminal/colors/Light Cyan | Color(0.0, 1.0, 1.0, 1.0) |
+| [Color] | Terminal/colors/Light Green | Color(0.0, 1.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/Light Grey | Color(0.75, 0.75, 0.75, 1.0) |
+| [Color] | Terminal/colors/Light Magenta | Color(1.0, 0.0, 1.0, 1.0) |
+| [Color] | Terminal/colors/Light Red | Color(1.0, 0.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/Light Yellow | Color(1.0, 1.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/Magenta | Color(0.5, 0.0, 0.5, 1.0) |
+| [Color] | Terminal/colors/Red | Color(0.5, 0.0, 0.0, 1.0) |
+| [Color] | Terminal/colors/White | Color(1.0, 1.0, 1.0, 1.0) |
+| [Color] | Terminal/colors/Yellow | Color(0.5, 0.5, 0.0, 1.0) |
+| [Font] | Terminal/fonts/Bold | |
+| [Font] | Terminal/fonts/Bold Italic | |
+| [Font] | Terminal/fonts/Italic | |
+| [Font] | Terminal/fonts/Regular | |
+
+## Signals
+
+- **data_sent** **(** PoolByteArray data **)**
+
+ Emitted when some data comes out of the terminal.
+ This typically occurs when the user interacts with the terminal by typing on the keyboard or clicking somewhere.
+ It might also occur.
+ Depending on several terminal settings can be interpreted differently, depending on modifier keys and the terminals settings/state.
+ In a typical setup, this data would be forwarded to the linux operating system.
+
+---
+
+- **key_pressed** **(** int row **)**
+
+ Emitted when a key is pressed.
+
+## Property Descriptions
+
+- [int] **rows**
+
+ | | |
+ |-----------|-----------------|
+ | *Default* | 24 |
+ | *Setter* | set_rows(value) |
+ | *Getter* | get_rows(value) |
+
+ The number of rows in the terminal's rect.
+ When using a monospace font, this is typically the number of characters that can fit from one side to another.
+ Therefore it is affected by the things thing.
+
+---
+
+- [int] **cols**
+The number of columns in the terminal's rect.
+
+## Method Descriptions
+
+- void **write** **(** String|PoolByteArray data **)**
+
+ Writes data to the terminal emulator. Accepts either a String or PoolByteArray.
+
+ Example:
+ ```gdscript
+ $Terminal.write("Hello World")
+ $Terminal.write("Hello World".to_utf8())
+ $Terminal.write(PoolByteArray([0x1b, 0x9e])
+ ```
+
+[Control]: https://docs.godotengine.org/en/stable/classes/class_control.html#class-control
+[CanvasItem]: https://docs.godotengine.org/en/stable/classes/class_canvasitem.html#class-canvasitem
+[Node]: https://docs.godotengine.org/en/stable/classes/class_node.html#class-node
+[Object]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object
+[Color]: https://docs.godotengine.org/en/stable/classes/class_color.html#class-color
+[Font]: https://docs.godotengine.org/en/stable/classes/class_font.html#class-font
+[int]: https://docs.godotengine.org/en/stable/classes/class_int.html#class-int
diff --git a/addons/godot_xterm/nodes/terminal/docs/.gdignore b/addons/godot_xterm/nodes/terminal/docs/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg b/addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
new file mode 100644
index 0000000..3c3e64e
--- /dev/null
+++ b/addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
@@ -0,0 +1,3777 @@
+
+
diff --git a/addons/godot_xterm/nodes/terminal/docs/important_properties.png b/addons/godot_xterm/nodes/terminal/docs/important_properties.png
new file mode 100644
index 0000000..21ac261
Binary files /dev/null and b/addons/godot_xterm/nodes/terminal/docs/important_properties.png differ
diff --git a/addons/godot_xterm/resources/asciicast.gd b/addons/godot_xterm/resources/asciicast.gd
index 02948b2..aa6b41e 100644
--- a/addons/godot_xterm/resources/asciicast.gd
+++ b/addons/godot_xterm/resources/asciicast.gd
@@ -17,3 +17,7 @@ func get_class() -> String:
func is_class(name) -> bool:
return name == get_class() or .is_class(name)
+
+
+func _init():
+ step = 0.01 # Parent override.
diff --git a/addons/godot_xterm/util/tput.gd b/addons/godot_xterm/util/tput.gd
new file mode 100644
index 0000000..e3bb8e5
--- /dev/null
+++ b/addons/godot_xterm/util/tput.gd
@@ -0,0 +1,66 @@
+extends Reference
+class_name TPut
+
+# Control Sequence Introducer
+const CSI = "\u001b["
+
+const CURSOR_UP = "\u001b[A"
+const CURSOR_DOWN = "\u001b[B"
+const CURSOR_RIGHT = "\u001b[C"
+const CURSOR_LEFT = "\u001b[D"
+
+const DEFAULT_FOREGROUND_COLOR = "\u001b[0m"
+
+
+var terminal
+
+
+func _init(p_terminal: Control) -> void:
+ if p_terminal:
+ terminal = p_terminal
+
+
+func write_string(string: String, color: Color = Color.white) -> void:
+ if color:
+ var fg = "\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
+ terminal.write(fg.to_utf8())
+
+ terminal.write(string.to_utf8())
+
+ # Reset color back to default.
+ terminal.write("\u001b[0m".to_utf8())
+
+# tput_* functions based on the tput command.
+# See: https://man7.org/linux/man-pages/man1/tput.1.html for more info.
+
+
+# Hide the cursor.
+func civis():
+ terminal.write("%s?25l" % CSI)
+
+
+# Position the cursor at the given row and col.
+func cup(row: int = 0, col: int = 0) -> void:
+ terminal.write("\u001b[%d;%dH" % [row, col])
+
+
+func setaf(color: Color) -> void:
+ var fg = "\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
+ terminal.write(fg)
+
+
+func setab(color: Color) -> void:
+ var bg = "\u001b[48;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
+ terminal.write(bg)
+
+
+func rev() -> void:
+ terminal.write("\u001b[7m")
+
+
+func sgr0() -> void:
+ terminal.write("\u001b[0m")
+
+
+func reset() -> void:
+ terminal.write("\u001bc")
diff --git a/examples/asciicast/asciicast.tscn b/examples/asciicast/asciicast.tscn
index f655220..e585608 100644
--- a/examples/asciicast/asciicast.tscn
+++ b/examples/asciicast/asciicast.tscn
@@ -1,24 +1,23 @@
-[gd_scene load_steps=5 format=2]
+[gd_scene load_steps=4 format=2]
-[ext_resource path="res://examples/asciicast/asciicast.gd" type="Script" id=1]
[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=2]
[ext_resource path="res://addons/godot_xterm/nodes/terminal/terminal.gdns" type="Script" id=4]
[ext_resource path="res://examples/asciicast/example.cast" type="Animation" id=6]
-[node name="Container" type="Container"]
-margin_right = 40.0
-margin_bottom = 40.0
-script = ExtResource( 1 )
+[node name="Terminal" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+focus_mode = 2
+theme = ExtResource( 2 )
+script = ExtResource( 4 )
__meta__ = {
"_edit_use_anchors_": false
}
+rows = 31
+cols = 102
-[node name="Terminal" type="Control" parent="."]
-margin_right = 40.0
-margin_bottom = 40.0
-theme = ExtResource( 2 )
-script = ExtResource( 4 )
-
-[node name="AnimationPlayer" type="AnimationPlayer" parent="Terminal"]
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+autoplay = "example"
+playback_speed = 2.0
method_call_mode = 1
-anims/a = ExtResource( 6 )
+anims/example = ExtResource( 6 )
diff --git a/examples/asciicast/example.cast b/examples/asciicast/example.cast
index afba5c6..3ac1db0 100644
--- a/examples/asciicast/example.cast
+++ b/examples/asciicast/example.cast
@@ -1,61 +1,779 @@
-{"version": 2, "width": 86, "height": 29, "timestamp": 1589772748, "env": {"SHELL": "/run/current-system/sw/bin/bash", "TERM": "xterm"}}
-[0.082961, "o", "> "]
-[0.798002, "o", "e"]
-[0.893414, "o", "c"]
-[0.956255, "o", "h"]
-[1.008677, "o", "o"]
-[1.089472, "o", " "]
-[1.189602, "o", "h"]
-[1.266892, "o", "e"]
-[1.347483, "o", "l"]
-[1.46568, "o", "l"]
-[1.541039, "o", "o"]
-[1.726772, "o", "\r\n"]
-[1.727475, "o", "hello\r\n> "]
-[2.060109, "o", "#"]
-[2.179668, "o", " "]
-[2.471941, "o", "T"]
-[2.652735, "o", "h"]
-[2.746515, "o", "i"]
-[2.810578, "o", "s"]
-[2.921342, "o", " "]
-[2.98886, "o", "i"]
-[3.069095, "o", "s"]
-[3.31728, "o", " "]
-[3.399615, "o", "a"]
-[3.513605, "o", " "]
-[3.72609, "o", "d"]
-[3.811197, "o", "e"]
-[3.94649, "o", "m"]
-[4.047162, "o", "o"]
-[4.225042, "o", "\r\n"]
-[4.225402, "o", "> "]
-[4.935288, "o", "t"]
-[5.163552, "o", "o"]
-[5.323205, "o", "i"]
-[5.46746, "o", "l"]
-[5.561098, "o", "et "]
-[6.064937, "o", "-"]
-[6.41563, "o", "-"]
-[6.60443, "o", "g"]
-[6.666621, "o", "a"]
-[6.768317, "o", "y"]
-[6.848917, "o", " "]
-[7.076406, "o", "H"]
-[7.250067, "o", "E"]
-[7.410878, "o", "L"]
-[7.537016, "o", "L"]
-[7.604155, "o", "O"]
-[7.888992, "o", " "]
-[8.193437, "o", "W"]
-[8.365871, "o", "O"]
-[8.454678, "o", "R"]
-[8.525163, "o", "L"]
-[8.60286, "o", "D"]
-[8.873053, "o", "!"]
-[9.216434, "o", "\r\n"]
-[9.251462, "o", " \r\n \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;33;93mm\u001b[0m \u001b[0;1;35;95mm\u001b[0m \u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;33;93mm\u001b[0;1;32;92mmm\u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;32;92mm\u001b[0m \u001b[0;1;35;95mm\u001b[0;1;31;91mmm\u001b[0;1;33;93mm\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;36;96mm\u001b[0;1;34;94m\"\u001b[0m \u001b[0;1;35;95m\"\u001b[0;1;31;91mm\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92mm\"\u001b[0m \u001b[0;1;34;94m\"m\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m\"\u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;32;92m\"\u001b[0;1;36;96mm\u001b[0m\r\n \u001b[0;1;32;92m#\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b"]
-[9.251901, "o", "[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#\"\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m\r\n \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m##\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m\"\u001b[0;1;34;94mm\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m\r\n \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#m\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93mmm\u001b[0;1;32;92mm\u001b[0m \u001b[0;1;36;96m#m\u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0m \u001b[0;1;33;93m#m\u001b[0;1;32;92mm#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1"]
-[9.251944, "o", ";32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0;1;34;94mmm\u001b[0;1;35;95mm\"\u001b[0m \r\n \r\n \r\n \r\n \u001b[0;1;36;96mm\u001b[0m \r\n \u001b[0;1;34;94m#\u001b[0m \r\n \u001b[0;1;35;95m#\u001b[0m \r\n \u001b[0;1;31;91m\"\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \r\n \r\n \r\n"]
-[9.252259, "o", "> "]
-[12.56287, "o", "exit\r\n"]
+{"version": 2, "width": 95, "height": 56, "timestamp": 1601879053, "env": {"SHELL": "/run/current-system/sw/bin/bash", "TERM": "xterm"}}
+[0.009015, "o", "sh-4.4$ "]
+[0.768572, "o", "#"]
+[0.994511, "o", " "]
+[1.251891, "o", "P"]
+[1.390056, "o", "r"]
+[1.556055, "o", "e"]
+[1.633343, "o", "s"]
+[1.758128, "o", "s"]
+[1.837416, "o", " "]
+[2.082763, "o", "E"]
+[2.15487, "o", "S"]
+[2.206575, "o", "C"]
+[2.535156, "o", " "]
+[2.675284, "o", "t"]
+[2.764107, "o", "o"]
+[2.826769, "o", " "]
+[2.95468, "o", "e"]
+[3.107351, "o", "x"]
+[3.236995, "o", "i"]
+[3.315722, "o", "t"]
+[3.363186, "o", " "]
+[3.464495, "o", "t"]
+[3.502985, "o", "h"]
+[3.561326, "o", "i"]
+[3.637971, "o", "s"]
+[3.696915, "o", " "]
+[3.828476, "o", "d"]
+[3.920953, "o", "e"]
+[3.977016, "o", "m"]
+[4.080181, "o", "o"]
+[4.183719, "o", " "]
+[4.411889, "o", "a"]
+[4.505969, "o", "t"]
+[4.602301, "o", " "]
+[4.738613, "o", "a"]
+[4.815285, "o", "n"]
+[4.970011, "o", "y"]
+[5.048946, "o", " "]
+[5.140987, "o", "t"]
+[5.22994, "o", "i"]
+[5.300908, "o", "m"]
+[5.391697, "o", "e"]
+[5.833247, "o", "\r\n"]
+[5.833292, "o", "sh-4.4$ "]
+[6.497175, "o", "#"]
+[6.584674, "o", " "]
+[6.783908, "o", "T"]
+[6.982053, "o", "h"]
+[7.075839, "o", "i"]
+[7.121841, "o", "s"]
+[7.207194, "o", " "]
+[7.472273, "o", "t"]
+[7.56542, "o", "e"]
+[7.633672, "o", "r"]
+[7.708612, "o", "m"]
+[7.794029, "o", "i"]
+[7.852906, "o", "n"]
+[7.960908, "o", "a"]
+[8.034148, "o", "l"]
+[8.1316, "o", " "]
+[8.2592, "o", "s"]
+[8.376712, "o", "e"]
+[8.453216, "o", "s"]
+[8.583006, "o", "s"]
+[8.64767, "o", "i"]
+[8.711495, "o", "o"]
+[8.779941, "o", "n"]
+[8.862385, "o", " "]
+[9.112564, "o", "w"]
+[9.236889, "o", "a"]
+[9.29605, "o", "s"]
+[9.376424, "o", " "]
+[9.672264, "o", "r"]
+[9.768395, "o", "e"]
+[9.875859, "o", "c"]
+[10.360921, "o", "o"]
+[10.48178, "o", "r"]
+[10.573511, "o", "d"]
+[10.70553, "o", "e"]
+[10.772014, "o", "d"]
+[10.901859, "o", " "]
+[11.222266, "o", "u"]
+[11.314773, "o", "s"]
+[11.388137, "o", "i"]
+[11.462257, "o", "n"]
+[11.508588, "o", "g"]
+[11.610203, "o", " "]
+[11.704458, "o", "t"]
+[11.769386, "o", "h"]
+[11.829674, "o", "e"]
+[11.920249, "o", " "]
+[12.057327, "o", "a"]
+[12.150311, "o", "s"]
+[12.221965, "o", "c"]
+[12.335971, "o", "i"]
+[12.461568, "o", "i"]
+[12.659298, "o", "n"]
+[12.787306, "o", "e"]
+[12.861305, "o", "m"]
+[13.243252, "o", "a"]
+[13.415869, "o", " "]
+[13.640678, "o", "p"]
+[13.727956, "o", "r"]
+[13.79999, "o", "o"]
+[13.869981, "o", "g"]
+[13.932686, "o", "r"]
+[14.120489, "o", "a"]
+[14.563155, "o", "m"]
+[14.950597, "o", "\r\n"]
+[14.950636, "o", "sh-4.4$ "]
+[15.25787, "o", "#"]
+[15.357041, "o", " "]
+[15.549955, "o", "F"]
+[15.69595, "o", "o"]
+[15.742517, "o", "r"]
+[15.816029, "o", " "]
+[16.017346, "o", "m"]
+[16.134482, "o", "o"]
+[16.214998, "o", "r"]
+[16.287911, "o", "e"]
+[16.341746, "o", " "]
+[16.536357, "o", "i"]
+[16.63003, "o", "n"]
+[16.757694, "o", "f"]
+[16.840006, "o", "o"]
+[16.955199, "o", " "]
+[17.10089, "o", "v"]
+[17.212207, "o", "i"]
+[17.310707, "o", "s"]
+[17.433414, "o", "i"]
+[17.504056, "o", "t"]
+[17.639282, "o", " "]
+[18.027938, "o", "h"]
+[18.148473, "o", "t"]
+[18.254587, "o", "t"]
+[18.365992, "o", "p"]
+[18.422981, "o", "s"]
+[18.69077, "o", ":"]
+[18.981756, "o", "/"]
+[19.072248, "o", "/"]
+[19.451925, "o", "a"]
+[19.52595, "o", "s"]
+[19.590197, "o", "c"]
+[19.690475, "o", "i"]
+[19.812762, "o", "i"]
+[20.002006, "o", "n"]
+[20.285781, "o", "e"]
+[20.436427, "o", "m"]
+[20.528716, "o", "a"]
+[20.763135, "o", "."]
+[20.903967, "o", "o"]
+[20.975119, "o", "r"]
+[20.985324, "o", "g"]
+[20.99331, "o", "\r\nsh-4.4$ "]
+[21.605944, "o", "#"]
+[21.807285, "o", " "]
+[22.010063, "o", "L"]
+[22.188024, "o", "e"]
+[22.279903, "o", "t"]
+[22.361474, "o", "'"]
+[22.486357, "o", "s"]
+[22.57465, "o", " "]
+[22.740959, "o", "i"]
+[22.813009, "o", "n"]
+[22.857842, "o", "s"]
+[22.954863, "o", "t"]
+[23.066967, "o", "a"]
+[23.150687, "o", "l"]
+[23.263568, "o", "l"]
+[23.34495, "o", " "]
+[24.24858, "o", "a"]
+[24.339416, "o", "s"]
+[24.418355, "o", "c"]
+[24.541209, "o", "i"]
+[24.697872, "o", "i"]
+[24.872434, "o", "n"]
+[24.964613, "o", "e"]
+[25.07858, "o", "m"]
+[25.153616, "o", "a"]
+[26.123044, "o", "\r\n"]
+[26.123082, "o", "sh-4.4$ "]
+[26.610637, "o", "n"]
+[26.731314, "o", "i"]
+[26.840493, "o", "x"]
+[26.950531, "o", "-"]
+[27.030416, "o", "e"]
+[27.143202, "o", "nv "]
+[27.380791, "o", "-"]
+[27.494532, "o", "i"]
+[27.766916, "o", "A"]
+[27.947392, "o", " "]
+[28.069982, "o", "n"]
+[28.153264, "o", "i"]
+[28.285577, "o", "x"]
+[28.398953, "o", "o"]
+[28.528587, "o", "s"]
+[28.714452, "o", "."]
+[29.066201, "o", "a"]
+[29.178501, "o", "s"]
+[29.257687, "o", "c"]
+[29.36677, "o", "i"]
+[29.487456, "o", "i"]
+[29.617427, "o", "n"]
+[29.708982, "o", "e"]
+[29.793394, "o", "m"]
+[29.885927, "o", "a"]
+[30.166746, "o", "\r\n"]
+[30.269591, "o", "installing 'asciinema-2.0.2'\r\n"]
+[30.442995, "o", "these paths will be fetched (0.05 MiB download, 0.19 MiB unpacked):\r\n /nix/store/jn1kalsv6g21rzhr46wsmb33rlbnxbp2-asciinema-2.0.2\r\n"]
+[30.443793, "o", "copying path '/nix/store/jn1kalsv6g21rzhr46wsmb33rlbnxbp2-asciinema-2.0.2' from 'https://aseipp-nix-cache.global.ssl.fastly.net'...\r\n"]
+[30.772997, "o", "building '/nix/store/j8zk1ixrzz8ffl2r8bfcblrbp8jib14n-user-environment.drv'...\r\n"]
+[30.827776, "o", "created 2238 symlinks in user environment\r\n"]
+[30.837521, "o", "\r\nsh-4.4$ "]
+[31.439756, "o", "#"]
+[31.531759, "o", " "]
+[31.733672, "o", "N"]
+[31.885784, "o", "o"]
+[31.969473, "o", "w"]
+[32.04399, "o", " "]
+[32.184525, "o", "t"]
+[32.498387, "o", "\b\u001b[K"]
+[32.674218, "o", "w"]
+[32.780078, "o", "e"]
+[32.869318, "o", " "]
+[33.015693, "o", "c"]
+[33.131168, "o", "a"]
+[33.215492, "o", "n"]
+[33.277638, "o", " "]
+[33.435631, "o", "r"]
+[33.491613, "o", "e"]
+[33.769703, "o", "c"]
+[33.878877, "o", "o"]
+[33.964167, "o", "r"]
+[34.051787, "o", "d"]
+[34.231502, "o", " "]
+[34.38911, "o", "a"]
+[34.517173, "o", " "]
+[34.660129, "o", "n"]
+[34.75175, "o", "e"]
+[34.83474, "o", "w"]
+[34.908203, "o", " "]
+[36.304364, "o", "s"]
+[36.387211, "o", "e"]
+[36.465211, "o", "s"]
+[36.596101, "o", "s"]
+[36.695781, "o", "i"]
+[36.762876, "o", "o"]
+[36.824392, "o", "n"]
+[36.922221, "o", " "]
+[37.108339, "o", "t"]
+[37.264521, "o", "o"]
+[37.361574, "o", " "]
+[37.490987, "o", "a"]
+[37.578879, "o", " "]
+[38.018804, "o", "."]
+[38.110959, "o", "c"]
+[38.210231, "o", "a"]
+[38.270978, "o", "s"]
+[38.3198, "o", "t"]
+[38.402376, "o", " "]
+[38.566701, "o", "f"]
+[38.660991, "o", "i"]
+[38.73289, "o", "l"]
+[38.813984, "o", "e"]
+[39.342188, "o", "\r\n"]
+[39.342365, "o", "sh-4.4$ "]
+[39.885874, "o", "a"]
+[39.959473, "o", "s"]
+[40.034948, "o", "c"]
+[40.133815, "o", "i"]
+[40.215169, "o", "inema "]
+[40.590943, "o", "r"]
+[40.672976, "o", "e"]
+[40.752245, "o", "c"]
+[40.83314, "o", " "]
+[41.861795, "o", "n"]
+[41.938138, "o", "e"]
+[42.032994, "o", "w"]
+[42.144281, "o", "."]
+[42.250499, "o", "c"]
+[42.323403, "o", "a"]
+[42.407712, "o", "s"]
+[42.470832, "o", "t"]
+[42.814799, "o", "\r\n"]
+[42.90735, "o", "\u001b[0;32masciinema: recording asciicast to new.cast\u001b[0m\r\n\u001b[0;32masciinema: press or type \"exit\" when you're done\u001b[0m\r\n"]
+[42.951473, "o", "\u001b]2;laptop:leroy:~/projects/godot-xterm\u0007\r\r\n\u001b[1;32m[\u001b]0;leroy@laptop: ~/projects/godot-xterm\u0007leroy@laptop:~/projects/godot-xterm]$\u001b[0m "]
+[45.647452, "o", "l"]
+[45.943288, "o", "s"]
+[46.286881, "o", "\r\n"]
+[46.288383, "o", "\u001b[0m\u001b[01;34maddons\u001b[0m docker-compose.yml example.cast \u001b[01;35micon.png\u001b[0m new.cast\r\nCHANGELOG.md \u001b[01;34mdockerfiles\u001b[0m example.cast.import icon.png.import project.godot\r\ndefault_env.tres \u001b[01;34mdocs\u001b[0m \u001b[01;34mexamples\u001b[0m LICENSE README.md\r\n"]
+[46.288974, "o", "\u001b]2;laptop:leroy:~/projects/godot-xterm\u0007\r\r\n\u001b[1;32m[\u001b]0;leroy@laptop: ~/projects/godot-xterm\u0007leroy@laptop:~/projects/godot-xterm]$\u001b[0m "]
+[48.264058, "o", "s"]
+[48.35698, "o", "h"]
+[48.465218, "o", "a"]
+[48.860943, "o", "1"]
+[49.137467, "o", "s"]
+[49.255023, "o", "u"]
+[49.402646, "o", "m"]
+[49.994215, "o", " "]
+[50.137741, "o", "."]
+[50.251377, "o", "/"]
+[50.478669, "o", "*"]
+[51.035519, "o", " "]
+[51.587166, "o", "|"]
+[51.713609, "o", " "]
+[51.863023, "o", "l"]
+[51.96232, "o", "o"]
+[52.012303, "o", "l"]
+[52.240101, "o", "c"]
+[52.353223, "o", "a"]
+[52.447717, "o", "t"]
+[52.565175, "o", " "]
+[52.947413, "o", "-"]
+[53.353261, "o", "F"]
+[53.634436, "o", " "]
+[53.936746, "o", "0"]
+[54.099132, "o", "."]
+[54.364446, "o", "3"]
+[54.66516, "o", "\r\n"]
+[54.666593, "o", "sha1sum: ./addons: Is a directory\r\n"]
+[54.66663, "o", "sha1sum: ./dockerfiles: Is a directory\r\nsha1sum: ./docs: Is a directory\r\nsha1sum: ./examples: Is a directory\r\n"]
+[54.819505, "o", "\u001b[38;2;180;1;202mb\u001b[39m\u001b[38;2;168;3;211m2\u001b[39m\u001b[38;2;156;6;221mf\u001b[39m"]
+[54.819607, "o", "\u001b[38;2;143;10;229m7\u001b[39m\u001b[38;2;131;16;236m9\u001b[39m\u001b[38;2;118;23;242me\u001b[39m"]
+[54.819638, "o", "\u001b[38;2;105;30;247m8\u001b[39m\u001b[38;2;93;39;251mb\u001b[39m\u001b[38;2;81;48;253m2\u001b[39m\u001b[38;2;69;59;254mc\u001b[39m\u001b[38;2;58;70;254m2\u001b[39m"]
+[54.819747, "o", "\u001b[38;2;48;81;253m9\u001b[39m\u001b[38;2;39;93;251m1\u001b[39m"]
+[54.819806, "o", "\u001b[38;2;30;106;247ma\u001b[39m\u001b[38;2;22;118;242m7\u001b[39m"]
+[54.819936, "o", "\u001b[38;2;16;131;236m9\u001b[39m\u001b[38;2;10;144;228m2\u001b[39m\u001b[38;2;6;156;220m7\u001b[39m\u001b[38;2;3;168;211m4\u001b[39m\u001b[38;2;1;180;201m3\u001b[39m"]
+[54.820001, "o", "\u001b[38;2;1;191;191m2\u001b[39m\u001b[38;2;1;202;179m2\u001b[39m"]
+[54.820056, "o", "\u001b[38;2;3;212;167m2\u001b[39m\u001b[38;2;6;221;155md\u001b[39m\u001b[38;2;11;229;143m8"]
+[54.82013, "o", "\u001b[39m\u001b[38;2;16;236;130md\u001b[39m\u001b[38;2;23;242;117m4\u001b[39m\u001b[38;2;31;247;105m7\u001b[39m"]
+[54.820187, "o", "\u001b[38;2;39;251;92me\u001b[39m\u001b[38;2;49;253;80mc\u001b[39m"]
+[54.820245, "o", "\u001b[38;2;59;254;69m2\u001b[39m\u001b[38;2;70;254;58m5\u001b[39m"]
+[54.820314, "o", "\u001b[38;2;82;253;48me\u001b[39m\u001b[38;2;94;250;38m8\u001b[39m\u001b[38;2;106;246;30m1"]
+[54.820376, "o", "\u001b[39m\u001b[38;2;119;241;22m3\u001b[39m\u001b[38;2;132;235;15ma\u001b[39m"]
+[54.820439, "o", "\u001b[38;2;144;228;10m5\u001b[39m\u001b[38;2;157;220;6m2\u001b[39m\u001b[38;2;169;211;3m3\u001b[39m"]
+[54.820504, "o", "\u001b[38;2;181;201;1m \u001b[39m\u001b[38;2;192;190;1m \u001b[39m"]
+[54.820559, "o", "\u001b[38;2;203;179;1m.\u001b[39m\u001b[38;2;213;167;3m/\u001b[39m"]
+[54.82062, "o", "\u001b[38;2;222;154;7mC\u001b[39m\u001b[38;2;230;142;11mH\u001b[39m\u001b[38;2;237;129;17mA\u001b[39m"]
+[54.820678, "o", "\u001b[38;2;243;117;23mN\u001b[39m\u001b[38;2;247;104;31mG\u001b[39m"]
+[54.820738, "o", "\u001b[38;2;251;92;40mE\u001b[39m\u001b[38;2;253;80;50mL\u001b[39m"]
+[54.820795, "o", "\u001b[38;2;254;68;60mO\u001b[39m\u001b[38;2;254;57;71mG\u001b[39m\u001b[38;2;253;47;83m.\u001b[39m"]
+[54.820855, "o", "\u001b[38;2;250;38;95mm\u001b[39m\u001b[38;2;246;29;107md\u001b[39m"]
+[54.820926, "o", "\u001b[38;2;241;22;120m\u001b[39m\r\n"]
+[54.820985, "o", "\u001b[38;2;143;10;229m8\u001b[39m"]
+[54.821038, "o", "\u001b[38;2;131;16;236m0\u001b[39m\u001b[38;2;118;23;242m4\u001b[39m\u001b[38;2;105;30;247m2\u001b[39m"]
+[54.821094, "o", "\u001b[38;2;93;39;251m5\u001b[39m\u001b[38;2;81;48;253m3\u001b[39m"]
+[54.821149, "o", "\u001b[38;2;69;59;254mf\u001b[39m\u001b[38;2;58;70;254m5\u001b[39m"]
+[54.821206, "o", "\u001b[38;2;48;81;253m2\u001b[39m\u001b[38;2;39;93;251m6\u001b[39m"]
+[54.821258, "o", "\u001b[38;2;30;106;247m6\u001b[39m\u001b[38;2;22;118;242m5\u001b[39m"]
+[54.821311, "o", "\u001b[38;2;16;131;236mc\u001b[39m"]
+[54.821381, "o", "\u001b[38;2;10;144;228m6\u001b[39m"]
+[54.821436, "o", "\u001b[38;2;6;156;220m8\u001b[39m\u001b[38;2;3;168;211m7\u001b[39m\u001b[38;2;1;180;201md\u001b[39m"]
+[54.821486, "o", "\u001b[38;2;1;191;191m6\u001b[39m\u001b[38;2;1;202;179m6\u001b[39m\u001b[38;2;3;212;167mb\u001b[39m"]
+[54.821577, "o", "\u001b[38;2;6;221;155m4\u001b[39m\u001b[38;2;11;229;143ma\u001b[39m\u001b[38;2;16;236;130m2\u001b[39m\u001b[38;2;23;242;117m4\u001b[39m\u001b[38;2;31;247;105mb\u001b[39m"]
+[54.821633, "o", "\u001b[38;2;39;251;92m3\u001b[39m\u001b[38;2;49;253;80m9\u001b[39m"]
+[54.82166, "o", "\u001b[38;2;59;254;69mf\u001b[39m"]
+[54.821741, "o", "\u001b[38;2;70;254;58m1\u001b[39m\u001b[38;2;82;253;48mc\u001b[39m\u001b[38;2;94;250;38m3\u001b[39m"]
+[54.821906, "o", "\u001b[38;2;106;246;30m2\u001b[39m\u001b[38;2;119;241;22m8\u001b[39m\u001b[38;2;132;235;15m6\u001b[39m\u001b[38;2;144;228;10m9\u001b[39m\u001b[38;2;157;220;6m6\u001b[39m"]
+[54.822031, "o", "\u001b[38;2;169;211;3m7\u001b[39m"]
+[54.822114, "o", "\u001b[38;2;181;201;1mc\u001b[39m"]
+[54.822971, "o", "\u001b[38;2;192;190;1m9\u001b[39m"]
+[54.82305, "o", "\u001b[38;2;203;179;1md\u001b[39m\u001b[38;2;213;167;3m \u001b[39m\u001b[38;2;222;154;7m \u001b[39m\u001b[38;2;230;142;11m.\u001b[39m"]
+[54.823129, "o", "\u001b[38;2;237;129;17m/\u001b[39m\u001b[38;2;243;117;23md\u001b[39m"]
+[54.823188, "o", "\u001b[38;2;247;104;31me\u001b[39m"]
+[54.823241, "o", "\u001b[38;2;251;92;40mf\u001b[39m"]
+[54.823306, "o", "\u001b[38;2;253;80;50ma\u001b[39m\u001b[38;2;254;68;60mu\u001b[39m"]
+[54.823368, "o", "\u001b[38;2;254;57;71ml\u001b[39m"]
+[54.823436, "o", "\u001b[38;2;253;47;83mt\u001b[39m\u001b[38;2;250;38;95m_\u001b[39m"]
+[54.823503, "o", "\u001b[38;2;246;29;107me\u001b[39m\u001b[38;2;241;22;120mn\u001b[39m"]
+[54.823562, "o", "\u001b[38;2;235;15;132mv\u001b[39m\u001b[38;2;228;10;145m.\u001b[39m"]
+[54.823622, "o", "\u001b[38;2;219;6;158mt\u001b[39m"]
+[54.82368, "o", "\u001b[38;2;210;3;170mr\u001b[39m\u001b[38;2;200;1;182me\u001b[39m"]
+[54.82374, "o", "\u001b[38;2;189;1;193ms\u001b[39m\u001b[38;2;178;1;203m\u001b[39m\r\n"]
+[54.823865, "o", "\u001b[38;2;105;30;247ma\u001b[39m"]
+[54.823926, "o", "\u001b[38;2;93;39;251ma\u001b[39m\u001b[38;2;81;48;253ma\u001b[39m\u001b[38;2;69;59;254m3\u001b[39m"]
+[54.823989, "o", "\u001b[38;2;58;70;254md\u001b[39m\u001b[38;2;48;81;253ma\u001b[39m"]
+[54.824055, "o", "\u001b[38;2;39;93;251me\u001b[39m\u001b[38;2;30;106;247m7\u001b[39m"]
+[54.824148, "o", "\u001b[38;2;22;118;242ma\u001b[39m\u001b[38;2;16;131;236m2\u001b[39m"]
+[54.824208, "o", "\u001b[38;2;10;144;228m9\u001b[39m\u001b[38;2;6;156;220m8\u001b[39m"]
+[54.824275, "o", "\u001b[38;2;3;168;211me\u001b[39m\u001b[38;2;1;180;201m9\u001b[39m"]
+[54.824347, "o", "\u001b[38;2;1;191;191m5\u001b[39m\u001b[38;2;1;202;179m8\u001b[39m"]
+[54.824414, "o", "\u001b[38;2;3;212;167m4\u001b[39m\u001b[38;2;6;221;155m1\u001b[39m"]
+[54.824469, "o", "\u001b[38;2;11;229;143md\u001b[39m\u001b[38;2;16;236;130m8\u001b[39m"]
+[54.824557, "o", "\u001b[38;2;23;242;117m4\u001b[39m\u001b[38;2;31;247;105m7\u001b[39m\u001b[38;2;39;251;92m5\u001b[39m"]
+[54.824618, "o", "\u001b[38;2;49;253;80ma\u001b[39m\u001b[38;2;59;254;69mc\u001b[39m"]
+[54.824683, "o", "\u001b[38;2;70;254;58m2\u001b[39m\u001b[38;2;82;253;48m3\u001b[39m"]
+[54.824738, "o", "\u001b[38;2;94;250;38mb\u001b[39m"]
+[54.824796, "o", "\u001b[38;2;106;246;30m8\u001b[39m\u001b[38;2;119;241;22ma\u001b[39m"]
+[54.824852, "o", "\u001b[38;2;132;235;15me\u001b[39m\u001b[38;2;144;228;10mc\u001b[39m"]
+[54.82491, "o", "\u001b[38;2;157;220;6m7\u001b[39m\u001b[38;2;169;211;3ma"]
+[54.825026, "o", "\u001b[39m\u001b[38;2;181;201;1mb\u001b[39m\u001b[38;2;192;190;1mb\u001b[39m\u001b[38;2;203;179;1mb\u001b[39m"]
+[54.825088, "o", "\u001b[38;2;213;167;3ma\u001b[39m\u001b[38;2;222;154;7ma\u001b[39m"]
+[54.825155, "o", "\u001b[38;2;230;142;11m2\u001b[39m\u001b[38;2;237;129;17m \u001b[39m\u001b[38;2;243;117;23m \u001b[39m"]
+[54.825218, "o", "\u001b[38;2;247;104;31m.\u001b[39m\u001b[38;2;251;92;40m/\u001b[39m"]
+[54.825294, "o", "\u001b[38;2;253;80;50md\u001b[39m"]
+[54.825351, "o", "\u001b[38;2;254;68;60mo\u001b[39m\u001b[38;2;254;57;71mc\u001b[39m"]
+[54.825374, "o", "\u001b[38;2;253;47;83mk\u001b[39m"]
+[54.825398, "o", "\u001b[38;2;250;38;95me\u001b[39m"]
+[54.825502, "o", "\u001b[38;2;246;29;107mr\u001b[39m\u001b[38;2;241;22;120m-\u001b[39m\u001b[38;2;235;15;132mc\u001b[39m"]
+[54.82557, "o", "\u001b[38;2;228;10;145mo\u001b[39m\u001b[38;2;219;6;158mm\u001b[39m"]
+[54.825634, "o", "\u001b[38;2;210;3;170mp\u001b[39m\u001b[38;2;200;1;182mo\u001b[39m"]
+[54.825699, "o", "\u001b[38;2;189;1;193ms\u001b[39m\u001b[38;2;178;1;203me\u001b[39m"]
+[54.825832, "o", "\u001b[38;2;166;3;213m.\u001b[39m"]
+[54.825889, "o", "\u001b[38;2;154;7;222my\u001b[39m"]
+[54.825917, "o", "\u001b[38;2;141;11;230mm\u001b[39m"]
+[54.825938, "o", "\u001b[38;2;129;17;237ml\u001b[39m"]
+[54.826017, "o", "\u001b[38;2;116;24;243m\u001b[39m\r\n"]
+[54.826105, "o", "\u001b[38;2;69;59;254m2\u001b[39m\u001b[38;2;58;70;254m8\u001b[39m"]
+[54.826172, "o", "\u001b[38;2;48;81;253m8\u001b[39m\u001b[38;2;39;93;251mb\u001b[39m"]
+[54.826245, "o", "\u001b[38;2;30;106;247m0\u001b[39m\u001b[38;2;22;118;242me\u001b[39m"]
+[54.826317, "o", "\u001b[38;2;16;131;236m"]
+[54.826382, "o", "4\u001b[39m\u001b[38;2;10;144;228md\u001b[39m"]
+[54.826442, "o", "\u001b[38;2;6;156;220mb\u001b[39m"]
+[54.826501, "o", "\u001b[38;2;3;168;211m3\u001b[39m\u001b[38;2;1;180;201m2\u001b[39m\u001b[38;2;1;191;191m1\u001b[39m"]
+[54.826559, "o", "\u001b[38;2;1;202;179mb\u001b[39m"]
+[54.826618, "o", "\u001b[38;2;3;212;167m8\u001b[39m"]
+[54.826676, "o", "\u001b[38;2;6;221;155m3\u001b[39m\u001b[38;2;11;229;143mb\u001b[39m"]
+[54.826734, "o", "\u001b[38;2;16;236;130m0\u001b[39m\u001b[38;2;23;242;117m1\u001b[39m\u001b[38;2;31;247;105m2"]
+[54.826792, "o", "\u001b[39m\u001b[38;2;39;251;92m6"]
+[54.826849, "o", "\u001b[39m\u001b[38;2;49;253;80m3\u001b[39m\u001b[38;2;59;254;69m0\u001b[39m"]
+[54.826908, "o", "\u001b[38;2;70;254;58md\u001b[39m"]
+[54.826965, "o", "\u001b[38;2;82;253;48m1\u001b[39m\u001b[38;2;94;250;38md\u001b[39m"]
+[54.827023, "o", "\u001b[38;2;106;246;30md\u001b[39m\u001b[38;2;119;241;22m3\u001b[39m"]
+[54.827081, "o", "\u001b[38;2;132;235;15m7\u001b[39m"]
+[54.82714, "o", "\u001b[38;2;144;228;10mb\u001b[39m\u001b[38;2;157;220;6me\u001b[39m"]
+[54.827212, "o", "\u001b[38;2;169;211;3m2\u001b[39m"]
+[54.827282, "o", "\u001b[38;2;181;201;1m3\u001b[39m"]
+[54.827347, "o", "\u001b[38;2;192;190;1m9\u001b[39m\u001b[38;2;203;179;1m9\u001b[39m\u001b[38;2;213;167;3ma\u001b[39m"]
+[54.827406, "o", "\u001b[38;2;222;154;7m8\u001b[39m"]
+[54.827466, "o", "\u001b[38;2;230;142;11m9\u001b[39m\u001b[38;2;237;129;17m6\u001b[39m"]
+[54.827525, "o", "\u001b[38;2;243;117;23m4\u001b[39m\u001b[38;2;247;104;31m7\u001b[39m"]
+[54.827584, "o", "\u001b[38;2;251;92;40m \u001b[39m"]
+[54.827643, "o", "\u001b[38;2;253;80;50m \u001b[39m"]
+[54.827701, "o", "\u001b[38;2;254;68;60m.\u001b[39m\u001b[38;2;254;57;71m/\u001b[39m"]
+[54.827762, "o", "\u001b[38;2;253;47;83me\u001b[39m\u001b[38;2;250;38;95mx\u001b[39m"]
+[54.827821, "o", "\u001b[38;2;246;29;107ma\u001b[39m\u001b[38;2;241;22;120mm"]
+[54.827881, "o", "\u001b[39m\u001b[38;2;235;15;132mp\u001b[39m\u001b[38;2;228;10;145m"]
+[54.82794, "o", "l\u001b[39m\u001b[38;2;219;6;158m"]
+[54.827998, "o", "e\u001b[39m\u001b[38;2;210;3;170m.\u001b[39m\u001b[38;2;200;1;182mc"]
+[54.828058, "o", "\u001b[39m\u001b[38;2;189;1;193ma\u001b[39m"]
+[54.828118, "o", "\u001b[38;2;178;1;203ms\u001b[39m"]
+[54.82864, "o", "\u001b[38;2;166;3;213mt\u001b[39m"]
+[54.828711, "o", "\u001b[38;2;154;7;222m\u001b[39m\r\n"]
+[54.828773, "o", "\u001b[38;2;39;93;251m5\u001b[39m"]
+[54.828835, "o", "\u001b[38;2;30;106;247md\u001b[39m\u001b[38;2;22;118;242m5"]
+[54.828897, "o", "\u001b[39m\u001b[38;2;16;131;236m1\u001b[39m\u001b[38;2;10;144;228md\u001b[39m"]
+[54.828954, "o", "\u001b[38;2;6;156;220m3\u001b[39m"]
+[54.829012, "o", "\u001b[38;2;3;168;211m9\u001b[39m"]
+[54.829075, "o", "\u001b[38;2;1;180;201m8\u001b[39m\u001b[38;2;1;191;191mf\u001b[39m"]
+[54.829137, "o", "\u001b[38;2;1;202;179m4\u001b[39m"]
+[54.829213, "o", "\u001b[38;2;3;212;167m7\u001b[39m\u001b[38;2;6;221;155m1\u001b[39m"]
+[54.829279, "o", "\u001b[38;2;11;229;143md\u001b[39m"]
+[54.829412, "o", "\u001b[38;2;16;236;130m8\u001b[39m"]
+[54.829498, "o", "\u001b[38;2;23;242;117ma\u001b[39m"]
+[54.829565, "o", "\u001b[38;2;31;247;105ma\u001b[39m"]
+[54.829626, "o", "\u001b[38;2;39;251;92me\u001b[39m"]
+[54.829695, "o", "\u001b[38;2;49;253;80m2\u001b[39m\u001b[38;2;59;254;69m7\u001b[39m"]
+[54.829729, "o", "\u001b[38;2;70;254;58mf\u001b[39m\u001b[38;2;82;253;48m0\u001b[39m"]
+[54.829814, "o", "\u001b[38;2;94;250;38m0\u001b[39m"]
+[54.829875, "o", "\u001b[38;2;106;246;30m8\u001b[39m\u001b[38;2;119;241;22mf\u001b[39m\u001b[38;2;132;235;15mf\u001b[39m"]
+[54.829931, "o", "\u001b[38;2;144;228;10m9\u001b[39m\u001b[38;2;157;220;6m3\u001b[39m"]
+[54.829981, "o", "\u001b[38;2;169;211;3m8\u001b[39m"]
+[54.830032, "o", "\u001b[38;2;181;201;1m1\u001b[39m"]
+[54.830082, "o", "\u001b[38;2;192;190;1m8\u001b[39m\u001b[38;2;203;179;1md\u001b[39m"]
+[54.830141, "o", "\u001b[38;2;213;167;3m2\u001b[39m\u001b[38;2;222;154;7mc"]
+[54.830201, "o", "\u001b[39m\u001b[38;2;230;142;11m4\u001b[39m\u001b[38;2;237;129;17m5\u001b[39m"]
+[54.830254, "o", "\u001b[38;2;243;117;23m6\u001b[39m"]
+[54.830316, "o", "\u001b[38;2;247;104;31m5\u001b[39m\u001b[38;2;251;92;40md\u001b[39m"]
+[54.830371, "o", "\u001b[38;2;253;80;50m6\u001b[39m"]
+[54.830401, "o", "\u001b[38;2;254;68;60m9\u001b[39m"]
+[54.83049, "o", "\u001b[38;2;254;57;71m \u001b[39m\u001b[38;2;253;47;83m \u001b[39m\u001b[38;2;250;38;95m."]
+[54.830522, "o", "\u001b[39m"]
+[54.830611, "o", "\u001b[38;2;246;29;107m/\u001b[39m\u001b[38;2;241;22;120me\u001b[39m\u001b[38;2;235;15;132mx"]
+[54.830665, "o", "\u001b[39m\u001b[38;2;228;10;145ma\u001b[39m"]
+[54.830719, "o", "\u001b[38;2;219;6;158mm\u001b[39m"]
+[54.830767, "o", "\u001b[38;2;210;3;170mp\u001b[39m"]
+[54.830816, "o", "\u001b[38;2;200;1;182ml\u001b[39m\u001b[38;2;189;1;193me"]
+[54.830869, "o", "\u001b[39m\u001b[38;2;178;1;203m.\u001b[39m\u001b[38;2;166;3;213mc\u001b[39m"]
+[54.830919, "o", "\u001b[38;2;154;7;222ma\u001b[39m"]
+[54.830972, "o", "\u001b[38;2;141;11;230ms\u001b[39m"]
+[54.831023, "o", "\u001b[38;2;129;17;237mt\u001b[39m\u001b[38;2;116;24;243m.\u001b[39m"]
+[54.831079, "o", "\u001b[38;2;103;32;248mi\u001b[39m"]
+[54.831107, "o", "\u001b[38;2;91;40;251mm\u001b[39m"]
+[54.831169, "o", "\u001b[38;2;79;50;253mp\u001b[39m\u001b[38;2;68;61;254m"]
+[54.831239, "o", "o\u001b[39m\u001b[38;2;57;72;254mr\u001b[39m\u001b[38;2;46;83;253mt\u001b[39m"]
+[54.831306, "o", "\u001b[38;2;37;95;250m\u001b[39m\r\n"]
+[54.831393, "o", "\u001b[38;2;16;131;236mf\u001b[39m"]
+[54.83146, "o", "\u001b[38;2;10;144;228m5\u001b[39m"]
+[54.831523, "o", "\u001b[38;2;6;156;220m7\u001b[39m\u001b[38;2;3;168;211ma\u001b[39m"]
+[54.831583, "o", "\u001b[38;2;1;180;201m3\u001b[39m\u001b[38;2;1;191;191m5\u001b[39m"]
+[54.831645, "o", "\u001b[38;2;1;202;179me\u001b[39m"]
+[54.831696, "o", "\u001b[38;2;3;212;167m0\u001b[39m\u001b[38;2;6;221;155md\u001b[39m"]
+[54.831747, "o", "\u001b[38;2;11;229;143ma\u001b[39m"]
+[54.831797, "o", "\u001b[38;2;16;236;130m6\u001b[39m"]
+[54.831848, "o", "\u001b[38;2;23;242;117m2\u001b[39m\u001b[38;2;31;247;105m5\u001b[39m"]
+[54.831898, "o", "\u001b[38;2;39;251;92m9\u001b[39m"]
+[54.831948, "o", "\u001b[38;2;49;253;80m2\u001b[39m"]
+[54.831999, "o", "\u001b[38;2;59;254;69md\u001b[39m\u001b[38;2;70;254;58me\u001b[39m"]
+[54.832049, "o", "\u001b[38;2;82;253;48m5\u001b[39m"]
+[54.8321, "o", "\u001b[38;2;94;250;38ma\u001b[39m"]
+[54.832149, "o", "\u001b[38;2;106;246;30m0\u001b[39m"]
+[54.8322, "o", "\u001b[38;2;119;241;22m7\u001b[39m\u001b[38;2;132;235;15m6\u001b[39m\u001b[38;2;144;228;10ma\u001b[39m"]
+[54.832265, "o", "\u001b[38;2;157;220;6m5\u001b[39m"]
+[54.832324, "o", "\u001b[38;2;169;211;3m8\u001b[39m"]
+[54.832371, "o", "\u001b[38;2;181;201;1m1\u001b[39m"]
+[54.832421, "o", "\u001b[38;2;192;190;1m1\u001b[39m\u001b[38;2;203;179;1m9\u001b[39m"]
+[54.832469, "o", "\u001b[38;2;213;167;3m0\u001b[39m"]
+[54.832518, "o", "\u001b[38;2;222;154;7m4\u001b[39m"]
+[54.832565, "o", "\u001b[38;2;230;142;11m5\u001b[39m\u001b[38;2;237;129;17md\u001b[39m"]
+[54.832614, "o", "\u001b[38;2;243;117;23m2\u001b[39m"]
+[54.832665, "o", "\u001b[38;2;247;104;31m4\u001b[39m\u001b[38;2;251;92;40m9\u001b[39m"]
+[54.832733, "o", "\u001b[38;2;253;80;50m0\u001b[39m"]
+[54.832783, "o", "\u001b[38;2;254;68;60m8\u001b[39m\u001b[38;2;254;57;71m9\u001b[39m"]
+[54.832831, "o", "\u001b[38;2;253;47;83m1\u001b[39m"]
+[54.83288, "o", "\u001b[38;2;250;38;95m9\u001b[39m"]
+[54.832927, "o", "\u001b[38;2;246;29;107m \u001b[39m\u001b[38;2;241;22;120m \u001b[39m"]
+[54.832975, "o", "\u001b[38;2;235;15;132m.\u001b[39m"]
+[54.833023, "o", "\u001b[38;2;228;10;145m/\u001b[39m"]
+[54.833121, "o", "\u001b[38;2;219;6;158mi\u001b[39m"]
+[54.83321, "o", "\u001b[38;2;210;3;170mc\u001b[39m"]
+[54.833278, "o", "\u001b[38;2;200;1;182mo\u001b[39m\u001b[38;2;189;1;193mn\u001b[39m"]
+[54.83331, "o", "\u001b[38;2;178;1;203m.\u001b[39m"]
+[54.833363, "o", "\u001b[38;2;166;3;213mp\u001b[39m"]
+[54.833433, "o", "\u001b[38;2;154;7;222mn\u001b[39m\u001b[38;2;141;11;230mg\u001b[39m\u001b[38;2;129;17;237m\u001b[39m"]
+[54.833487, "o", "\r\n"]
+[54.833545, "o", "\u001b[38;2;3;168;211md\u001b[39m"]
+[54.833605, "o", "\u001b[38;2;1;180;201m8\u001b[39m"]
+[54.833658, "o", "\u001b[38;2;1;191;191ma\u001b[39m"]
+[54.833717, "o", "\u001b[38;2;1;202;179m1\u001b[39m\u001b[38;2;3;212;167m0\u001b[39m"]
+[54.833786, "o", "\u001b[38;2;6;221;155me\u001b[39m"]
+[54.833864, "o", "\u001b[38;2;11;229;143m8\u001b[39m"]
+[54.833928, "o", "\u001b[38;2;16;236;130m5\u001b[39m\u001b[38;2;23;242;117m7\u001b[39m"]
+[54.833993, "o", "\u001b[38;2;31;247;105m1\u001b[39m"]
+[54.834054, "o", "\u001b[38;2;39;251;92mb\u001b[39m\u001b[38;2;49;253;80m7\u001b[39m"]
+[54.834111, "o", "\u001b[38;2;59;254;69m8\u001b[39m\u001b[38;2;70;254;58m9\u001b[39m"]
+[54.834177, "o", "\u001b[38;2;82;253;48mb\u001b[39m"]
+[54.834256, "o", "\u001b[38;2;94;250;38mc\u001b[39m\u001b[38;2;106;246;30m7\u001b[39m"]
+[54.834328, "o", "\u001b[38;2;119;241;22ma\u001b[39m\u001b[38;2;132;235;15mc"]
+[54.834387, "o", "\u001b[39m"]
+[54.834938, "o", "\u001b[38;2;144;228;10m0\u001b[39m"]
+[54.83501, "o", "\u001b[38;2;157;220;6m9\u001b[39m\u001b[38;2;169;211;3mb\u001b[39m"]
+[54.83508, "o", "\u001b[38;2;181;201;1ma\u001b[39m\u001b[38;2;192;190;1me\u001b[39m\u001b[38;2;203;179;1m"]
+[54.835137, "o", "2\u001b[39m\u001b[38;2;213;167;3mc\u001b[39m"]
+[54.835222, "o", "\u001b[38;2;222;154;7m8\u001b[39m\u001b[38;2;230;142;11m"]
+[54.835296, "o", "e\u001b[39m\u001b[38;2;237;129;17me\u001b[39m\u001b[38;2;243;117;23m1\u001b[39m"]
+[54.835365, "o", "\u001b[38;2;247;104;31m2\u001b[39m\u001b[38;2;251;92;40m"]
+[54.835432, "o", "3\u001b[39m\u001b[38;2;253;80;50m6\u001b[39m"]
+[54.835485, "o", "\u001b[38;2;254;68;60ma\u001b[39m\u001b[38;2;254;57;71m2\u001b[39m"]
+[54.835546, "o", "\u001b[38;2;253;47;83mf\u001b[39m\u001b[38;2;250;38;95m"]
+[54.835612, "o", "a\u001b[39m\u001b[38;2;246;29;107mc\u001b[39m"]
+[54.835676, "o", "\u001b[38;2;241;22;120ma\u001b[39m\u001b[38;2;235;15;132ma\u001b[39m"]
+[54.835731, "o", "\u001b[38;2;228;10;145m \u001b[39m\u001b[38;2;219;6;158m \u001b[39m"]
+[54.835791, "o", "\u001b[38;2;210;3;170m.\u001b[39m"]
+[54.835858, "o", "\u001b[38;2;200;1;182m/\u001b[39m\u001b[38;2;189;1;193mi\u001b[39m"]
+[54.835922, "o", "\u001b[38;2;178;1;203mc\u001b[39m\u001b[38;2;166;3;213mo\u001b[39m"]
+[54.835974, "o", "\u001b[38;2;154;7;222mn\u001b[39m"]
+[54.83603, "o", "\u001b[38;2;141;11;230m.\u001b[39m\u001b[38;2;129;17;237mp\u001b[39m"]
+[54.836085, "o", "\u001b[38;2;116;24;243mn\u001b[39m"]
+[54.836148, "o", "\u001b[38;2;103;32;248mg\u001b[39m\u001b[38;2;91;40;251m.\u001b[39m"]
+[54.836213, "o", "\u001b[38;2;79;50;253mi\u001b[39m\u001b[38;2;68;61;254mm\u001b[39m"]
+[54.83628, "o", "\u001b[38;2;57;72;254mp\u001b[39m\u001b[38;2;46;83;253mo\u001b[39m"]
+[54.836353, "o", "\u001b[38;2;37;95;250mr\u001b[39m\u001b[38;2;29;108;246mt"]
+[54.836421, "o", "\u001b[39m\u001b[38;2;21;120;241m"]
+[54.836493, "o", "\u001b[39m\r\n"]
+[54.836592, "o", "\u001b[38;2;1;202;179m5\u001b[39m"]
+[54.836652, "o", "\u001b[38;2;3;212;167m2\u001b[39m"]
+[54.836709, "o", "\u001b[38;2;6;221;155m0\u001b[39m\u001b[38;2;11;229;143mc\u001b[39m"]
+[54.83677, "o", "\u001b[38;2;16;236;130m7\u001b[39m"]
+[54.836841, "o", "\u001b[38;2;23;242;117m0\u001b[39m\u001b[38;2;31;247;105m8\u001b[39m"]
+[54.836898, "o", "\u001b[38;2;39;251;92mb\u001b[39m"]
+[54.836965, "o", "\u001b[38;2;49;253;80me\u001b[39m\u001b[38;2;59;254;69ma\u001b[39m"]
+[54.837021, "o", "\u001b[38;2;70;254;58m1\u001b[39m"]
+[54.837075, "o", "\u001b[38;2;82;253;48m5\u001b[39m"]
+[54.837132, "o", "\u001b[38;2;94;250;38ma\u001b[39m"]
+[54.837194, "o", "\u001b[38;2;106;246;30m1\u001b[39m\u001b[38;2;119;241;22mf\u001b[39m"]
+[54.837257, "o", "\u001b[38;2;132;235;15m5\u001b[39m"]
+[54.837331, "o", "\u001b[38;2;144;228;10ma\u001b[39m\u001b[38;2;157;220;6m2\u001b[39m"]
+[54.837398, "o", "\u001b[38;2;169;211;3m7\u001b[39m\u001b[38;2;181;201;1mc\u001b[39m"]
+[54.837465, "o", "\u001b[38;2;192;190;1ma\u001b[39m\u001b[38;2;203;179;1m0\u001b[39m"]
+[54.83753, "o", "\u001b[38;2;213;167;3mb\u001b[39m\u001b[38;2;222;154;7m9"]
+[54.837594, "o", "\u001b[39m\u001b[38;2;230;142;11md\u001b[39m"]
+[54.83765, "o", "\u001b[38;2;237;129;17mf\u001b[39m\u001b[38;2;243;117;23ma\u001b[39m"]
+[54.83771, "o", "\u001b[38;2;247;104;31m8\u001b[39m"]
+[54.837767, "o", "\u001b[38;2;251;92;40m8\u001b[39m\u001b[38;2;253;80;50mc\u001b[39m"]
+[54.837824, "o", "\u001b[38;2;254;68;60m7\u001b[39m\u001b[38;2;254;57;71mf\u001b[39m"]
+[54.837888, "o", "\u001b[38;2;253;47;83m0\u001b[39m\u001b[38;2;250;38;95ma\u001b[39m"]
+[54.837966, "o", "\u001b[38;2;246;29;107mc\u001b[39m\u001b[38;2;241;22;120m"]
+[54.838041, "o", "0\u001b[39m\u001b[38;2;235;15;132m4\u001b[39m"]
+[54.83811, "o", "\u001b[38;2;228;10;145me\u001b[39m"]
+[54.838176, "o", "\u001b[38;2;219;6;158mb\u001b[39m\u001b[38;2;210;3;170m6\u001b[39m"]
+[54.838241, "o", "\u001b[38;2;200;1;182m \u001b[39m\u001b[38;2;189;1;193m \u001b[39m"]
+[54.83832, "o", "\u001b[38;2;178;1;203m.\u001b[39m\u001b[38;2;166;3;213m/\u001b[39m"]
+[54.838353, "o", "\u001b[38;2;154;7;222mL\u001b[39m\u001b[38;2;141;11;230mI"]
+[54.838418, "o", "\u001b[39m\u001b[38;2;129;17;237mC\u001b[39m"]
+[54.838464, "o", "\u001b[38;2;116;24;243mE\u001b[39m\u001b[38;2;103;32;248mN"]
+[54.838512, "o", "\u001b[39m\u001b[38;2;91;40;251mS\u001b[39m"]
+[54.838572, "o", "\u001b[38;2;79;50;253mE\u001b[39m\u001b[38;2;68;61;254m\u001b[39m\r\n"]
+[54.838665, "o", "\u001b[38;2;11;229;143m2\u001b[39m"]
+[54.838718, "o", "\u001b[38;2;16;236;130mb\u001b[39m"]
+[54.838787, "o", "\u001b[38;2;23;242;117m9\u001b[39m\u001b[38;2;31;247;105me\u001b[39m"]
+[54.838987, "o", "\u001b[38;2;39;251;92m7\u001b[39m"]
+[54.839081, "o", "\u001b[38;2;49;253;80mf\u001b[39m"]
+[54.839181, "o", "\u001b[38;2;59;254;69m6\u001b[39m"]
+[54.839246, "o", "\u001b[38;2;70;254;58m0\u001b[39m\u001b[38;2;82;253;48m6\u001b[39m"]
+[54.839312, "o", "\u001b[38;2;94;250;38mf\u001b[39m\u001b[38;2;106;246;30m9\u001b[39m"]
+[54.839376, "o", "\u001b[38;2;119;241;22m5\u001b[39m"]
+[54.839429, "o", "\u001b[38;2;132;235;15mc\u001b[39m\u001b[38;2;144;228;10m5\u001b[39m"]
+[54.839482, "o", "\u001b[38;2;157;220;6me\u001b[39m\u001b[38;2;169;211;3m"]
+[54.839535, "o", "c\u001b[39m\u001b[38;2;181;201;1m6\u001b[39m"]
+[54.839587, "o", "\u001b[38;2;192;190;1m9\u001b[39m"]
+[54.839637, "o", "\u001b[38;2;203;179;1md\u001b[39m\u001b[38;2;213;167;3m7\u001b[39m"]
+[54.839688, "o", "\u001b[38;2;222;154;7m9\u001b[39m"]
+[54.839738, "o", "\u001b[38;2;230;142;11mc\u001b[39m"]
+[54.839787, "o", "\u001b[38;2;237;129;17md\u001b[39m\u001b[38;2;243;117;23mc\u001b[39m"]
+[54.839842, "o", "\u001b[38;2;247;104;31m4\u001b[39m"]
+[54.839885, "o", "\u001b[38;2;251;92;40m1\u001b[39m"]
+[54.839941, "o", "\u001b[38;2;253;80;50m3\u001b[39m\u001b[38;2;254;68;60mb\u001b[39m"]
+[54.83999, "o", "\u001b[38;2;254;57;71m0\u001b[39m"]
+[54.840043, "o", "\u001b[38;2;253;47;83mb\u001b[39m\u001b[38;2;250;38;95m0\u001b[39m"]
+[54.840089, "o", "\u001b[38;2;246;29;107md\u001b[39m"]
+[54.840151, "o", "\u001b[38;2;241;22;120m9\u001b[39m\u001b[38;2;235;15;132mf"]
+[54.840196, "o", "\u001b[39m\u001b[38;2;228;10;145ma\u001b[39m"]
+[54.840245, "o", "\u001b[38;2;219;6;158m0\u001b[39m"]
+[54.840306, "o", "\u001b[38;2;210;3;170me\u001b[39m"]
+[54.840357, "o", "\u001b[38;2;200;1;182m8\u001b[39m\u001b[38;2;189;1;193m8\u001b[39m"]
+[54.840409, "o", "\u001b[38;2;178;1;203ma\u001b[39m"]
+[54.840458, "o", "\u001b[38;2;166;3;213m \u001b[39m\u001b[38;2;154;7;222m \u001b[39m"]
+[54.840507, "o", "\u001b[38;2;141;11;230m.\u001b[39m"]
+[54.840558, "o", "\u001b[38;2;129;17;237m/\u001b[39m"]
+[54.840609, "o", "\u001b[38;2;116;24;243mn\u001b[39m\u001b[38;2;103;32;248me\u001b[39m"]
+[54.840662, "o", "\u001b[38;2;91;40;251mw\u001b[39m"]
+[54.840709, "o", "\u001b[38;2;79;50;253m.\u001b[39m\u001b[38;2;68;61;254m"]
+[54.840762, "o", "c\u001b[39m"]
+[54.841327, "o", "\u001b[38;2;57;72;254ma\u001b[39m"]
+[54.841424, "o", "\u001b[38;2;46;83;253ms\u001b[39m\u001b[38;2;37;95;250mt\u001b[39m\u001b[38;2;29;108;246m\u001b[39m\r\n"]
+[54.841493, "o", "\u001b[38;2;31;247;105mb\u001b[39m\u001b[38;2;39;251;92mb\u001b[39m"]
+[54.841552, "o", "\u001b[38;2;49;253;80m9\u001b[39m"]
+[54.841607, "o", "\u001b[38;2;59;254;69me\u001b[39m\u001b[38;2;70;254;58m2\u001b[39m"]
+[54.841663, "o", "\u001b[38;2;82;253;48ma\u001b[39m"]
+[54.841719, "o", "\u001b[38;2;94;250;38ma\u001b[39m\u001b[38;2;106;246;30m7\u001b[39m"]
+[54.841779, "o", "\u001b[38;2;119;241;22m9\u001b[39m"]
+[54.841812, "o", "\u001b[38;2;132;235;15ma\u001b[39m\u001b[38;2;144;228;10m6\u001b[39m"]
+[54.841885, "o", "\u001b[38;2;157;220;6ma\u001b[39m"]
+[54.841945, "o", "\u001b[38;2;169;211;3m6\u001b[39m\u001b[38;2;181;201;1mc\u001b[39m"]
+[54.842006, "o", "\u001b[38;2;192;190;1m9\u001b[39m"]
+[54.842066, "o", "\u001b[38;2;203;179;1m4\u001b[39m\u001b[38;2;213;167;3m"]
+[54.842122, "o", "d\u001b[39m\u001b[38;2;222;154;7m4\u001b[39m"]
+[54.842176, "o", "\u001b[38;2;230;142;11m4\u001b[39m"]
+[54.842263, "o", "\u001b[38;2;237;129;17m6\u001b[39m\u001b[38;2;243;117;23m0\u001b[39m"]
+[54.842396, "o", "\u001b[38;2;247;104;31m8\u001b[39m"]
+[54.842476, "o", "\u001b[38;2;251;92;40m4\u001b[39m"]
+[54.842562, "o", "\u001b[38;2;253;80;50m1\u001b[39m\u001b[38;2;254;68;60me\u001b[39m"]
+[54.842659, "o", "\u001b[38;2;254;57;71m0\u001b[39m\u001b[38;2;253;47;83m5\u001b[39m"]
+[54.842742, "o", "\u001b[38;2;250;38;95m3\u001b[39m\u001b[38;2;246;29;107m9\u001b[39m"]
+[54.842848, "o", "\u001b[38;2;241;22;120m4\u001b[39m"]
+[54.842933, "o", "\u001b[38;2;235;15;132m8\u001b[39m\u001b[38;2;228;10;145m4\u001b[39m\u001b[38;2;219;6;158me\u001b[39m"]
+[54.843016, "o", "\u001b[38;2;210;3;170md\u001b[39m\u001b[38;2;200;1;182mc\u001b[39m\u001b[38;2;189;1;193m0\u001b[39m"]
+[54.843101, "o", "\u001b[38;2;178;1;203m5\u001b[39m\u001b[38;2;166;3;213mf\u001b[39m\u001b[38;2;154;7;222ma\u001b[39m"]
+[54.843189, "o", "\u001b[38;2;141;11;230m5\u001b[39m\u001b[38;2;129;17;237m \u001b[39m\u001b[38;2;116;24;243m \u001b[39m"]
+[54.843298, "o", "\u001b[38;2;103;32;248m.\u001b[39m\u001b[38;2;91;40;251m/\u001b[39m"]
+[54.843338, "o", "\u001b[38;2;79;50;253mp\u001b[39m\u001b[38;2;68;61;254mr\u001b[39m"]
+[54.843438, "o", "\u001b[38;2;57;72;254mo\u001b[39m\u001b[38;2;46;83;253mj\u001b[39m\u001b[38;2;37;95;250me\u001b[39m"]
+[54.843511, "o", "\u001b[38;2;29;108;246mc\u001b[39m\u001b[38;2;21;120;241mt\u001b[39m\u001b[38;2;15;133;235m.\u001b[39m"]
+[54.843584, "o", "\u001b[38;2;10;146;227mg\u001b[39m\u001b[38;2;5;158;219mo\u001b[39m\u001b[38;2;3;170;210md"]
+[54.843652, "o", "\u001b[39m\u001b[38;2;1;182;199mo\u001b[39m\u001b[38;2;1;193;189mt\u001b[39m"]
+[54.843723, "o", "\u001b[38;2;1;204;177m\u001b[39m\r\n"]
+[54.843797, "o", "\u001b[38;2;59;254;69m3\u001b[39m"]
+[54.84387, "o", "\u001b[38;2;70;254;58ma\u001b[39m\u001b[38;2;82;253;48m8\u001b[39m\u001b[38;2;94;250;38m3"]
+[54.843944, "o", "\u001b[39m\u001b[38;2;106;246;30m0\u001b[39m\u001b[38;2;119;241;22m0\u001b[39m"]
+[54.844009, "o", "\u001b[38;2;132;235;15m5\u001b[39m\u001b[38;2;144;228;10ma\u001b[39m"]
+[54.844071, "o", "\u001b[38;2;157;220;6m5\u001b[39m\u001b[38;2;169;211;3md\u001b[39m"]
+[54.844155, "o", "\u001b[38;2;181;201;1mc\u001b[39m\u001b[38;2;192;190;1m8\u001b[39m"]
+[54.844245, "o", "\u001b[38;2;203;179;1mb\u001b[39m\u001b[38;2;213;167;3ma\u001b[39m\u001b[38;2;222;154;7m5"]
+[54.844341, "o", "\u001b[39m\u001b[38;2;230;142;11md\u001b[39m\u001b[38;2;237;129;17m0\u001b[39m\u001b[38;2;243;117;23mf\u001b[39m\u001b[38;2;247;104;31m0\u001b[39m"]
+[54.844424, "o", "\u001b[38;2;251;92;40ma\u001b[39m\u001b[38;2;253;80;50mb\u001b[39m"]
+[54.844495, "o", "\u001b[38;2;254;68;60mb\u001b[39m\u001b[38;2;254;57;71m3\u001b[39m"]
+[54.844563, "o", "\u001b[38;2;253;47;83md\u001b[39m\u001b[38;2;250;38;95me\u001b[39m"]
+[54.844639, "o", "\u001b[38;2;246;29;107m8\u001b[39m\u001b[38;2;241;22;120m4\u001b[39m\u001b[38;2;235;15;132m7\u001b[39m\u001b[38;2;228;10;145md\u001b[39m"]
+[54.844728, "o", "\u001b[38;2;219;6;158m2\u001b[39m\u001b[38;2;210;3;170md\u001b[39m"]
+[54.844818, "o", "\u001b[38;2;200;1;182me\u001b[39m\u001b[38;2;189;1;193m0\u001b[39m\u001b[38;2;178;1;203ma\u001b[39m"]
+[54.844893, "o", "\u001b[38;2;166;3;213m4\u001b[39m\u001b[38;2;154;7;222m6\u001b[39m\u001b[38;2;141;11;230md\u001b[39m\u001b[38;2;129;17;237m7\u001b[39m"]
+[54.84499, "o", "\u001b[38;2;116;24;243m7\u001b[39m\u001b[38;2;103;32;248mb\u001b[39m\u001b[38;2;91;40;251m \u001b[39m"]
+[54.845083, "o", "\u001b[38;2;79;50;253m \u001b[39m\u001b[38;2;68;61;254m.\u001b[39m\u001b[38;2;57;72;254m/\u001b[39m\u001b[38;2;46;83;253mR\u001b[39m\u001b[38;2;37;95;250mE\u001b[39m"]
+[54.845178, "o", "\u001b[38;2;29;108;246mA\u001b[39m\u001b[38;2;21;120;241mD\u001b[39m"]
+[54.845256, "o", "\u001b[38;2;15;133;235mM\u001b[39m\u001b[38;2;10;146;227mE\u001b[39m"]
+[54.845343, "o", "\u001b[38;2;5;158;219m.\u001b[39m\u001b[38;2;3;170;210mm\u001b[39m\u001b[38;2;1;182;199md\u001b[39m"]
+[54.845422, "o", "\u001b[38;2;1;193;189m\u001b[39m\r\n"]
+[54.845491, "o", "\u001b[m\u001b[?25h\u001b[?1;5;2004l"]
+[54.847349, "o", "\u001b]2;laptop:leroy:~/projects/godot-xterm\u0007\r\r\n\u001b[1;32m[\u001b]0;leroy@laptop: ~/projects/godot-xterm\u0007leroy@laptop:~/projects/godot-xterm]$\u001b[0m "]
+[57.064686, "o", "e"]
+[57.230802, "o", "x"]
+[59.010363, "o", "\b\u001b[K"]
+[59.121216, "o", "\b\u001b[K"]
+[59.456844, "o", "#"]
+[59.676225, "o", "/"]
+[60.10367, "o", "\b\u001b[K"]
+[60.18748, "o", " "]
+[60.4099, "o", "w"]
+[60.748895, "o", "e"]
+[60.992399, "o", "\b\u001b[K"]
+[61.116881, "o", "\b\u001b[K"]
+[61.313248, "o", "W"]
+[61.473147, "o", "h"]
+[61.562029, "o", "e"]
+[61.645136, "o", "n"]
+[61.917431, "o", " "]
+[62.093406, "o", "w"]
+[62.161239, "o", "e"]
+[62.248416, "o", " "]
+[62.33829, "o", "a"]
+[62.415246, "o", "r"]
+[62.507794, "o", "e"]
+[62.557211, "o", " "]
+[62.753875, "o", "f"]
+[62.85347, "o", "i"]
+[62.921221, "o", "n"]
+[63.033374, "o", "i"]
+[63.094372, "o", "s"]
+[63.187799, "o", "h"]
+[63.483077, "o", "e"]
+[63.612519, "o", "d"]
+[63.792416, "o", ","]
+[63.894738, "o", " "]
+[64.04963, "o", "j"]
+[64.202261, "o", "u"]
+[64.263783, "o", "s"]
+[64.307216, "o", "t"]
+[64.369715, "o", " "]
+[64.492205, "o", "e"]
+[64.653815, "o", "x"]
+[64.746695, "o", "i"]
+[64.858507, "o", "t"]
+[65.103114, "o", " "]
+[65.664488, "o", "t"]
+[65.719435, "o", "h"]
+[65.782418, "o", "e"]
+[65.844266, "o", " "]
+[65.963151, "o", "s"]
+[66.037719, "o", "h"]
+[66.121665, "o", "e"]
+[66.224533, "o", "l"]
+[66.323957, "o", "l"]
+[66.562302, "o", "\r\n"]
+[66.562831, "o", "\u001b]2;laptop:leroy:~/projects/godot-xterm\u0007\r\r\n\u001b[1;32m[\u001b]0;leroy@laptop: ~/projects/godot-xterm\u0007leroy@laptop:~/projects/godot-xterm]$\u001b[0m "]
+[66.844263, "o", "e"]
+[66.983977, "o", "x"]
+[67.104686, "o", "i"]
+[67.212146, "o", "t"]
+[67.515156, "o", "\r\n"]
+[67.515263, "o", "exit\r\n"]
+[67.516389, "o", "\u001b[0;32masciinema: recording finished\u001b[0m\r\n\u001b[0;32masciinema: asciicast saved to new.cast\u001b[0m\r\n"]
+[67.526166, "o", "sh-4.4$ "]
+[69.107982, "o", "c"]
+[69.201795, "o", "o"]
+[69.306066, "o", "w"]
+[69.520215, "o", "s"]
+[69.638453, "o", "a"]
+[69.696601, "o", "y"]
+[70.376247, "o", " "]
+[70.997599, "o", "A"]
+[71.148845, "o", "l"]
+[71.283482, "o", "l"]
+[71.322607, "o", " "]
+[71.823544, "o", "d"]
+[71.910819, "o", "o"]
+[72.005256, "o", "n"]
+[72.059912, "o", "e"]
+[72.234089, "o", "!"]
+[72.62946, "o", "\r\n"]
+[72.64376, "o", " ___________ \r\n< All done! >\r\n ----------- \r\n \\ ^__^\r\n \\ (oo)\\_______\r\n (__)\\ )\\/\\\r\n ||----w |\r\n || ||\r\n"]
+[72.644144, "o", "sh-4.4$ "]
+[73.525287, "o", "\r\n"]
+[73.525434, "o", "sh-4.4$ "]
+[74.180823, "o", "e"]
+[74.345967, "o", "x"]
+[74.476496, "o", "i"]
+[74.626928, "o", "t"]
+[75.019672, "o", "\r\nexit\r\n"]
diff --git a/examples/menu/menu.gd b/examples/menu/menu.gd
new file mode 100644
index 0000000..7b84558
--- /dev/null
+++ b/examples/menu/menu.gd
@@ -0,0 +1,149 @@
+tool
+extends Control
+# This scene demonstrates how we can control the Terminal node directly by
+# sending and receiving strings and ANSI escape sequences to the terminal
+# directly.
+
+# References:
+# - https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
+# - https://www.youtube.com/watch?v=jTSQlIK_92w
+
+const TITLE = """
+░█▀▀░█▀█░█▀▄░█▀█░▀█▀░░░█░█░▀█▀░█▀▀░█▀▄░█▄█\r
+░█░█░█░█░█░█░█░█░░█░░░░▄▀▄░░█░░█▀▀░█▀▄░█░█\r
+░▀▀▀░▀▀▀░▀▀░░▀▀▀░░▀░░░░▀░▀░░▀░░▀▀▀░▀░▀░▀░▀\r
+"""
+const TITLE_WIDTH = 42
+
+var menu_items := [
+ { "name": "Asciicast", "scene": preload("../asciicast/asciicast.tscn") },
+ { "name": "Terminal", "scene": preload("../terminal/terminal.tscn") },
+ { "name": "Exit"}
+]
+
+var selected_index := 0
+
+var row: int
+var menu_start_row: int
+var offset: int
+
+onready var tput = TPut.new($Terminal)
+
+
+func _ready():
+ # warning-ignore:return_value_discarded
+ $Terminal.connect("size_changed", self, "draw_all")
+ $Terminal.grab_focus()
+ draw_all()
+
+
+func draw_all(_size = Vector2.ZERO):
+ offset = int(floor(($Terminal.cols / 2.0) - (TITLE_WIDTH / 2.0)))
+ tput.reset()
+ row = 5
+ tput.civis() # Hide the cursor.
+ draw_title()
+ draw_menu()
+ tput.sgr0()
+ row += 1
+
+
+func draw_title():
+ tput.cup(row, 0)
+
+ for line in TITLE.split("\r"):
+ row += 1
+ tput.cup(row, offset)
+ $Terminal.write(line)
+
+ # Get the plugin version from the plugin's config file.
+ var config = ConfigFile.new()
+ var err = config.load("res://addons/godot_xterm/plugin.cfg")
+ if err == OK:
+ $Terminal.write("\n")
+ $Terminal.write("Version: %s" % config.get_value("plugin", "version",
+ "unknown"))
+ row += 2
+
+
+func draw_menu():
+ if not menu_start_row:
+ menu_start_row = row + 1
+
+ row = menu_start_row
+
+ var col_offset: int
+
+ for i in range(menu_items.size()):
+ row += 1
+ var item = menu_items[i]
+
+ if not col_offset:
+ col_offset = int(floor(($Terminal.cols / 2) - (item.name.length() / 2)))
+
+ tput.cup(row, offset)
+
+ if selected_index == i:
+ tput.setab(Color("#FF7500"))
+ tput.setaf(Color.black)
+
+ $Terminal.write("%s. %s" % [i + 1, item.name])
+
+ if selected_index == i:
+ tput.sgr0()
+
+
+func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
+ match(data):
+ TPut.CURSOR_UP: # Up arrow key
+ selected_index = int(clamp(selected_index - 1, 0, menu_items.size() - 1))
+ draw_menu()
+ TPut.CURSOR_DOWN: # Down arrow key
+ selected_index = int(clamp(selected_index + 1, 0, menu_items.size() - 1))
+ draw_menu()
+ "1":
+ selected_index = 0
+ draw_menu()
+ "2":
+ selected_index = 1
+ draw_menu()
+ "3":
+ selected_index = 2
+ draw_menu()
+
+ # We can also match against the raw InputEventKey.
+ if event.scancode == KEY_ENTER:
+ var item = menu_items[selected_index]
+
+ match item.name:
+ "Asciicast":
+ var scene = item.scene.instance()
+ var animation_player: AnimationPlayer = scene.get_node("AnimationPlayer")
+ scene.connect("key_pressed", self, "_on_Asciicast_key_pressed",
+ [animation_player])
+ get_tree().get_root().add_child(scene)
+ visible = false
+ scene.grab_focus()
+ yield(animation_player, "animation_finished")
+ visible = true
+ get_tree().get_root().remove_child(scene)
+ $Terminal.grab_focus()
+ scene.queue_free()
+ "Terminal":
+ var scene = item.scene.instance()
+ var pty = scene.get_node("Pseudoterminal")
+ get_tree().get_root().add_child(scene)
+ visible = false
+ scene.grab_focus()
+ yield(pty, "exited")
+ visible = true
+ $Terminal.grab_focus()
+ scene.queue_free()
+ "Exit":
+ get_tree().quit()
+
+
+func _on_Asciicast_key_pressed(data: String, _event: InputEventKey,
+ animation_player: AnimationPlayer) -> void:
+ if data == "\u001b":
+ animation_player.emit_signal("animation_finished")
diff --git a/examples/menu/menu.tscn b/examples/menu/menu.tscn
new file mode 100644
index 0000000..b4dcfa5
--- /dev/null
+++ b/examples/menu/menu.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/godot_xterm/nodes/terminal/terminal.gdns" type="Script" id=1]
+[ext_resource path="res://examples/menu/menu.gd" type="Script" id=2]
+[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=3]
+
+[node name="Menu" type="Control"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Terminal" type="Control" parent="."]
+anchor_right = 1.0
+anchor_bottom = 1.0
+focus_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme = ExtResource( 3 )
+script = ExtResource( 1 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+rows = 31
+cols = 102
+[connection signal="key_pressed" from="Terminal" to="." method="_on_Terminal_key_pressed"]
diff --git a/examples/terminal/Node.gd b/examples/terminal/Node.gd
new file mode 100644
index 0000000..593387e
--- /dev/null
+++ b/examples/terminal/Node.gd
@@ -0,0 +1,7 @@
+extends Node
+
+
+
+func _on_Terminal_key_pressed(event: InputEventKey, data: PoolByteArray):
+ print(data as Array)
+ print(event.scancode)
diff --git a/examples/terminal/Terminal.tscn b/examples/terminal/terminal.tscn
similarity index 69%
rename from examples/terminal/Terminal.tscn
rename to examples/terminal/terminal.tscn
index c54c563..276fe26 100644
--- a/examples/terminal/Terminal.tscn
+++ b/examples/terminal/terminal.tscn
@@ -8,15 +8,16 @@
anchor_right = 1.0
anchor_bottom = 1.0
focus_mode = 2
-size_flags_horizontal = 3
-size_flags_vertical = 3
theme = ExtResource( 4 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
+rows = 31
+cols = 102
[node name="Pseudoterminal" type="Node" parent="."]
script = ExtResource( 2 )
-[connection signal="data_read" from="." to="Pseudoterminal" method="put_data"]
-[connection signal="data_received" from="Pseudoterminal" to="." method="write"]
+[connection signal="data_sent" from="." to="Pseudoterminal" method="write"]
+[connection signal="size_changed" from="." to="Pseudoterminal" method="resize"]
+[connection signal="data_sent" from="Pseudoterminal" to="." method="write"]
diff --git a/project.godot b/project.godot
index 09f49ea..b3b63e5 100644
--- a/project.godot
+++ b/project.godot
@@ -10,23 +10,30 @@ config_version=4
_global_script_classes=[ {
"base": "Reference",
-"class": "ArrayUtils",
+"class": "TPut",
"language": "GDScript",
-"path": "res://addons/SIsilicon.3d.text/array_utils.gd"
+"path": "res://addons/godot_xterm/util/tput.gd"
} ]
_global_script_class_icons={
-"ArrayUtils": ""
+"TPut": ""
}
[application]
config/name="Godot Xterm"
+run/main_scene="res://examples/menu/menu.tscn"
config/icon="res://icon.png"
+[display]
+
+window/vsync/use_vsync=false
+
[editor_plugins]
-enabled=PoolStringArray( "SIsilicon.3d.text", "godot_xterm" )
+enabled=PoolStringArray( "godot_xterm" )
[rendering]
+quality/filters/anisotropic_filter_level=16
+quality/filters/msaa=4
environment/default_environment="res://default_env.tres"