mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-04 20:24:23 +02:00
parent
a55a05d3a4
commit
9bd17ec8dc
31 changed files with 5058 additions and 1031 deletions
|
@ -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())
|
|
@ -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.
|
|
@ -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)
|
|
@ -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 )
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 3a1.0001 1.0001 0 0 0 -.70703.29297l-4 4a1.0001 1.0001 0 0 0 0 1.4141l4 4a1.0001 1.0001 0 0 0 .70703.29297h8a1.0001 1.0001 0 0 0 1-1v-8a1.0001 1.0001 0 0 0 -1-1h-8zm-1 4a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1z" fill="#fc9c9c" fill-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 367 B |
|
@ -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
|
|
@ -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)
|
Binary file not shown.
|
@ -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)
|
|
@ -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"]
|
|
@ -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"
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "pseudoterminal.h"
|
||||
#include <pty.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
|
||||
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<Pseudoterminal>((char *)"data_received", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Pseudoterminal>((char *)"data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Pseudoterminal>((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<std::mutex> 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<std::mutex> 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<std::mutex> guard(size_mutex);
|
||||
size = new_size;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<Terminal, int>("rows", &Terminal::rows, 24);
|
||||
//register_property<Terminal, int>("cols", &Terminal::cols, 80);
|
||||
register_property<Terminal, int>("rows", &Terminal::rows, 24);
|
||||
register_property<Terminal, int>("cols", &Terminal::cols, 80);
|
||||
|
||||
register_signal<Terminal>("data_read", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Terminal>("data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Terminal>("key_pressed", "data", GODOT_VARIANT_TYPE_STRING, "event", GODOT_VARIANT_TYPE_OBJECT);
|
||||
register_signal<Terminal>("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<Font> 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 = {};
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace godot
|
|||
|
||||
Cells cells;
|
||||
|
||||
Ref<InputEventKey> input_event_key;
|
||||
|
||||
protected:
|
||||
tsm_screen *screen;
|
||||
tsm_vte *vte;
|
||||
|
|
131
addons/godot_xterm/nodes/terminal/README.md
Normal file
131
addons/godot_xterm/nodes/terminal/README.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Terminal
|
||||
|
||||
**Inherits:** [Control] < [CanvasItem] < [Node] < [Object]
|
||||
|
||||
|
||||
Terminal emulator.
|
||||
|
||||
**IMPORTANT:**
|
||||
|
||||
<img align="right" src="./docs/important_properties.png"/>
|
||||
|
||||
- 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
|
||||
|
||||

|
||||
|
||||
### (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
|
0
addons/godot_xterm/nodes/terminal/docs/.gdignore
Normal file
0
addons/godot_xterm/nodes/terminal/docs/.gdignore
Normal file
3777
addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
Normal file
3777
addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 360 KiB |
BIN
addons/godot_xterm/nodes/terminal/docs/important_properties.png
Normal file
BIN
addons/godot_xterm/nodes/terminal/docs/important_properties.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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.
|
||||
|
|
66
addons/godot_xterm/util/tput.gd
Normal file
66
addons/godot_xterm/util/tput.gd
Normal file
|
@ -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")
|
Loading…
Add table
Add a link
Reference in a new issue