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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (1) + _gui_input() + + (2) + data_sent() + + (4) + _draw() + + (3) + write() + + + Image credits:"computer keyboard 2", from U.S. patent drawing, uploaded by johnny_automatic to https://openclipart.org/detail/2396/computer-keyboard-2, released under CC0."monitor", from U.S. patent drawing, uploaded by johnny_automatic to https://openclipart.org/detail/1637/monitor, released under CC0. + + 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"