mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-04 04:14:22 +02:00
Add/update more files
This commit is contained in:
parent
3307231b65
commit
0769592a1b
44 changed files with 4188 additions and 362 deletions
127
addons/godot_xterm/buffer/attribute_data.gd
Normal file
127
addons/godot_xterm/buffer/attribute_data.gd
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
|
||||
const Attributes = Constants.Attributes
|
||||
const FgFlags = Constants.FgFlags
|
||||
const BgFlags = Constants.BgFlags
|
||||
const UnderlineStyle = Constants.UnderlineStyle
|
||||
|
||||
var fg = 0
|
||||
var bg = 0
|
||||
var extended = ExtendedAttrs.new()
|
||||
|
||||
|
||||
# flags
|
||||
func is_inverse() -> int:
|
||||
return fg & FgFlags.INVERSE
|
||||
func is_bold() -> int:
|
||||
return fg & FgFlags.BOLD
|
||||
func is_underline() -> int:
|
||||
return fg & FgFlags.UNDERLINE
|
||||
func is_blink() -> int:
|
||||
return fg & FgFlags.BLINK
|
||||
func is_invisible() -> int:
|
||||
return fg & FgFlags.INVISIBLE
|
||||
func is_italic() -> int:
|
||||
return fg & BgFlags.ITALIC
|
||||
func is_dim() -> int:
|
||||
return fg & BgFlags.DIM
|
||||
|
||||
|
||||
# color modes
|
||||
func get_fg_color_mode() -> int:
|
||||
return fg & Attributes.CM_MASK
|
||||
func get_bg_color_mode() -> int:
|
||||
return bg & Attributes.CM_MASK
|
||||
func is_fg_rgb() -> bool:
|
||||
return (fg & Attributes.CM_MASK) == Attributes.CM_RGB
|
||||
func is_bg_rgb() -> bool:
|
||||
return (bg & Attributes.CM_MASK) == Attributes.CM_RGB
|
||||
func is_fg_palette() -> bool:
|
||||
return (fg & Attributes.CM_MASK) == Attributes.CM_P16 or (fg & Attributes.CM_MASK) == Attributes.CM_P256
|
||||
func is_bg_palette() -> bool:
|
||||
return (bg & Attributes.CM_MASK) == Attributes.CM_P16 or (bg & Attributes.CM_MASK) == Attributes.CM_P256
|
||||
func is_fg_default() -> bool:
|
||||
return (fg & Attributes.CM_MASK) == 0
|
||||
func is_bg_default() -> bool:
|
||||
return (bg & Attributes.CM_MASK) == 0
|
||||
func is_attribute_default() -> bool:
|
||||
return fg == 0 && bg == 0
|
||||
|
||||
|
||||
func get_fg_color() -> int:
|
||||
match fg & Attributes.CM_MASK:
|
||||
Attributes.CM_P16, Attributes.CM_P256:
|
||||
return fg & Attributes.PCOLOR_MASK
|
||||
Attributes.CM_RGB:
|
||||
return fg & Attributes.RGB_MASK
|
||||
_:
|
||||
return -1
|
||||
|
||||
|
||||
func has_extended_attrs() -> int:
|
||||
return bg & BgFlags.HAS_EXTENDED
|
||||
|
||||
|
||||
func get_underline_color() -> int:
|
||||
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
|
||||
match extended.underline_color & Attributes.CM_MASK:
|
||||
Attributes.CM_P16, Attributes.CM_P256:
|
||||
return extended.underline_color & Attributes.PCOLOR_MASK
|
||||
Attributes.CM_RGB:
|
||||
return extended.underline_color & Attributes.RGB_MASK
|
||||
_:
|
||||
return get_fg_color()
|
||||
else:
|
||||
return get_fg_color()
|
||||
|
||||
|
||||
func get_underline_color_mode() -> int:
|
||||
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
|
||||
return extended.underline_color & Attributes.CM_MASK
|
||||
else:
|
||||
return get_fg_color_mode()
|
||||
|
||||
|
||||
func is_underline_color_rgb() -> bool:
|
||||
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
|
||||
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_RGB
|
||||
else:
|
||||
return is_fg_rgb()
|
||||
|
||||
|
||||
func is_underline_color_palette() -> bool:
|
||||
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
|
||||
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_P16 \
|
||||
or extended.underline_color & Attributes.CM_MASK == Attributes.CM_P256
|
||||
else:
|
||||
return is_fg_palette()
|
||||
|
||||
|
||||
func is_underline_color_default() -> bool:
|
||||
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
|
||||
return extended.underline_color & Attributes.CM_MASK == 0
|
||||
else:
|
||||
return is_fg_default()
|
||||
|
||||
|
||||
func get_underline_style():
|
||||
if fg & FgFlags.UNDERLINE:
|
||||
return extended.underline_style if bg & BgFlags.HAS_EXTENDED else UnderlineStyle.SINGLE
|
||||
else:
|
||||
return UnderlineStyle.NONE
|
||||
|
||||
|
||||
class ExtendedAttrs:
|
||||
|
||||
|
||||
var underline_style = UnderlineStyle.NONE
|
||||
var underline_color: int = -1
|
||||
|
||||
|
||||
func _init():
|
||||
underline_style
|
141
addons/godot_xterm/buffer/buffer.gd
Normal file
141
addons/godot_xterm/buffer/buffer.gd
Normal file
|
@ -0,0 +1,141 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const BufferLine = preload("res://addons/godot_xterm/buffer/buffer_line.gd")
|
||||
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
|
||||
const Charsets = preload("res://addons/godot_xterm/data/charsets.gd")
|
||||
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
|
||||
const CircularList = preload("res://addons/godot_xterm/circular_list.gd")
|
||||
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
|
||||
|
||||
|
||||
const MAX_BUFFER_SIZE = 4294967295 # 2^32 - 1
|
||||
|
||||
var lines
|
||||
var ydisp: int = 0
|
||||
var ybase: int = 0
|
||||
var y: int = 0
|
||||
var x: int = 0
|
||||
var scroll_bottom: int
|
||||
var scroll_top: int
|
||||
var tabs = {}
|
||||
var saved_y: int = 0
|
||||
var saved_x: int = 0
|
||||
var saved_cur_attr_data = AttributeData.new()
|
||||
var saved_charset = Charsets.DEFAULT_CHARSET
|
||||
var markers: Array = []
|
||||
|
||||
var _null_cell = CellData.from_char_data([0, Constants.NULL_CELL_CHAR,
|
||||
Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])
|
||||
var _whitespace_cell = CellData.from_char_data([0, Constants.WHITESPACE_CELL_CHAR,
|
||||
Constants.WHITESPACE_CELL_WIDTH, Constants.WHITESPACE_CELL_CODE])
|
||||
var _cols: int
|
||||
var _rows: int
|
||||
var _has_scrollback
|
||||
var _options_service
|
||||
var _buffer_service
|
||||
|
||||
|
||||
func _init(has_scrollback: bool, options_service, buffer_service):
|
||||
_has_scrollback = has_scrollback
|
||||
_options_service = options_service
|
||||
_buffer_service = buffer_service
|
||||
_cols = buffer_service.cols
|
||||
_rows = buffer_service.rows
|
||||
lines = CircularList.new(_get_correct_buffer_length(_rows))
|
||||
scroll_top = 0
|
||||
scroll_bottom = _rows - 1
|
||||
setup_tab_stops()
|
||||
|
||||
|
||||
func get_null_cell(attr = null):
|
||||
if attr:
|
||||
_null_cell.fg = attr.fg
|
||||
_null_cell.bg = attr.bg
|
||||
_null_cell.extended = attr.extended
|
||||
else:
|
||||
_null_cell.fg = 0
|
||||
_null_cell.bg = 0
|
||||
_null_cell.extended = AttributeData.ExtendedAttrs.new()
|
||||
return _null_cell
|
||||
|
||||
|
||||
func get_blank_line(attr, is_wrapped: bool = false):
|
||||
return BufferLine.new(_buffer_service.cols, get_null_cell(attr), is_wrapped)
|
||||
|
||||
|
||||
func _get_correct_buffer_length(rows: int) -> int:
|
||||
if not _has_scrollback:
|
||||
return rows
|
||||
else:
|
||||
var correct_buffer_length = rows + _options_service.options.scrollback
|
||||
return correct_buffer_length if correct_buffer_length < MAX_BUFFER_SIZE else MAX_BUFFER_SIZE
|
||||
|
||||
|
||||
# Fills the viewport with blank lines.
|
||||
func fill_viewport_rows(fill_attr = null) -> void:
|
||||
if lines.length == 0:
|
||||
if not fill_attr:
|
||||
fill_attr = AttributeData.new()
|
||||
var i = _rows
|
||||
while i:
|
||||
lines.push(get_blank_line(fill_attr))
|
||||
i -= 1
|
||||
|
||||
|
||||
|
||||
# Clears the buffer to it's initial state, discarding all previous data.
|
||||
func clear() -> void:
|
||||
ydisp = 0
|
||||
ybase = 0
|
||||
y = 0
|
||||
x = 0
|
||||
lines = CircularList.new(_get_correct_buffer_length(_rows))
|
||||
scroll_top = 0
|
||||
scroll_bottom = _rows - 1
|
||||
setup_tab_stops()
|
||||
|
||||
|
||||
func get_wrapped_range_for_line(y: int) -> Dictionary:
|
||||
var first = y
|
||||
var last = y
|
||||
# Scan upwards for wrapped lines
|
||||
while first > 0 and lines.get_el(first).is_wrapped:
|
||||
first -= 1
|
||||
# Scan downwards for wrapped lines
|
||||
while last + 1 < lines.length and lines.get_el(last + 1).is_wrapped:
|
||||
last += 1
|
||||
return {"first": first, "last": last}
|
||||
|
||||
|
||||
func setup_tab_stops(i = null) -> void:
|
||||
if i == null:
|
||||
return
|
||||
|
||||
if not tabs.get(i):
|
||||
i = prev_stop(i)
|
||||
else:
|
||||
tabs = {}
|
||||
i = 0
|
||||
|
||||
while i < _cols:
|
||||
tabs[i] = true
|
||||
i += _options_service.options.tab_stop_width
|
||||
|
||||
|
||||
func prev_stop(x: int) -> int:
|
||||
if x == null:
|
||||
x = self.x
|
||||
|
||||
while not tabs.get(x - 1, false) and x - 1 > 0:
|
||||
x - 1
|
||||
|
||||
return _cols - 1 if x > _cols else 0 if x < 0 else x
|
||||
|
||||
|
||||
|
||||
|
||||
|
247
addons/godot_xterm/buffer/buffer_line.gd
Normal file
247
addons/godot_xterm/buffer/buffer_line.gd
Normal file
|
@ -0,0 +1,247 @@
|
|||
# Copyright (c) 2018 The xterm.js authors. All rights reserved
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
|
||||
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
|
||||
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
|
||||
const Content = Constants.Content
|
||||
const BgFlags = Constants.BgFlags
|
||||
|
||||
const CELL_SIZE = 3
|
||||
|
||||
enum Cell {
|
||||
CONTENT
|
||||
FG
|
||||
BG
|
||||
}
|
||||
|
||||
var _data: Array
|
||||
var _combined: Dictionary = {}
|
||||
var _extended_attrs: Dictionary = {}
|
||||
|
||||
var length: int
|
||||
var is_wrapped
|
||||
|
||||
func _init(cols: int, fill_cell_data = null, is_wrapped: bool = false):
|
||||
self.is_wrapped = is_wrapped
|
||||
_data = []
|
||||
_data.resize(cols * CELL_SIZE)
|
||||
var cell = fill_cell_data if fill_cell_data \
|
||||
else CellData.from_char_data([0, Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])
|
||||
for i in range(cols):
|
||||
set_cell(i, cell)
|
||||
length = cols
|
||||
|
||||
|
||||
func get_cell(index: int):
|
||||
return _data[index * CELL_SIZE + Cell.CONTENT]
|
||||
|
||||
|
||||
func get_width(index: int) -> int:
|
||||
return _data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT
|
||||
|
||||
|
||||
func has_content(index: int) -> int:
|
||||
return _data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK
|
||||
|
||||
|
||||
# Get codepoint of the cell.
|
||||
# To be in line with `code` in CharData this either returns
|
||||
# a single UTF32 codepoint or the last codepoint of a combined string.
|
||||
func get_codepoint(index: int) -> int:
|
||||
var content = _data[index * CELL_SIZE + Cell.CONTENT]
|
||||
if content & Content.IS_COMBINED_MASK:
|
||||
return _combined[index].ord_at(_combined[index].length() - 1)
|
||||
else:
|
||||
return content & Content.CODEPOINT_MASK
|
||||
|
||||
|
||||
func load_cell(index: int, cell):
|
||||
var start_index = index * CELL_SIZE
|
||||
cell.content = _data[start_index + Cell.CONTENT]
|
||||
cell.fg = _data[start_index + Cell.FG]
|
||||
cell.bg = _data[start_index + Cell.BG]
|
||||
if cell.content and cell.content & Content.IS_COMBINED_MASK:
|
||||
cell.combined_data = _combined[index]
|
||||
if cell.bg & BgFlags.HAS_EXTENDED:
|
||||
cell.extended = _extended_attrs[index]
|
||||
return cell
|
||||
|
||||
|
||||
func set_cell(index: int, cell) -> void:
|
||||
if cell.content & Content.IS_COMBINED_MASK:
|
||||
_combined[index] = cell.combined_data
|
||||
if cell.bg & BgFlags.HAS_EXTENDED:
|
||||
_extended_attrs[index] = cell.extended
|
||||
_data[index * CELL_SIZE + Cell.CONTENT] = cell.content
|
||||
_data[index * CELL_SIZE + Cell.FG] = cell.fg
|
||||
_data[index * CELL_SIZE + Cell.BG] = cell.bg
|
||||
|
||||
|
||||
func set_cell_from_codepoint(index: int, codepoint: int, width: int, fg: int, bg: int, e_attrs) -> void:
|
||||
if bg & BgFlags.HAS_EXTENDED:
|
||||
_extended_attrs[index] = e_attrs
|
||||
_data[index * CELL_SIZE + Cell.CONTENT] = codepoint | (width << Content.WIDTH_SHIFT)
|
||||
_data[index * CELL_SIZE + Cell.FG] = fg
|
||||
_data[index * CELL_SIZE + Cell.BG] = bg
|
||||
|
||||
|
||||
# Add a codepoint to a cell from input handler
|
||||
# During input stage combining chars with a width of 0 follow and stack
|
||||
# onto a leading char. Since we already set the attrs
|
||||
# by the previous `set_data_from_code_pont` call, we can omit it here.
|
||||
func add_codepoint_to_cell(index: int, codepoint: int) -> void:
|
||||
var content = _data[index * CELL_SIZE + Cell.CONTENT]
|
||||
if content & Content.IS_COMBINED_MASK:
|
||||
# we already have a combined string, simply add
|
||||
_combined[index] += char(codepoint)
|
||||
else:
|
||||
if content & Content.CODEPOINT_MASK:
|
||||
# normal case for combining chars:
|
||||
# - move current leading char + new one into combined string
|
||||
# - set combined flag
|
||||
_combined[index] = char(content & Content.CODEPOINT_MASK) + char(codepoint)
|
||||
content &= ~Content.CODEPOINT_MASK # set codepoint in buffer to 0
|
||||
content |= Content.IS_COMBINED_MASK
|
||||
else:
|
||||
# should not happen - we actually have no data in the cell yet
|
||||
# simply set the data in the cell buffer with a width of 1
|
||||
content = codepoint | (1 << Content.WIDTH_SHIFT)
|
||||
_data[index * CELL_SIZE + Cell.CONTENT] = content
|
||||
|
||||
|
||||
func insert_cells(pos: int, n: int, fill_cell_data, erase_attr = null) -> void:
|
||||
pos %= length
|
||||
|
||||
# handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
|
||||
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
|
||||
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
|
||||
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
|
||||
if pos and get_width(pos - 1) == 2:
|
||||
set_cell_from_codepoint(pos - 1, 0, 1, fg, bg, extended)
|
||||
|
||||
if n < length - pos:
|
||||
var cell = CellData.new()
|
||||
var i = length - pos - n - 1
|
||||
while i >= 0:
|
||||
set_cell(pos + n + i, load_cell(pos + i, cell))
|
||||
i -= 1
|
||||
for i in range(n):
|
||||
set_cell(pos + i, fill_cell_data)
|
||||
else:
|
||||
for i in range(pos, length):
|
||||
set_cell(i, fill_cell_data)
|
||||
|
||||
# handle fullwidth at line end: reset last cell if it is first cell of a wide char
|
||||
if get_width(length - 1) == 2:
|
||||
set_cell_from_codepoint(length - 1, 0, 1, fg, bg, extended)
|
||||
|
||||
|
||||
func delete_cells(pos: int, n: int, fill_cell_data, erase_attr = null) -> void:
|
||||
pos %= length
|
||||
if n < length - pos:
|
||||
var cell = CellData.new()
|
||||
for i in range(length - pos - n):
|
||||
set_cell(pos + i, load_cell(pos + n + i, cell))
|
||||
for i in range(length - n, length):
|
||||
set_cell(i, fill_cell_data)
|
||||
else:
|
||||
for i in range(pos, length):
|
||||
set_cell(i, fill_cell_data)
|
||||
|
||||
# handle fullwidth at pos:
|
||||
# - reset pos-1 if wide char
|
||||
# - reset pos if width==0 (previous second cell of a wide char)
|
||||
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
|
||||
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
|
||||
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
|
||||
if pos and get_width(pos - 1) == 2:
|
||||
set_cell_from_codepoint(pos - 1, 0, 1, fg, bg, extended)
|
||||
if get_width(pos) == 0 and not has_content(pos):
|
||||
set_cell_from_codepoint(pos, 0, 1, fg, bg, extended)
|
||||
|
||||
|
||||
func replace_cells(start: int, end: int, fill_cell_data, erase_attr = null) -> void:
|
||||
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
|
||||
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
|
||||
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
|
||||
|
||||
# handle fullwidth at start: reset cell one to left if start is second cell of a wide char
|
||||
if start and get_width(start - 1) == 2:
|
||||
set_cell_from_codepoint(start - 1, 0, 1, fg, bg, extended)
|
||||
# handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
|
||||
if end < length and get_width(end - 1) == 2:
|
||||
set_cell_from_codepoint(end, 0, 1, fg, bg, extended)
|
||||
|
||||
while start < end and start < length:
|
||||
set_cell(start, fill_cell_data)
|
||||
start += 1
|
||||
|
||||
|
||||
func resize(cols: int, fill_cell_data) -> void:
|
||||
if cols == length:
|
||||
return
|
||||
if cols > length:
|
||||
var data = []
|
||||
if length:
|
||||
if cols * CELL_SIZE < _data.size():
|
||||
data = _data.slice(0, cols * CELL_SIZE - 1)
|
||||
else:
|
||||
data = _data.duplicate()
|
||||
data.resize(cols * CELL_SIZE)
|
||||
_data = data
|
||||
var i = length
|
||||
while i < cols:
|
||||
set_cell(i, fill_cell_data)
|
||||
i += 1
|
||||
else:
|
||||
if cols:
|
||||
var data = []
|
||||
data = _data.slice(0, cols * CELL_SIZE - 1)
|
||||
data.resize(cols * CELL_SIZE)
|
||||
_data = data
|
||||
# Remove any cut off combined data, FIXME: repeat this for extended attrs
|
||||
for key in _combined.keys():
|
||||
if key as int > cols:
|
||||
_combined.erase(key)
|
||||
else:
|
||||
_data = []
|
||||
_combined = {}
|
||||
length = cols
|
||||
|
||||
|
||||
# Fill a line with `fill_cell_data`.
|
||||
func fill(fill_cell_data) -> void:
|
||||
_combined = {}
|
||||
_extended_attrs = {}
|
||||
for i in range(length):
|
||||
set_cell(i, fill_cell_data)
|
||||
|
||||
|
||||
func get_trimmed_length() -> int:
|
||||
for i in range(length - 1, 0, -1):
|
||||
if _data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK:
|
||||
return i + (_data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT)
|
||||
return 0
|
||||
|
||||
|
||||
func translate_to_string(trim_right: bool = false, start_col: int = 0, end_col: int = -1) -> String:
|
||||
if end_col == -1:
|
||||
end_col = length
|
||||
if trim_right:
|
||||
end_col = min(end_col, get_trimmed_length())
|
||||
var result = ""
|
||||
while start_col < end_col:
|
||||
var content = _data[start_col * CELL_SIZE + Cell.CONTENT]
|
||||
var cp = content & Content.CODEPOINT_MASK
|
||||
if content & Content.IS_COMBINED_MASK:
|
||||
result += _combined[start_col]
|
||||
elif cp:
|
||||
result += char(cp)
|
||||
else:
|
||||
result += Constants.WHITESPACE_CELL_CHAR
|
||||
start_col += max(content >> Content.WIDTH_SHIFT, 1) # always advance by 1
|
||||
return result
|
55
addons/godot_xterm/buffer/buffer_set.gd
Normal file
55
addons/godot_xterm/buffer/buffer_set.gd
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
const Buffer = preload("res://addons/godot_xterm/buffer/buffer.gd")
|
||||
|
||||
signal buffer_activated(active_buffer, inactive_buffer)
|
||||
|
||||
var normal
|
||||
var alt
|
||||
var active
|
||||
|
||||
|
||||
func _init(options_service, buffer_service):
|
||||
normal = Buffer.new(true, options_service, buffer_service)
|
||||
normal.fill_viewport_rows()
|
||||
|
||||
# The alt buffer should never have scrollback.
|
||||
# See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
|
||||
alt = Buffer.new(false, options_service, buffer_service)
|
||||
active = normal
|
||||
|
||||
# TODO
|
||||
#setup_tab_stops()
|
||||
|
||||
|
||||
# Sets the normal Bufer of the BufferSet as its currently active Buffer.
|
||||
func activate_normal_buffer() -> void:
|
||||
if active == normal:
|
||||
return
|
||||
|
||||
normal.x = alt.x
|
||||
normal.y = alt.y
|
||||
|
||||
# The alt buffer should always be cleared when we switch to the normal
|
||||
# buffer. This frees up memory since the alt buffer should always be new
|
||||
# when activated.
|
||||
alt.clear()
|
||||
active = normal
|
||||
emit_signal("buffer_activated", normal, alt)
|
||||
|
||||
|
||||
# Sets the alt Buffer of the BufferSet as its currently active Buffer.
|
||||
func activate_alt_buffer(fill_attr = null) -> void:
|
||||
if active == alt:
|
||||
return
|
||||
|
||||
# Since the alt buffer is always cleared when the normal buffer is
|
||||
# activated, we want to fill it when switching to it.
|
||||
alt.fill_viewport_rows(fill_attr)
|
||||
alt.x = normal.x
|
||||
alt.y = normal.y
|
||||
active = alt
|
||||
emit_signal("buffer_activated", alt, normal)
|
68
addons/godot_xterm/buffer/cell_data.gd
Normal file
68
addons/godot_xterm/buffer/cell_data.gd
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends "res://addons/godot_xterm/buffer/attribute_data.gd"
|
||||
# CellData - represents a single cell in the terminal buffer.
|
||||
|
||||
|
||||
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
|
||||
|
||||
const Content = Constants.Content
|
||||
|
||||
# Helper to create CellData from CharData
|
||||
static func from_char_data(value):
|
||||
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
|
||||
var char_data = load("res://addons/godot_xterm/buffer/cell_data.gd").new()
|
||||
char_data.set_from_char_data(value)
|
||||
return char_data
|
||||
|
||||
|
||||
# Primitives from terminal buffer
|
||||
var content = 0
|
||||
var combined_data = ''
|
||||
|
||||
|
||||
# Whether cell contains a combined string
|
||||
func is_combined() -> int:
|
||||
return content & Content.IS_COMBINED_MASK
|
||||
|
||||
|
||||
func get_width() -> int:
|
||||
return content >> Content.WIDTH_SHIFT
|
||||
|
||||
|
||||
func get_chars() -> String:
|
||||
if content & Content.IS_COMBINED_MASK:
|
||||
return combined_data
|
||||
elif content & Content.CODEPOINT_MASK:
|
||||
return char(content & Content.CODEPOINT_MASK)
|
||||
else:
|
||||
return Constants.NULL_CELL_CHAR
|
||||
|
||||
func get_code() -> int:
|
||||
if is_combined():
|
||||
return combined_data.ord_at(combined_data.length() - 1)
|
||||
else:
|
||||
return content & Content.CODEPOINT_MASK
|
||||
|
||||
|
||||
func set_from_char_data(value) -> void:
|
||||
var attr: int = value[Constants.CHAR_DATA_ATTR_INDEX]
|
||||
var character: String = value[Constants.CHAR_DATA_CHAR_INDEX]
|
||||
var width: int = value[Constants.CHAR_DATA_WIDTH_INDEX]
|
||||
var code: int = value[Constants.CHAR_DATA_CODE_INDEX]
|
||||
|
||||
fg = attr
|
||||
bg = 0
|
||||
# combined strings need special treatment. Javascript uses utf16 for strings
|
||||
# whereas Godot uses utf8, therefore we don't need any of the special
|
||||
# handling of surrogates in the original xterm.js code.
|
||||
if character.length() >= 2:
|
||||
combined_data = character
|
||||
content = Content.IS_COMBINED_MASK | (width << Content.WIDTH_SHIFT)
|
||||
else:
|
||||
content = (character.ord_at(0) if character.length() else 0) | (width << Content.WIDTH_SHIFT)
|
||||
|
||||
|
||||
func get_as_char_data():
|
||||
return [fg, get_chars(), get_width(), get_code()]
|
93
addons/godot_xterm/buffer/constants.gd
Normal file
93
addons/godot_xterm/buffer/constants.gd
Normal file
|
@ -0,0 +1,93 @@
|
|||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const DEFAULT_COLOR = 256
|
||||
const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0)
|
||||
|
||||
const CHAR_DATA_ATTR_INDEX = 0
|
||||
const CHAR_DATA_CHAR_INDEX = 1
|
||||
const CHAR_DATA_WIDTH_INDEX = 2
|
||||
const CHAR_DATA_CODE_INDEX = 3
|
||||
|
||||
# Null cell - a real empty cell (containing nothing).
|
||||
# Note that code should always be 0 for a null cell as
|
||||
# several test condition of the buffer line rely on this.
|
||||
const NULL_CELL_CHAR = ''
|
||||
const NULL_CELL_WIDTH = 1
|
||||
const NULL_CELL_CODE = 0
|
||||
|
||||
|
||||
# Whitespace cell.
|
||||
# This is meant as a replacement for empty cells when needed
|
||||
# during rendering lines to preserve correct alignment.
|
||||
const WHITESPACE_CELL_CHAR = ' '
|
||||
const WHITESPACE_CELL_WIDTH = 1
|
||||
const WHITESPACE_CELL_CODE = 32
|
||||
|
||||
|
||||
# Bitmasks for accessing data in `content`.
|
||||
enum Content {
|
||||
CODEPOINT_MASK = 0x1FFFFF
|
||||
IS_COMBINED_MASK = 0x200000
|
||||
HAS_CONTENT_MASK = 0x3FFFFF
|
||||
WIDTH_MASK = 0xC00000
|
||||
WIDTH_SHIFT = 22
|
||||
}
|
||||
|
||||
|
||||
enum Attributes {
|
||||
# bit 1..8 blue in RGB, color in P256 and P16
|
||||
BLUE_MASK = 0xFF
|
||||
BLUE_SHIFT = 0
|
||||
PCOLOR_MASK = 0xFF
|
||||
PCOLOR_SHIFT = 0
|
||||
|
||||
# bit 9..16 green in RGB
|
||||
GREEN_MASK = 0xFF00
|
||||
GREEN_SHIFT = 8
|
||||
|
||||
# bit 17..24 red in RGB
|
||||
RED_MASK = 0xFF0000
|
||||
RED_SHIFT = 16
|
||||
|
||||
# bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
|
||||
CM_MASK = 0x3000000
|
||||
CM_DEFAULT = 0
|
||||
CM_P16 = 0x1000000
|
||||
CM_P256 = 0x2000000
|
||||
CM_RGB = 0x3000000
|
||||
|
||||
# bit 1..24 RGB room
|
||||
RGB_MASK = 0xFFFFFF
|
||||
}
|
||||
|
||||
|
||||
enum FgFlags {
|
||||
# bit 27..31 (32th bit unused)
|
||||
INVERSE = 0x4000000
|
||||
BOLD = 0x8000000
|
||||
UNDERLINE = 0x10000000
|
||||
BLINK = 0x20000000
|
||||
INVISIBLE = 0x40000000
|
||||
}
|
||||
|
||||
|
||||
enum BgFlags {
|
||||
# bit 27..32 (upper 3 unused)
|
||||
ITALIC = 0x4000000
|
||||
DIM = 0x8000000
|
||||
HAS_EXTENDED = 0x10000000
|
||||
}
|
||||
|
||||
|
||||
enum UnderlineStyle {
|
||||
NONE
|
||||
SINGLE
|
||||
DOUBLE
|
||||
CURLY
|
||||
DOTTED
|
||||
DASHED
|
||||
}
|
75
addons/godot_xterm/circular_list.gd
Normal file
75
addons/godot_xterm/circular_list.gd
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
# Represents a circular list; a list with a maximum size that wraps around when push is called,
|
||||
# overriding values at the start of the list.
|
||||
|
||||
signal deleted
|
||||
signal inserted
|
||||
signal trimmed
|
||||
|
||||
var _array
|
||||
var _start_index: int
|
||||
var length: int = 0 setget _set_length,_get_length
|
||||
var max_length: int setget _set_max_length,_get_max_length
|
||||
|
||||
|
||||
func _set_length(new_length: int):
|
||||
if new_length > length:
|
||||
for i in range(length, new_length):
|
||||
_array[i] = null
|
||||
length = new_length
|
||||
|
||||
|
||||
func _get_length():
|
||||
return length
|
||||
|
||||
|
||||
func _set_max_length(new_max_length):
|
||||
if max_length == new_max_length:
|
||||
return
|
||||
|
||||
# Reconstruct array, starting at index 0.
|
||||
# Only transfer values from the indexes 0 to length.
|
||||
var new_array = []
|
||||
new_array.resize(new_max_length)
|
||||
for i in range(0, min(new_max_length, length)):
|
||||
new_array[i] = _array[_get_cyclic_index(i)]
|
||||
_array = new_array
|
||||
max_length = new_max_length
|
||||
_start_index = 0
|
||||
|
||||
|
||||
func _get_max_length():
|
||||
return max_length
|
||||
|
||||
|
||||
func _init(max_length):
|
||||
self.max_length = max_length
|
||||
_array = []
|
||||
_array.resize(max_length)
|
||||
_start_index = 0
|
||||
|
||||
|
||||
func get_el(index: int):
|
||||
return _array[_get_cyclic_index(index)]
|
||||
|
||||
|
||||
func set_el(index: int, value) -> void:
|
||||
_array[_get_cyclic_index(index)] = value
|
||||
|
||||
|
||||
func push(value) -> void:
|
||||
_array[_get_cyclic_index(length)] = value
|
||||
if length == max_length:
|
||||
_start_index += 1
|
||||
_start_index %= max_length
|
||||
emit_signal("trimmed", 1)
|
||||
else:
|
||||
length += 1
|
||||
|
||||
|
||||
func _get_cyclic_index(index: int) -> int:
|
||||
return _start_index + index % max_length
|
||||
|
108
addons/godot_xterm/color_manager.gd
Normal file
108
addons/godot_xterm/color_manager.gd
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
# Xterm.js stores colors in both css and rgba formats. In this case we only need
|
||||
# to store the colors in godots RGBA Color format.
|
||||
|
||||
|
||||
static func _generate_default_ansi_colors() -> PoolColorArray:
|
||||
var colors = PoolColorArray([
|
||||
# dark:
|
||||
Color('#2e3436'),
|
||||
Color('#cc0000'),
|
||||
Color('#4e9a06'),
|
||||
Color('#c4a000'),
|
||||
Color('#3465a4'),
|
||||
Color('#75507b'),
|
||||
Color('#06989a'),
|
||||
Color('#d3d7cf'),
|
||||
# bright:
|
||||
Color('#555753'),
|
||||
Color('#ef2929'),
|
||||
Color('#8ae234'),
|
||||
Color('#fce94f'),
|
||||
Color('#729fcf'),
|
||||
Color('#ad7fa8'),
|
||||
Color('#34e2e2'),
|
||||
Color('#eeeeec'),
|
||||
])
|
||||
|
||||
# Fill in the remaining 240 ANSI colors.
|
||||
# Generate colors (16-231)
|
||||
var v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
|
||||
for i in range(0, 216):
|
||||
var r = v[(i / 36) % 6 | 0]
|
||||
var g = v[(i / 6) % 6 | 0]
|
||||
var b = v[i % 6]
|
||||
colors.append(Color(r, g, b))
|
||||
|
||||
# Generate greys (232-255)
|
||||
for i in range(0, 24):
|
||||
var c = 8 + i * 10
|
||||
colors.append(Color(c, c, c))
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
const DEFAULT_FOREGROUND = Color('#ffffff')
|
||||
const DEFAULT_BACKGROUND = Color('#000000')
|
||||
const DEFAULT_CURSOR = Color('#ffffff')
|
||||
const DEFAULT_CURSOR_ACCENT = Color('#000000')
|
||||
const DEFAULT_SELECTION = Color(1, 1, 1, 0.3)
|
||||
var DEFAULT_ANSI_COLORS = _generate_default_ansi_colors()
|
||||
|
||||
var colors
|
||||
var _litmus_color: Gradient = Gradient.new()
|
||||
var _contrast_cache: Dictionary = {}
|
||||
|
||||
|
||||
func _init():
|
||||
colors = {
|
||||
'foreground': DEFAULT_FOREGROUND,
|
||||
'background': DEFAULT_BACKGROUND,
|
||||
'cursor': DEFAULT_CURSOR,
|
||||
'cursor_accent': DEFAULT_CURSOR_ACCENT,
|
||||
'selection': DEFAULT_SELECTION,
|
||||
'selection_opaque': DEFAULT_BACKGROUND.blend(DEFAULT_SELECTION),
|
||||
'ansi': DEFAULT_ANSI_COLORS,
|
||||
'contrast_cache': _contrast_cache,
|
||||
}
|
||||
|
||||
|
||||
func on_options_change(key: String) -> void:
|
||||
if key == 'minimum_contrast_ratio':
|
||||
_contrast_cache.clear()
|
||||
|
||||
|
||||
# Sets the terminal's theme.
|
||||
# If a partial theme is provided then default
|
||||
# colors will be used where colors are not defined.
|
||||
func set_theme(theme: Dictionary = {}) -> void:
|
||||
colors['foreground'] = theme.get('foreground', DEFAULT_FOREGROUND)
|
||||
colors['bakcground'] = theme.get('background', DEFAULT_BACKGROUND)
|
||||
colors['cursor'] = theme.get('cursor', DEFAULT_CURSOR)
|
||||
colors['cursor_accent'] = theme.get('cursor_accent', DEFAULT_CURSOR_ACCENT)
|
||||
colors['selection'] = theme.get('selection', DEFAULT_SELECTION)
|
||||
colors['selection_opaque'] = theme.get('selection_opaque', colors['selection_opaque'])
|
||||
colors['ansi'][0] = theme.get('black', DEFAULT_ANSI_COLORS[0])
|
||||
colors['ansi'][1] = theme.get('red', DEFAULT_ANSI_COLORS[1])
|
||||
colors['ansi'][2] = theme.get('green', DEFAULT_ANSI_COLORS[2])
|
||||
colors['ansi'][3] = theme.get('yellow', DEFAULT_ANSI_COLORS[3])
|
||||
colors['ansi'][4] = theme.get('blue', DEFAULT_ANSI_COLORS[4])
|
||||
colors['ansi'][5] = theme.get('magenta', DEFAULT_ANSI_COLORS[5])
|
||||
colors['ansi'][6] = theme.get('cyan', DEFAULT_ANSI_COLORS[6])
|
||||
colors['ansi'][7] = theme.get('white', DEFAULT_ANSI_COLORS[7])
|
||||
colors['ansi'][8] = theme.get('bright_black', DEFAULT_ANSI_COLORS[8])
|
||||
colors['ansi'][9] = theme.get('bright_red', DEFAULT_ANSI_COLORS[9])
|
||||
colors['ansi'][10] = theme.get('bright_green', DEFAULT_ANSI_COLORS[10])
|
||||
colors['ansi'][11] = theme.get('bright_yellow', DEFAULT_ANSI_COLORS[11])
|
||||
colors['ansi'][12] = theme.get('bright_blue', DEFAULT_ANSI_COLORS[12])
|
||||
colors['ansi'][13] = theme.get('bright_magenta', DEFAULT_ANSI_COLORS[13])
|
||||
colors['ansi'][14] = theme.get('bright_cyan', DEFAULT_ANSI_COLORS[14])
|
||||
colors['ansi'][15] = theme.get('bright_white', DEFAULT_ANSI_COLORS[15])
|
||||
|
||||
_contrast_cache.clear()
|
||||
|
||||
|
||||
|
24
addons/godot_xterm/data/charsets.gd
Normal file
24
addons/godot_xterm/data/charsets.gd
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyrigth (c) 2016 The xterm.js authors. All rights reserved
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
# The character sets supported by the terminal. These enable several languages
|
||||
# to be represented within the terminal with only 8-bit encoding. See ISO 2022
|
||||
# for a discussion on character sets. Only VT100 character sets are supported.
|
||||
const CHARSETS = {
|
||||
# British character set
|
||||
# ESC (A
|
||||
# Reference: http://vt100.net/docs/vt220-rm/table2-5.html
|
||||
'A': {
|
||||
'#': '£'
|
||||
},
|
||||
|
||||
# United States character set
|
||||
# ESC (B
|
||||
'B': null,
|
||||
}
|
||||
|
||||
# The default character set, US.
|
||||
const DEFAULT_CHARSET = CHARSETS['B']
|
|
@ -3,4 +3,5 @@
|
|||
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
size = 20
|
||||
font_data = ExtResource( 1 )
|
||||
|
|
|
@ -28,22 +28,19 @@ static func utf32_to_utf8(codepoint: int):
|
|||
|
||||
return utf8
|
||||
|
||||
# Convert UTF32 codepoint into a String.
|
||||
static func string_from_codepoint(codepoint: int):
|
||||
var utf8 = utf32_to_utf8(codepoint)
|
||||
return utf8.get_string_from_utf8()
|
||||
|
||||
# Covert UTF32 char codes into a String.
|
||||
# Basically the same as `string_from_codepoint` but for multiple codepoints
|
||||
# Basically the same as `char` but for multiple codepoints
|
||||
# in a loop (which is a lot faster).
|
||||
static func utf32_to_string(data: Array, start: int = 0, end: int = -1):
|
||||
if end == -1:
|
||||
end = data.size()
|
||||
var result = ''
|
||||
for i in range(start, end):
|
||||
result += string_from_codepoint(data[i])
|
||||
result += char(data[i])
|
||||
return result
|
||||
|
||||
|
||||
# Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.
|
||||
class Utf8ToUtf32:
|
||||
var interim = PoolByteArray()
|
||||
|
|
1036
addons/godot_xterm/input_handler.gd
Normal file
1036
addons/godot_xterm/input_handler.gd
Normal file
File diff suppressed because it is too large
Load diff
|
@ -244,7 +244,7 @@ func parse(data: Array, length: int):
|
|||
handlers.invert()
|
||||
for handler in handlers:
|
||||
# undefined or true means success and to stop bubbling
|
||||
if handler['target'].call(handler['method'], params.to_array()):
|
||||
if handler['target'].call(handler['method'], params):
|
||||
continue
|
||||
handlers.invert()
|
||||
if handlers.empty():
|
||||
|
|
|
@ -53,6 +53,17 @@ func _init(max_length: int = 32, max_sub_params_length: int = 32):
|
|||
sub_params.resize(max_sub_params_length)
|
||||
sub_params_idx.resize(max_length)
|
||||
|
||||
|
||||
# Gets param at `index` from param if it exists and is non-zero.
|
||||
# Otherwise returns `default` (which is zero anyway due to zero default
|
||||
# mode (ZDM), but allows the caller to specify a non-zero default value).
|
||||
func get_param(index: int, default = 0) -> int:
|
||||
if index < params.size() and params[index]:
|
||||
return params[index]
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
func add_param(value: int):
|
||||
digit_is_sub = false
|
||||
if length >= _max_length:
|
||||
|
@ -78,7 +89,6 @@ func add_sub_param(value: int):
|
|||
sub_params_idx[length - 1] += 1
|
||||
|
||||
func add_digit(value: int):
|
||||
print("adding digit: ", value, " is sub: ", digit_is_sub)
|
||||
var _length = sub_params_length if digit_is_sub else length
|
||||
if _reject_digits or (not _length) or (digit_is_sub and _reject_sub_digits):
|
||||
return
|
||||
|
@ -86,6 +96,11 @@ func add_digit(value: int):
|
|||
var cur = store[_length - 1]
|
||||
store[_length - 1] = min(cur * 10 + value, MAX_VALUE) if ~cur else value
|
||||
|
||||
|
||||
func size():
|
||||
return params.size()
|
||||
|
||||
|
||||
func to_array():
|
||||
var res = []
|
||||
for i in range(length):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
|
250
addons/godot_xterm/renderer/base_render_layer.gd
Normal file
250
addons/godot_xterm/renderer/base_render_layer.gd
Normal file
|
@ -0,0 +1,250 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
|
||||
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
|
||||
const CanvasRenderingContext2D = preload("res://addons/godot_xterm/renderer/canvas_rendering_context_2d.gd")
|
||||
|
||||
|
||||
const Attributes = Constants.Attributes
|
||||
# TODO: Something about these consts and atlas
|
||||
const INVERTED_DEFAULT_COLOR = Color(0, 0, 0, 1)
|
||||
const DEFAULT_COLOR = Color(1, 1, 1, 0)
|
||||
|
||||
var _container: Node
|
||||
var id: String
|
||||
var z_index: int
|
||||
var _alpha: bool
|
||||
var _colors
|
||||
var _renderer_id: int
|
||||
var _buffer_service
|
||||
var _options_service
|
||||
|
||||
var _ctx: CanvasRenderingContext2D
|
||||
var _scaled_char_width: int = 0
|
||||
var _scaled_char_height: int = 0
|
||||
var _scaled_cell_width: int = 0
|
||||
var _scaled_cell_height: int = 0
|
||||
var _scaled_char_left: int = 0
|
||||
var _scaled_char_top: int = 0
|
||||
var _char_atlas
|
||||
|
||||
|
||||
# An object that's reused when drawing glyphs in order to reduce GC.
|
||||
class GlyphIdentifier:
|
||||
extends Reference
|
||||
var chars = ''
|
||||
var code = 0
|
||||
var bg = 0
|
||||
var fg = 0
|
||||
var bold = false
|
||||
var dim = false
|
||||
var italic = false
|
||||
|
||||
var _current_glyph_identifier = GlyphIdentifier.new()
|
||||
|
||||
|
||||
func _init(container: Node, id: String, z_index: int, alpha: bool,
|
||||
colors: Dictionary, renderer_id: int, buffer_service, options_service):
|
||||
_container = container
|
||||
self.id = id
|
||||
self.z_index = z_index
|
||||
_alpha = alpha
|
||||
_colors = colors
|
||||
_renderer_id = renderer_id
|
||||
_buffer_service = buffer_service
|
||||
_options_service = options_service
|
||||
|
||||
_ctx = CanvasRenderingContext2D.new()
|
||||
_ctx.z_index = z_index
|
||||
_container.add_child(_ctx)
|
||||
|
||||
|
||||
|
||||
func on_grid_changed(start_row: int, end_row: int) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func resize(dim) -> void:
|
||||
_scaled_cell_width = dim.scaled_cell_width
|
||||
_scaled_cell_height = dim.scaled_cell_height
|
||||
_scaled_char_width = dim.scaled_char_width
|
||||
_scaled_char_height = dim.scaled_char_height
|
||||
_scaled_char_left = dim.scaled_char_left
|
||||
_scaled_char_top = dim.scaled_char_top
|
||||
#_canvas_width = dim.scaled_canvas_width
|
||||
#_canvas_height = dim.scaled_canvas_height
|
||||
#this._canvas.style.width = `${dim.canvasWidth}px`;
|
||||
#this._canvas.style.height = `${dim.canvasHeight}px`;
|
||||
|
||||
func _fill_cells(x: int, y: int, width: int, height: int) -> void:
|
||||
_ctx.fill_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
|
||||
width * _scaled_cell_width, height * _scaled_cell_height))
|
||||
|
||||
func _clear_cells(x: int, y: int, width: int, height: int) -> void:
|
||||
var scaled = Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
|
||||
width * _scaled_cell_width, height * _scaled_cell_height)
|
||||
|
||||
if _alpha:
|
||||
_ctx.clear_rect(scaled)
|
||||
else:
|
||||
_ctx.fill_style = _colors.background
|
||||
_ctx.fill_rect(scaled)
|
||||
|
||||
|
||||
func _draw_chars(cell, x, y) -> void:
|
||||
# TODO
|
||||
#var contrast_color = _get_contrast_color(cell)
|
||||
var contrast_color = null
|
||||
|
||||
# skip cache right away if we draw in RGB
|
||||
# Note: to avoid bad runtime JoinedCellData will be skipped
|
||||
# in the cache handler itself (atlasDidDraw == false) and
|
||||
# fall through to uncached later down below
|
||||
if contrast_color or cell.is_fg_rgb() or cell.is_bg_rgb():
|
||||
_draw_uncached_chars(cell, x, y, contrast_color)
|
||||
return
|
||||
|
||||
var fg
|
||||
var bg
|
||||
if cell.is_inverse():
|
||||
fg = INVERTED_DEFAULT_COLOR if cell.is_bg_default() else cell.get_bg_color()
|
||||
bg = INVERTED_DEFAULT_COLOR if cell.is_fg_default() else cell.get_fg_color()
|
||||
else:
|
||||
bg = DEFAULT_COLOR if cell.is_bg_default() else cell.get_bg_color()
|
||||
fg = DEFAULT_COLOR if cell.is_fg_default() else cell.get_fg_color()
|
||||
|
||||
var draw_in_bright_color = _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and fg < 8
|
||||
|
||||
fg = Color(fg as int + 8) if draw_in_bright_color else 0
|
||||
_current_glyph_identifier.chars = cell.get_chars() if cell.get_chars() else Constants.WHITESPACE_CELL_CHAR
|
||||
_current_glyph_identifier.code = cell.get_code() if cell.get_code() else Constants.WHITESPACE_CELL_CODE
|
||||
_current_glyph_identifier.bg = bg
|
||||
_current_glyph_identifier.fg = fg
|
||||
_current_glyph_identifier.bold = cell.is_bold() as bool
|
||||
_current_glyph_identifier.dim = cell.is_dim() as bool
|
||||
_current_glyph_identifier.italic = cell.is_italic() as bool
|
||||
var atlas_did_draw = _char_atlas and _char_atlas.draw(_ctx,
|
||||
_current_glyph_identifier, x * _scaled_cell_width + _scaled_char_left,
|
||||
y * _scaled_cell_width, _scaled_char_top)
|
||||
|
||||
if not atlas_did_draw:
|
||||
_draw_uncached_chars(cell, x, y)
|
||||
|
||||
|
||||
# Draws one or more charaters at one or more cells. The character(s) will be
|
||||
# clipped to ensure that they fit with the cell(s), including the cell to the
|
||||
# right if the last character is a wide character.
|
||||
func _draw_uncached_chars(cell, x: int, y: int, fg_override = null) -> void:
|
||||
_ctx.save()
|
||||
_ctx.font = _get_font(cell.is_bold() as bool, cell.is_italic() as bool)
|
||||
|
||||
if cell.is_inverse():
|
||||
if cell.is_bg_default():
|
||||
_ctx.fill_style = _colors.background
|
||||
elif cell.is_bg_rgb():
|
||||
_ctx.fill_style = AttributeData.to_color_rgb(cell.get_bg_color())
|
||||
else:
|
||||
var bg = cell.get_bg_color()
|
||||
if _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and bg < 8:
|
||||
bg += 8
|
||||
_ctx.fill_style = _colors.ansi[bg]
|
||||
else:
|
||||
if cell.is_fg_default():
|
||||
_ctx.fill_style = _colors.foreground
|
||||
elif cell.is_fg_rgb():
|
||||
_ctx.fill_style = AttributeData.to_color_rgb(cell.get_fg_color())
|
||||
else:
|
||||
var fg = cell.get_fg_color()
|
||||
if _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and fg < 8:
|
||||
fg += 8
|
||||
_ctx.fill_style = _colors.ansi[fg]
|
||||
|
||||
#_clip_row(y)
|
||||
|
||||
# Apply alpha to dim the character
|
||||
if cell.is_dim():
|
||||
pass
|
||||
#_ctx.global_alpha = DIM_OPACITY
|
||||
# Draw the character
|
||||
_ctx.fill_text(cell.get_chars(), x * _scaled_cell_width + _scaled_char_left,
|
||||
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2)
|
||||
_ctx.restore()
|
||||
|
||||
func _get_font(is_bold: bool, is_italic: bool) -> Font:
|
||||
var font_family = _options_service.options.font_family
|
||||
|
||||
if is_bold and is_italic and font_family.bold_italic:
|
||||
return font_family.bold_italic
|
||||
elif is_bold and font_family.bold:
|
||||
return font_family.bold
|
||||
elif is_italic and font_family.italic:
|
||||
return font_family.italic
|
||||
else:
|
||||
return font_family.regular
|
||||
|
||||
|
||||
func _get_contrast_color(cell):
|
||||
if _options_service.options.minimum_contrast_ratio == 1:
|
||||
return null
|
||||
|
||||
var adjusted_color = _colors.contrast_cache.get_color(cell.bg, cell.fg)
|
||||
if adjusted_color != null:
|
||||
return adjusted_color
|
||||
|
||||
var fg_color = cell.get_fg_color()
|
||||
var fg_color_mode = cell.get_fg_color_mode()
|
||||
var bg_color = cell.get_bg_color()
|
||||
var bg_color_mode = cell.get_bg_color_mode()
|
||||
var is_inverse = cell.is_inverse() as bool
|
||||
var is_bold = cell.is_bold() as bool
|
||||
if is_inverse:
|
||||
var temp = fg_color
|
||||
fg_color = bg_color
|
||||
bg_color = temp
|
||||
var temp2 = fg_color_mode
|
||||
fg_color_mode = bg_color_mode
|
||||
bg_color_mode = temp2
|
||||
|
||||
var bg_rgba = _resolve_background_rgba(bg_color_mode, bg_color, is_inverse)
|
||||
var fg_rgba = _resolve_foreground_rgba(fg_color_mode, fg_color, is_inverse, is_bold)
|
||||
# TODO
|
||||
#var result = rgba.ensure_contrast_ratio(bg_rgba, fg_rgba, _options_service.options.minimum_contrast_ratio)
|
||||
|
||||
func _resolve_background_rgba(bg_color_mode: int, bg_color: int, inverse: bool) -> int:
|
||||
match bg_color_mode:
|
||||
Attributes.CM_P16, Attributes.CM_P256:
|
||||
return _colors.ansi[bg_color].rgba
|
||||
Attributes.CM_RGB:
|
||||
return bg_color << 8
|
||||
Attributes.CM_DEFAULT, _:
|
||||
if inverse:
|
||||
return _colors.foreground.rgba
|
||||
else:
|
||||
return _colors.background.rgba
|
||||
|
||||
|
||||
func _resolve_foreground_rgba(fg_color_mode: int, fg_color: int, inverse: bool, bold: bool):
|
||||
match fg_color_mode:
|
||||
Attributes.CM_P16, Attributes.CM_P256:
|
||||
if _options_service.options.draw_bold_text_in_bright_colors and bold and fg_color < 8:
|
||||
return _colors.ansi[fg_color].rgba
|
||||
Attributes.CM_RGB:
|
||||
return fg_color << 8
|
||||
Attributes.CM_DEFAULT, _:
|
||||
if inverse:
|
||||
return _colors.background.rgba
|
||||
else:
|
||||
return _colors.foreground.rgba
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
74
addons/godot_xterm/renderer/canvas_rendering_context_2d.gd
Normal file
74
addons/godot_xterm/renderer/canvas_rendering_context_2d.gd
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Node2D
|
||||
class_name CanvasRenderingContext2D
|
||||
# This is a shim for the CavasRenderingContext2D interface of HTML5's Canvas API,
|
||||
# which the xterm.js renderer code uses heavily. It extends Node2D to take
|
||||
# advantage of the z_index property and also uses many methods of CanvasItem
|
||||
# which Node2D inherits.
|
||||
|
||||
|
||||
var fill_style
|
||||
var font = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
|
||||
var _saved
|
||||
var _draw_buffer = []
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
func draw_rect_deferred(rect: Rect2, color: Color):
|
||||
_draw_buffer.append({"method": "draw_rect", "args": [rect, color]})
|
||||
update()
|
||||
|
||||
|
||||
func clear_rect(rect: Rect2):
|
||||
draw_rect_deferred(rect, Color(0, 0, 0, 0))
|
||||
|
||||
|
||||
func fill_rect(rect: Rect2):
|
||||
draw_rect_deferred(rect, fill_style)
|
||||
|
||||
|
||||
func fill_text(text: String, x: int, y: int):
|
||||
_draw_buffer.append({"method": "_draw_text", "args": [font, Vector2(x, y), text, fill_style]})
|
||||
update()
|
||||
|
||||
func _draw_text(font: Font, pos: Vector2, text: String, color) -> void:
|
||||
for i in text.length():
|
||||
var c = text[i]
|
||||
var next_char = text[i + 1] if i + 1 < text.length() else ''
|
||||
var advance = draw_char(font, pos, c, next_char, color)
|
||||
pos.x += advance
|
||||
|
||||
|
||||
func _draw():
|
||||
for command in _draw_buffer:
|
||||
self.callv(command.method, command.args)
|
||||
_draw_buffer.resize(0)
|
||||
|
||||
|
||||
func save():
|
||||
_saved = {
|
||||
'fill_style': fill_style,
|
||||
'font': font,
|
||||
}
|
||||
|
||||
|
||||
func restore():
|
||||
fill_style = _saved['fill_style']
|
||||
font = _saved['font']
|
||||
|
||||
|
||||
func measure_text(text: String):
|
||||
var text_metrics = TextMetrics.new()
|
||||
text_metrics.width = font.get_string_size(text).x
|
||||
return text_metrics
|
||||
|
||||
class TextMetrics:
|
||||
extends Reference
|
||||
# https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics
|
||||
|
||||
var width
|
46
addons/godot_xterm/renderer/character_joiner_registry.gd
Normal file
46
addons/godot_xterm/renderer/character_joiner_registry.gd
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
|
||||
|
||||
|
||||
class JoinedCellData extends "res://addons/godot_xterm/buffer/attribute_data.gd":
|
||||
|
||||
|
||||
var _width: int = 0
|
||||
var content: int = 0
|
||||
var combined_data: String = ''
|
||||
|
||||
|
||||
func _init(first_cell, chars: String, width: int):
|
||||
fg = first_cell.fg
|
||||
bg = first_cell.bg
|
||||
combined_data = chars
|
||||
_width = width
|
||||
|
||||
|
||||
var _character_joiners: Array = []
|
||||
var _next_character_joiner_id = 0
|
||||
var _work_cell = CellData.new()
|
||||
var _buffer_service
|
||||
|
||||
|
||||
func _init(buffer_service):
|
||||
_buffer_service = buffer_service
|
||||
|
||||
|
||||
func get_joined_characters(row: int) -> Array:
|
||||
if _character_joiners.empty():
|
||||
return []
|
||||
|
||||
var line = _buffer_service.buffer.lines.get_el(row)
|
||||
if not line or line.length == 0:
|
||||
return []
|
||||
|
||||
var ranges = []
|
||||
var line_str = line.translate_to_string(true)
|
||||
|
||||
return ranges
|
118
addons/godot_xterm/renderer/renderer.gd
Normal file
118
addons/godot_xterm/renderer/renderer.gd
Normal file
|
@ -0,0 +1,118 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const CharacterJoinerRegistry = preload("res://addons/godot_xterm/renderer/character_joiner_registry.gd")
|
||||
const TextRenderLayer = preload("res://addons/godot_xterm/renderer/text_render_layer.gd")
|
||||
|
||||
signal redraw_requested
|
||||
signal options_changed
|
||||
signal grid_changed(start, end)
|
||||
|
||||
var _id: int
|
||||
var _render_layers: Array
|
||||
var _device_pixel_ratio: float
|
||||
var _character_joiner_registry
|
||||
var _colors
|
||||
var _container
|
||||
var _buffer_service
|
||||
var _options_service
|
||||
var _char_size_service
|
||||
|
||||
var dimensions
|
||||
|
||||
|
||||
func _init(colors, container: Node, buffer_service, options_service):
|
||||
_id = get_instance_id()
|
||||
_colors = colors
|
||||
_container = container
|
||||
_buffer_service = buffer_service
|
||||
_options_service = options_service
|
||||
|
||||
var allow_transparency = _options_service.options.allow_transparency
|
||||
_character_joiner_registry = CharacterJoinerRegistry.new(_buffer_service)
|
||||
|
||||
_render_layers = [
|
||||
TextRenderLayer.new(_container, 0, _colors, _character_joiner_registry,
|
||||
allow_transparency, _id, _buffer_service, _options_service)
|
||||
]
|
||||
|
||||
# Connect render layers to our signals.
|
||||
for layer in _render_layers:
|
||||
self.connect("options_changed", layer, "on_options_changed")
|
||||
self.connect("grid_changed", layer, "on_grid_changed")
|
||||
|
||||
dimensions = {
|
||||
"scaled_char_width": 0,
|
||||
"scaled_char_height": 0,
|
||||
"scaled_cell_width": 0,
|
||||
"scaled_cell_height": 0,
|
||||
"scaled_char_left": 0,
|
||||
"scaled_char_top": 0,
|
||||
"scaled_canvas_width": 0,
|
||||
"scaled_canvas_height": 0,
|
||||
"canvas_width": 0,
|
||||
"canvas_height": 0,
|
||||
"actual_cell_width": 0,
|
||||
"actual_cell_height": 0,
|
||||
}
|
||||
_device_pixel_ratio = OS.get_screen_dpi()
|
||||
_update_dimensions()
|
||||
emit_signal("options_changed")
|
||||
|
||||
|
||||
func on_resize(cols, rows):
|
||||
# Update character and canvas dimensions
|
||||
_update_dimensions()
|
||||
|
||||
# Resize all render layers
|
||||
for layer in _render_layers:
|
||||
layer.resize(dimensions)
|
||||
|
||||
|
||||
func refresh_rows(start: int, end: int) -> void:
|
||||
emit_signal("grid_changed", start, end)
|
||||
|
||||
|
||||
# Recalculates the character and canvas dimensions.
|
||||
func _update_dimensions():
|
||||
var char_width = 0
|
||||
var char_height = 0
|
||||
|
||||
for font in _options_service.options.font_family.values():
|
||||
var size = font.get_string_size("W")
|
||||
char_width = max(char_width, size.x)
|
||||
char_height = max(char_height, size.y)
|
||||
|
||||
dimensions.scaled_char_width = char_width
|
||||
dimensions.scaled_char_height = char_height
|
||||
|
||||
# Calculate the scaled cell height, if line_height is not 1 then the value
|
||||
# will be floored because since line_height can never be lower then 1, there
|
||||
# is a guarantee that the scaled line height will always be larger than
|
||||
# scaled char height.
|
||||
dimensions.scaled_cell_height = floor(dimensions.scaled_char_height * _options_service.options.line_height)
|
||||
|
||||
# Calculate the y coordinate within a cell that text should draw from in
|
||||
# order to draw in the center of a cell.
|
||||
dimensions.scaled_char_top = 0 if _options_service.options.line_height == 1 else \
|
||||
round((dimensions.scaled_cell_height - dimensions.scaled_char_height) / 2)
|
||||
|
||||
# Calculate the scaled cell width, taking the letter_spacing into account.
|
||||
dimensions.scaled_cell_width = dimensions.scaled_char_width + round(_options_service.options.letter_spacing)
|
||||
|
||||
# Calculate the x coordinate with a cell that text should draw from in
|
||||
# order to draw in the center of a cell.
|
||||
dimensions.scaled_char_left = floor(_options_service.options.letter_spacing / 2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
191
addons/godot_xterm/renderer/text_render_layer.gd
Normal file
191
addons/godot_xterm/renderer/text_render_layer.gd
Normal file
|
@ -0,0 +1,191 @@
|
|||
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends "res://addons/godot_xterm/renderer/base_render_layer.gd"
|
||||
|
||||
|
||||
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
|
||||
const CharacterJoinerRegistry = preload("res://addons/godot_xterm/renderer/character_joiner_registry.gd")
|
||||
const JoinedCellData = CharacterJoinerRegistry.JoinedCellData
|
||||
const Content = Constants.Content
|
||||
|
||||
var _state
|
||||
var _character_width: int = 0
|
||||
var _character_font: DynamicFont = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
|
||||
var _character_overlap_cache: Dictionary = {}
|
||||
var _character_joiner_registry
|
||||
var _work_cell = CellData.new()
|
||||
|
||||
|
||||
func _init(container: Node, z_index: int, colors, character_joiner_registry,
|
||||
alpha: bool, renderer_id: int, buffer_service, options_service).(container,
|
||||
'text', z_index, alpha, colors, renderer_id, buffer_service, options_service):
|
||||
_state = null #TODO what?
|
||||
_character_joiner_registry = character_joiner_registry
|
||||
|
||||
|
||||
func on_grid_changed(first_row: int, last_row: int) -> void:
|
||||
_clear_cells(0, first_row, _buffer_service.cols, last_row - first_row + 1)
|
||||
_draw_background(first_row, last_row)
|
||||
_draw_foreground(first_row, last_row)
|
||||
|
||||
# Finally draw everything that has been queued in the draw buffer.
|
||||
_ctx.update()
|
||||
|
||||
|
||||
func on_options_changed() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _cells(first_row: int, last_row: int, joiner_registry = null) -> Array:
|
||||
var cells = []
|
||||
|
||||
for y in range(first_row, last_row + 1):
|
||||
var row = y + _buffer_service.buffer.ydisp
|
||||
var line = _buffer_service.buffer.lines.get_el(row)
|
||||
var joined_ranges = joiner_registry.get_joined_characters(row) if joiner_registry else []
|
||||
for x in range(_buffer_service.cols):
|
||||
line.load_cell(x, _work_cell)
|
||||
var cell = _work_cell
|
||||
|
||||
# If true, indicates that the current character(s) to draw were joined.
|
||||
var is_joined = false
|
||||
var last_char_x = x
|
||||
|
||||
# The character to the left is a wide character, drawing is owned by
|
||||
# the char at x-1
|
||||
if cell.get_width() == 0:
|
||||
continue
|
||||
|
||||
# Process any joined character range as needed. Because of how the
|
||||
# ranges are produced, we know that they are valid for the characters
|
||||
# and attributes of our input.
|
||||
if not joined_ranges.empty() and x == joined_ranges[0][0]:
|
||||
is_joined = true
|
||||
var r = joined_ranges.pop_front()
|
||||
|
||||
# We already know the exact start and end column of the joined
|
||||
# range, so we get the string and width representing it directly
|
||||
|
||||
cell = JoinedCellData.new(_work_cell,
|
||||
line.trans_late_to_string(true, r[0], r[1]), r[1] - r[0])
|
||||
|
||||
# Skip over the cells occupied by this range in the loop
|
||||
last_char_x = r[1] - 1
|
||||
|
||||
# If the character is an overlapping char and the character to the
|
||||
# right is a space, take ownership of the cell to the right. We skip
|
||||
# this check for joined characters because their rendering likely won't
|
||||
# yield the same result as rendering the last character individually.
|
||||
if not is_joined and _is_overlapping(cell):
|
||||
if last_char_x < line.length - 1 and line.get_codepoint(last_char_x + 1) == Constants.NULL_CELL_CODE:
|
||||
# patch width to 2
|
||||
cell.content &= ~Content.WIDTH_MASK
|
||||
cell.content |= 2 << Content.WIDTH_SHIFT
|
||||
|
||||
# Append a new instance of cell, as we wil reuse the current instance.
|
||||
cells.append({"cell": CellData.from_char_data(cell.get_as_char_data()),
|
||||
"x": x, "y": y})
|
||||
|
||||
x = last_char_x
|
||||
|
||||
return cells
|
||||
|
||||
|
||||
func _draw_background(first_row: int, last_row: int) -> void:
|
||||
var ctx = _ctx
|
||||
var cols = _buffer_service.cols
|
||||
var start_x = 0
|
||||
var start_y = 0
|
||||
var prev_fill_style = null
|
||||
|
||||
ctx.save()
|
||||
|
||||
for c in _cells(first_row, last_row, null):
|
||||
var cell = c.cell
|
||||
var x = c.x
|
||||
var y = c.y
|
||||
|
||||
# libvte and xterm draw the background (but not foreground) of invisible characters,
|
||||
# so we should too.
|
||||
var next_fill_style = null # null represents the default background color
|
||||
|
||||
if cell.is_inverse():
|
||||
if cell.is_fg_default():
|
||||
next_fill_style = _colors.foreground
|
||||
elif cell.is_fg_rgb():
|
||||
next_fill_style = cell.get_fg_color() # TODO: Figure out how to convert this to Color()
|
||||
else:
|
||||
next_fill_style = _colors.ansi[cell.get_fg_color()]
|
||||
elif cell.is_bg_rgb():
|
||||
next_fill_style = cell.get_bg_color() # TODO: Figure out how to convert this to Color()
|
||||
elif cell.is_bg_palette():
|
||||
next_fill_style = _colors.ansi[cell.get_bg_color()]
|
||||
|
||||
if prev_fill_style == null:
|
||||
# This is either the first iteration, or the default background was set. Either way, we
|
||||
# don't need to draw anything.
|
||||
start_x = x
|
||||
start_y = y
|
||||
|
||||
if y != start_y:
|
||||
# our row changed, draw the previous row
|
||||
ctx.fill_style = prev_fill_style if prev_fill_style else Color()
|
||||
_fill_cells(start_x, start_y, cols - start_x, 1)
|
||||
start_x = x
|
||||
start_y = y
|
||||
elif prev_fill_style != next_fill_style:
|
||||
# our color changed, draw the previous characters in this row
|
||||
ctx.fill_style = prev_fill_style if prev_fill_style else Color()
|
||||
start_x = x
|
||||
start_y = y
|
||||
|
||||
prev_fill_style = next_fill_style
|
||||
|
||||
# flush the last color we encountered
|
||||
if prev_fill_style != null:
|
||||
ctx.fill_style = prev_fill_style
|
||||
_fill_cells(start_x, start_y, cols - start_x, 1)
|
||||
|
||||
ctx.restore()
|
||||
|
||||
|
||||
func _draw_foreground(first_row: int, last_row: int) -> void:
|
||||
for c in _cells(first_row, last_row, _character_joiner_registry):
|
||||
var cell = c.cell
|
||||
var x = c.x
|
||||
var y = c.y
|
||||
|
||||
if cell.is_invisible():
|
||||
return
|
||||
|
||||
_draw_chars(cell, x, y)
|
||||
|
||||
|
||||
func _is_overlapping(cell) -> bool:
|
||||
# Only single cell characters can be overlapping, rendering issues can
|
||||
# occur without this check
|
||||
if cell.get_width() != 1:
|
||||
return false
|
||||
|
||||
var chars = cell.get_chars()
|
||||
|
||||
# Deliver from cache if available
|
||||
if _character_overlap_cache.has(chars):
|
||||
return _character_overlap_cache[chars]
|
||||
|
||||
# Setup the font
|
||||
_ctx.save()
|
||||
_ctx.font = _character_font
|
||||
|
||||
# Measure the width of the character, but floor it
|
||||
# because that is what the renderer does when it calculates
|
||||
# the character dimensions wer are comparing against
|
||||
var overlaps = floor(_ctx.measure_text(chars).width) > _character_width
|
||||
|
||||
# Restore the original context
|
||||
_ctx.restore()
|
||||
|
||||
# Cache and return
|
||||
_character_overlap_cache[chars] = overlaps
|
||||
return overlaps
|
39
addons/godot_xterm/services/buffer_service.gd
Normal file
39
addons/godot_xterm/services/buffer_service.gd
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
signal buffer_activated(active_buffer, inactive_buffer)
|
||||
signal resized
|
||||
|
||||
const BufferSet = preload("res://addons/godot_xterm/buffer/buffer_set.gd")
|
||||
|
||||
const MINIMUM_COLS = 2 # Less than 2 can mess with wide chars
|
||||
const MINIMUM_ROWS = 1
|
||||
|
||||
var service_brand
|
||||
|
||||
var cols: int
|
||||
var rows: int
|
||||
var buffers
|
||||
# Whether the user is scrolling (locks the scroll position)
|
||||
var is_user_scrolling: bool = false
|
||||
var _options_service
|
||||
|
||||
var buffer setget ,_get_buffer
|
||||
|
||||
|
||||
func _get_buffer():
|
||||
return buffers.active if buffers else null
|
||||
|
||||
|
||||
func _init(options_service):
|
||||
_options_service = options_service
|
||||
cols = max(_options_service.options.cols, MINIMUM_COLS)
|
||||
rows = max(_options_service.options.rows, MINIMUM_ROWS)
|
||||
buffers = BufferSet.new(_options_service, self)
|
||||
buffers.connect("buffer_activated", self, "_buffer_activated")
|
||||
|
||||
|
||||
func _buffer_activated(active_buffer, inactive_buffer):
|
||||
emit_signal("buffer_activated", active_buffer, inactive_buffer)
|
30
addons/godot_xterm/services/charset_service.gd
Normal file
30
addons/godot_xterm/services/charset_service.gd
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
var service_brand
|
||||
var charset = null
|
||||
var glevel: int = 0
|
||||
|
||||
var _charsets = []
|
||||
|
||||
|
||||
func reset() -> void:
|
||||
charset = null
|
||||
_charsets = []
|
||||
glevel = 0
|
||||
|
||||
|
||||
func set_glevel(g: int) -> void:
|
||||
glevel = g
|
||||
charset = _charsets[g]
|
||||
|
||||
|
||||
func set_gcharset(g: int, charset = null) -> void:
|
||||
if _charsets.size() < g + 1:
|
||||
_charsets.resize(g + 1)
|
||||
_charsets[g] = charset
|
||||
if glevel == g:
|
||||
charset = charset
|
27
addons/godot_xterm/services/core_service.gd
Normal file
27
addons/godot_xterm/services/core_service.gd
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
var DEFAULT_MODES = {
|
||||
"insert_mode": false,
|
||||
}
|
||||
|
||||
var DEFAULT_DEC_PRIVATE_MODES = {
|
||||
"application_cursor_keys": false,
|
||||
"application_keypad": false,
|
||||
"bracketed_paste_mode": false,
|
||||
"origin": false,
|
||||
"reverse_wraparound": false, # defaults: xterm -true, vt100 - false
|
||||
}
|
||||
|
||||
var modes = DEFAULT_MODES.duplicate()
|
||||
var dec_private_modes = DEFAULT_DEC_PRIVATE_MODES.duplicate()
|
||||
var is_cursor_hidden = false
|
||||
var is_cursor_initialized = true
|
||||
|
||||
|
||||
func reset():
|
||||
modes = DEFAULT_MODES.duplicate()
|
||||
dec_private_modes.duplicate()
|
53
addons/godot_xterm/services/options_service.gd
Normal file
53
addons/godot_xterm/services/options_service.gd
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
class TerminalOptions:
|
||||
var cols: int
|
||||
var rows: int
|
||||
var cursor_blink: bool
|
||||
var cursor_style
|
||||
var cursor_width: int
|
||||
var bell_sound
|
||||
var bell_style
|
||||
var draw_bold_text_in_bright_colors: bool
|
||||
var fast_scroll_modifier
|
||||
var fast_scroll_sensitivity: int
|
||||
var font_family: Dictionary
|
||||
var font_size: int
|
||||
var font_weight: String
|
||||
var font_weight_bold: String
|
||||
var line_height: float
|
||||
var link_tooltip_hover_duration: int
|
||||
var letter_spacing: float
|
||||
var log_level
|
||||
var scrollback: int
|
||||
var scroll_sensitivity: int
|
||||
var screen_reader_mode: bool
|
||||
var mac_option_is_meta: bool
|
||||
var mac_option_click_forces_selection: bool
|
||||
var minimum_contrast_ratio: float
|
||||
var disable_stdin: bool
|
||||
var allow_proposed_api: bool
|
||||
var allow_transparency: bool
|
||||
var tab_stop_width: int
|
||||
var colors: Dictionary
|
||||
var right_click_selects_word
|
||||
var renderer_type
|
||||
var window_options: Dictionary
|
||||
var windows_mode: bool
|
||||
var word_separator: String
|
||||
var convert_eol: bool
|
||||
var term_name: String
|
||||
var cancel_events: bool
|
||||
|
||||
|
||||
signal option_changed
|
||||
|
||||
var options
|
||||
|
||||
|
||||
func _init(options):
|
||||
self.options = options
|
|
@ -1,16 +1,42 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
# Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.
|
||||
# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
# Ported to GDScript by the GodotXterm authors.
|
||||
# Licese MIT
|
||||
#
|
||||
# Originally forked from (with the author's permission):
|
||||
# Fabrice Bellard's javascript vt100 for jslinux:
|
||||
# http://bellard.org/jslinux/
|
||||
# Copyright (c) 2011 Fabrice Bellard
|
||||
# The original design remains. The terminal itself
|
||||
# has been extended to include xterm CSI codes, among
|
||||
# other features.
|
||||
#
|
||||
# Terminal Emulation References:
|
||||
# http://vt100.net/
|
||||
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
|
||||
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
# http://invisible-island.net/vttest/
|
||||
# http://www.inwap.com/pdp10/ansicode.txt
|
||||
# http://linux.die.net/man/4/console_codes
|
||||
# http://linux.die.net/man/7/urxvt
|
||||
tool
|
||||
extends Control
|
||||
|
||||
|
||||
signal data_sent(data)
|
||||
|
||||
const BufferService = preload("res://addons/godot_xterm/services/buffer_service.gd")
|
||||
const CoreService = preload("res://addons/godot_xterm/services/core_service.gd")
|
||||
const OptionsService = preload("res://addons/godot_xterm/services/options_service.gd")
|
||||
const CharsetService = preload("res://addons/godot_xterm/services/charset_service.gd")
|
||||
const InputHandler = preload("res://addons/godot_xterm/input_handler.gd")
|
||||
const Const = preload("res://addons/godot_xterm/Constants.gd")
|
||||
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
|
||||
const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd")
|
||||
const Buffer = preload("res://addons/godot_xterm/buffer.gd")
|
||||
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
|
||||
const Renderer = preload("res://addons/godot_xterm/renderer/renderer.gd")
|
||||
const ColorManager = preload("res://addons/godot_xterm/color_manager.gd")
|
||||
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
|
||||
|
||||
const SourceCodeProRegular = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
|
||||
const SourceCodeProBold = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres")
|
||||
const SourceCodeProItalic = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres")
|
||||
|
@ -26,342 +52,200 @@ const LEFT_BRACKET = 91
|
|||
const ENTER = 10
|
||||
const BACKSPACE_ALT = 127
|
||||
|
||||
export (Font) var normal_font = SourceCodeProRegular setget _set_normal_font
|
||||
export (Font) var bold_font = SourceCodeProBold setget _set_bold_font
|
||||
export (Font) var italic_font = SourceCodeProItalic setget _set_italics_font
|
||||
export (Font) var bold_italic_font = SourceCodeProBoldItalic setget _set_bold_italics_font
|
||||
var buffer
|
||||
var alternate_buffer
|
||||
var parser
|
||||
# TODO: Move me somewhere else.
|
||||
enum BellStyle {
|
||||
NONE
|
||||
}
|
||||
|
||||
signal output(data)
|
||||
|
||||
export var cols = 80
|
||||
export var rows = 24
|
||||
export var cursor_blink = false
|
||||
export var cursor_style = 'block'
|
||||
export var cursor_width = 1
|
||||
export var bell_sound: AudioStream = null # TODO Bell sound
|
||||
export(BellStyle) var bell_style = BellStyle.NONE
|
||||
export var draw_bold_text_in_bright_colors = true
|
||||
export var fast_scroll_modifier = 'alt' # TODO Use scancode?
|
||||
export var fast_scroll_sensitivity = 5
|
||||
export var font_family: Dictionary = {
|
||||
"regular": SourceCodeProRegular,
|
||||
"bold": SourceCodeProBold,
|
||||
"italic": SourceCodeProItalic,
|
||||
"bold_italic": SourceCodeProBoldItalic,
|
||||
}
|
||||
export var font_size: int = 15
|
||||
export var font_weight = 'normal' # Enum?
|
||||
export var font_weight_bold = 'bold' # Enum?
|
||||
export var line_height = 1.0
|
||||
export var link_tooltip_hover_duration = 500 # Not relevant?
|
||||
export var letter_spacing = 0
|
||||
export var log_level = 'info' # Not relevant?
|
||||
export var scrollback = 1000
|
||||
export var scroll_sensitivity = 1
|
||||
export var screen_reader_mode: bool = false
|
||||
export var mac_option_is_meta = false
|
||||
export var mac_option_click_forces_selection = false
|
||||
export var minimum_contrast_ratio = 1
|
||||
export var disable_stdin = false
|
||||
export var allow_proposed_api = true
|
||||
export var allow_transparency = false
|
||||
export var tab_stop_width = 8
|
||||
export var colors: Dictionary = {
|
||||
'black': Color(0, 0, 0)
|
||||
}
|
||||
export var right_click_selects_word = 'isMac' # TODO
|
||||
export var renderer_type = 'canvas' # Relevant?
|
||||
export var window_options = {
|
||||
'set_win_lines': false
|
||||
}
|
||||
export var windows_mode = false
|
||||
export var word_separator = " ()[]{}',\"`"
|
||||
export var convert_eol = true
|
||||
export var term_name = 'xterm'
|
||||
export var cancel_events = false
|
||||
|
||||
var options_service
|
||||
var decoder
|
||||
var cols = 80
|
||||
var rows = 24
|
||||
var cell: Vector2
|
||||
|
||||
# font flags
|
||||
export(int, FLAGS,
|
||||
"Bold",
|
||||
"Italic", # Not xterm-256color
|
||||
"Underlined",
|
||||
"Blink",
|
||||
"Inverse",
|
||||
"Invisible",
|
||||
"Strikethrough" # Not xterm-256color
|
||||
) var font_flags = Const.FONT_NORMAL
|
||||
|
||||
|
||||
func _init():
|
||||
pass
|
||||
|
||||
|
||||
func _set_normal_font(font: Font) -> void:
|
||||
normal_font = font
|
||||
_calculate_cell_size()
|
||||
|
||||
|
||||
func _set_bold_font(font: Font) -> void:
|
||||
bold_font = font
|
||||
_calculate_cell_size()
|
||||
|
||||
|
||||
func _set_italics_font(font: Font) -> void:
|
||||
italic_font = font
|
||||
_calculate_cell_size()
|
||||
|
||||
|
||||
func _set_bold_italics_font(font: Font) -> void:
|
||||
bold_italic_font = font
|
||||
_calculate_cell_size()
|
||||
|
||||
|
||||
func _calculate_cell_size() -> void:
|
||||
var x = 0.0
|
||||
var y = 0.0
|
||||
var fonts = [normal_font, bold_font, italic_font, bold_italic_font]
|
||||
for font in fonts:
|
||||
if not font:
|
||||
continue
|
||||
var size = font.get_string_size("W")
|
||||
x = max(x, size.x)
|
||||
y = max(y, size.y)
|
||||
cell.x = x
|
||||
cell.y = y
|
||||
|
||||
var parser
|
||||
var _buffer_service
|
||||
var _core_service
|
||||
var _charset_service
|
||||
var _input_handler
|
||||
var _render_service
|
||||
var _color_manager
|
||||
var _scaled_char_width
|
||||
var _scaled_char_height
|
||||
var _scaled_cell_width
|
||||
var _scaled_cell_height
|
||||
var _scaled_char_top
|
||||
var _scaled_char_left
|
||||
var _work_cell = CellData.new()
|
||||
|
||||
func _ready():
|
||||
_calculate_cell_size()
|
||||
var rect = get_rect()
|
||||
var rs = rect_size
|
||||
cols = (rect_size.x / cell.x) as int
|
||||
rows = (rect_size.y / cell.y) as int
|
||||
var options = OptionsService.TerminalOptions.new()
|
||||
options.cols = cols
|
||||
options.rows = rows
|
||||
options.font_family = font_family
|
||||
options.line_height = line_height
|
||||
options.screen_reader_mode = screen_reader_mode
|
||||
options.window_options = window_options
|
||||
options.convert_eol = convert_eol
|
||||
|
||||
decoder = Decoder.Utf8ToUtf32.new()
|
||||
options_service = OptionsService.new(options)
|
||||
options_service.connect("option_changed", self, "_update_options")
|
||||
|
||||
buffer = Buffer.new(rows, cols)
|
||||
alternate_buffer = Buffer.new(rows, cols, true)
|
||||
_buffer_service = BufferService.new(options_service)
|
||||
_core_service = CoreService.new()
|
||||
_charset_service = CharsetService.new()
|
||||
|
||||
parser = Parser.new()
|
||||
|
||||
# Print handler
|
||||
parser.set_print_handler(buffer, "insert_at_cursor")
|
||||
# Register input handler and connect signals.
|
||||
_input_handler = InputHandler.new(_buffer_service, _core_service, _charset_service, options_service)
|
||||
_input_handler.connect("bell_requested", self, "bell")
|
||||
_input_handler.connect("refresh_rows_requested", self, "_refresh_rows")
|
||||
_input_handler.connect("reset_requested", self, "reset")
|
||||
_input_handler.connect("scroll_requested", self, "scroll")
|
||||
_input_handler.connect("windows_options_report_requested", self, "report_windows_options")
|
||||
|
||||
# Execute handlers
|
||||
parser.set_execute_handler(C0.BEL, self, 'bell')
|
||||
parser.set_execute_handler(C0.LF, buffer, 'line_feed')
|
||||
parser.set_execute_handler(C0.VT, buffer, 'line_feed')
|
||||
parser.set_execute_handler(C0.FF, buffer, 'line_feed')
|
||||
parser.set_execute_handler(C0.CR, buffer, 'carriage_return')
|
||||
parser.set_execute_handler(C0.BS, buffer, 'backspace')
|
||||
parser.set_execute_handler(C0.HT, buffer, 'insert_tab');
|
||||
parser.set_execute_handler(C0.SO, self, 'shift_out')
|
||||
parser.set_execute_handler(C0.SI, self, 'shift_in')
|
||||
parser.set_execute_handler(C1.IND, self, 'index')
|
||||
parser.set_execute_handler(C1.NEL, self, 'next_line')
|
||||
parser.set_execute_handler(C1.HTS, self, 'tab_set')
|
||||
_color_manager = ColorManager.new()
|
||||
_color_manager.set_theme(colors)
|
||||
_render_service = Renderer.new(_color_manager.colors, self, _buffer_service, options_service)
|
||||
|
||||
connect("resized", self, "_update_dimensions")
|
||||
_update_dimensions()
|
||||
|
||||
# CSI handlers
|
||||
parser.set_csi_handler({'final': '@'}, self, 'insert_chars')
|
||||
parser.set_csi_handler({'intermediates': ' ', 'final': '@'}, self, 'scroll_left')
|
||||
parser.set_csi_handler({'final': 'A'}, self, 'cursor_up')
|
||||
parser.set_csi_handler({'intermediates': ' ', 'final': 'A'}, self, 'scroll_right')
|
||||
parser.set_csi_handler({'final': 'B'}, self, 'cursor_down')
|
||||
parser.set_csi_handler({'final': 'C'}, self, 'cursor_forward')
|
||||
parser.set_csi_handler({'final': 'D'}, self, 'cursor_backward')
|
||||
parser.set_csi_handler({'final': 'E'}, self, 'cursor_nextLine')
|
||||
parser.set_csi_handler({'final': 'F'}, self, 'cursor_precedingLine')
|
||||
parser.set_csi_handler({'final': 'G'}, self, 'cursor_charAbsolute')
|
||||
parser.set_csi_handler({'final': 'H'}, buffer, 'cursor_position')
|
||||
parser.set_csi_handler({'final': 'I'}, self, 'cursor_forward_tab')
|
||||
parser.set_csi_handler({'final': 'J'}, self, 'erase_in_display')
|
||||
parser.set_csi_handler({'prefix': '?', 'final': 'J'}, self, 'erase_in_display')
|
||||
parser.set_csi_handler({'final': 'K'}, self, 'erase_in_line')
|
||||
parser.set_csi_handler({'prefix': '?', 'final': 'K'}, self, 'erase_in_line')
|
||||
parser.set_csi_handler({'final': 'L'}, self, 'insert_lines')
|
||||
parser.set_csi_handler({'final': 'M'}, self, 'delete_lines')
|
||||
parser.set_csi_handler({'final': 'P'}, self, 'delete_chars')
|
||||
parser.set_csi_handler({'final': 'S'}, self, 'scroll_up')
|
||||
parser.set_csi_handler({'final': 'T'}, self, 'scroll_down')
|
||||
parser.set_csi_handler({'final': 'X'}, self, 'erase_chars')
|
||||
parser.set_csi_handler({'final': 'Z'}, self, 'cursor_backward_tab')
|
||||
parser.set_csi_handler({'final': '`'}, self, 'char_pos_absolute')
|
||||
parser.set_csi_handler({'final': 'a'}, self, 'h_position_relative')
|
||||
parser.set_csi_handler({'final': 'b'}, self, 'repeat_preceding_character')
|
||||
parser.set_csi_handler({'final': 'c'}, self, 'send_device_attributes_primary')
|
||||
parser.set_csi_handler({'prefix': '>', 'final': 'c'}, self, 'send_device_attributes_secondary')
|
||||
parser.set_csi_handler({'final': 'd'}, self, 'line_pos_absolute')
|
||||
parser.set_csi_handler({'final': 'e'}, self, 'v_position_relative')
|
||||
parser.set_csi_handler({'final': 'f'}, self, 'h_v_position')
|
||||
parser.set_csi_handler({'final': 'g'}, self, 'tab_clear')
|
||||
parser.set_csi_handler({'final': 'h'}, self, 'set_mode')
|
||||
parser.set_csi_handler({'prefix': '?', 'final': 'h'}, self, 'set_mode_private')
|
||||
parser.set_csi_handler({'final': 'l'}, self, 'reset_mode')
|
||||
parser.set_csi_handler({'prefix': '?', 'final': 'l'}, self, 'reset_mode_private')
|
||||
parser.set_csi_handler({'final': 'm'}, self, 'char_attributes')
|
||||
parser.set_csi_handler({'final': 'n'}, self, 'device_status')
|
||||
parser.set_csi_handler({'prefix': '?', 'final': 'n'}, self, 'device_status_private')
|
||||
parser.set_csi_handler({'intermediates': '!', 'final': 'p'}, self, 'soft_reset')
|
||||
parser.set_csi_handler({'intermediates': ' ', 'final': 'q'}, self, 'set_cursor_style')
|
||||
parser.set_csi_handler({'final': 'r'}, self, 'set_scroll_region')
|
||||
parser.set_csi_handler({'final': 's'}, self, 'save_cursor')
|
||||
parser.set_csi_handler({'final': 't'}, self, 'window_options')
|
||||
parser.set_csi_handler({'final': 'u'}, self, 'restore_cursor')
|
||||
parser.set_csi_handler({'intermediates': '\'', 'final': '}'}, self, 'insert_columns')
|
||||
parser.set_csi_handler({'intermediates': '\'', 'final': '~'}, self, 'delete_columns')
|
||||
|
||||
func print(data, start, end):
|
||||
print(data.substr(start, end))
|
||||
|
||||
func bell():
|
||||
print("The bell signal was emited!")
|
||||
func _refresh_rows(start_row = 0, end_row = 0):
|
||||
# Not optimized, just draw
|
||||
update()
|
||||
|
||||
func line_feed():
|
||||
pass
|
||||
|
||||
func carriage_return():
|
||||
print("carriage return!")
|
||||
|
||||
func backspace():
|
||||
print("backspace!")
|
||||
pass
|
||||
|
||||
func tab():
|
||||
pass
|
||||
|
||||
func shift_out():
|
||||
pass
|
||||
|
||||
func shift_in():
|
||||
pass
|
||||
|
||||
func index():
|
||||
pass
|
||||
|
||||
func next_line():
|
||||
pass
|
||||
|
||||
func tab_set():
|
||||
pass
|
||||
|
||||
func insert_chars(params):
|
||||
pass
|
||||
|
||||
func scroll_left(params):
|
||||
pass
|
||||
func cursor_up(params):
|
||||
pass
|
||||
func scroll_right(params):
|
||||
pass
|
||||
func cursor_down(params):
|
||||
pass
|
||||
func cursor_forward(params):
|
||||
pass
|
||||
func cursor_backward(params):
|
||||
pass
|
||||
func cursor_next_line(params):
|
||||
pass
|
||||
func cursor_preceding_line(params):
|
||||
pass
|
||||
func cursor_char_absolute(params):
|
||||
pass
|
||||
func cursor_position(params):
|
||||
pass
|
||||
func cursor_forward_tab(params):
|
||||
pass
|
||||
func erase_in_display(params):
|
||||
pass
|
||||
func erase_in_line(params):
|
||||
pass
|
||||
func insert_lines(params):
|
||||
pass
|
||||
func delete_lines(params):
|
||||
pass
|
||||
func delete_chars(params):
|
||||
pass
|
||||
func scroll_up(params):
|
||||
pass
|
||||
func scroll_down(params):
|
||||
pass
|
||||
func erase_chars(params):
|
||||
pass
|
||||
func cursor_backward_tab(params):
|
||||
pass
|
||||
func char_pos_absolute(params):
|
||||
pass
|
||||
func h_position_relative(params):
|
||||
pass
|
||||
func repeat_preceding_character(params):
|
||||
pass
|
||||
func send_device_attributes_primary(params):
|
||||
pass
|
||||
func send_device_attributes_secondary(params):
|
||||
pass
|
||||
func line_pos_absolute(params):
|
||||
pass
|
||||
func v_position_relative(params):
|
||||
pass
|
||||
func h_v_position(params):
|
||||
pass
|
||||
func tab_clear(params):
|
||||
pass
|
||||
func set_mode(params):
|
||||
pass
|
||||
func set_mode_private(params):
|
||||
pass
|
||||
func reset_mode(params):
|
||||
pass
|
||||
func char_attributes(params):
|
||||
pass
|
||||
func device_status(params):
|
||||
pass
|
||||
func device_status_private(params):
|
||||
pass
|
||||
func soft_reset(params):
|
||||
pass
|
||||
func set_cursor_style(params):
|
||||
pass
|
||||
func set_scroll_region(params):
|
||||
pass
|
||||
func save_cursor(params):
|
||||
pass
|
||||
func window_options(params):
|
||||
pass
|
||||
func restore_cursor(params):
|
||||
pass
|
||||
func insert_columns(params):
|
||||
pass
|
||||
func delete_columns(params):
|
||||
pass
|
||||
|
||||
func _input(event):
|
||||
if event is InputEventKey and event.pressed:
|
||||
var data = PoolByteArray([])
|
||||
accept_event()
|
||||
|
||||
# TODO: Handle more of these.
|
||||
if (event.control and event.scancode == KEY_C):
|
||||
send_data(PoolByteArray([3]))
|
||||
data.append(3)
|
||||
elif event.unicode:
|
||||
send_data(PoolByteArray([event.unicode]))
|
||||
data.append(event.unicode)
|
||||
elif event.scancode == KEY_ENTER:
|
||||
send_data(PoolByteArray([ENTER]))
|
||||
data.append(ENTER)
|
||||
elif event.scancode == KEY_BACKSPACE:
|
||||
send_data(PoolByteArray([BACKSPACE_ALT]))
|
||||
data.append(BACKSPACE_ALT)
|
||||
elif event.scancode == KEY_ESCAPE:
|
||||
send_data(PoolByteArray([27]))
|
||||
data.append(27)
|
||||
elif event.scancode == KEY_TAB:
|
||||
send_data(PoolByteArray([9]))
|
||||
data.append(9)
|
||||
elif OS.get_scancode_string(event.scancode) == "Shift":
|
||||
pass
|
||||
elif OS.get_scancode_string(event.scancode) == "Control":
|
||||
pass
|
||||
else:
|
||||
push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
|
||||
pass
|
||||
#push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
|
||||
emit_signal("output", data)
|
||||
|
||||
|
||||
func send_data(data: PoolByteArray):
|
||||
emit_signal("data_sent", data)
|
||||
func write(data, callback_target = null, callback_method: String = ''):
|
||||
_input_handler.parse(data)
|
||||
if callback_target and callback_method:
|
||||
callback_target.call(callback_method)
|
||||
|
||||
|
||||
func refresh(start = null, end = null) -> void:
|
||||
pass
|
||||
|
||||
|
||||
# Recalculates the character and canvas dimensions.
|
||||
func _update_dimensions():
|
||||
var char_width = 0
|
||||
var char_height = 0
|
||||
|
||||
for font in options_service.options.font_family.values():
|
||||
var size = font.get_string_size("W")
|
||||
char_width = max(char_width, size.x)
|
||||
char_height = max(char_height, size.y)
|
||||
|
||||
_scaled_char_width = char_width
|
||||
_scaled_char_height = char_height
|
||||
|
||||
# Calculate the scaled cell height, if line_height is not 1 then the value
|
||||
# will be floored because since line_height can never be lower then 1, there
|
||||
# is a guarantee that the scaled line height will always be larger than
|
||||
# scaled char height.
|
||||
_scaled_cell_height = floor(_scaled_char_height * options_service.options.line_height)
|
||||
|
||||
# Calculate the y coordinate within a cell that text should draw from in
|
||||
# order to draw in the center of a cell.
|
||||
_scaled_char_top = 0 if options_service.options.line_height == 1 else \
|
||||
round((_scaled_cell_height - _scaled_char_height) / 2)
|
||||
|
||||
# Calculate the scaled cell width, taking the letter_spacing into account.
|
||||
_scaled_cell_width = _scaled_char_width + round(options_service.options.letter_spacing)
|
||||
|
||||
# Calculate the x coordinate with a cell that text should draw from in
|
||||
# order to draw in the center of a cell.
|
||||
_scaled_char_left = floor(options_service.options.letter_spacing / 2)
|
||||
|
||||
|
||||
func _draw():
|
||||
# Draw the terminal background
|
||||
draw_rect(get_rect(), Color(0.0, 0.5, 0.0))
|
||||
|
||||
# Naive method. Draw the entire buffer starting with row 0.
|
||||
for row in range(buffer.rows.size()):
|
||||
#print("Doing the thing for row: ", row)
|
||||
# Draw each CharacterData.
|
||||
for col in range(buffer.rows[row].size()):
|
||||
var data = buffer.rows[row][col]
|
||||
#print("row: ", ((row + 1) * charHeight), " col: ", (col * charWidth))
|
||||
_draw_character(col, row, data)
|
||||
|
||||
# Draw the cursor.
|
||||
_draw_cursor()
|
||||
|
||||
|
||||
func _draw_character(col, row, data):
|
||||
# Draw the background.
|
||||
draw_rect(Rect2(Vector2(col * cell.x, row * cell.y), Vector2(cell.x, cell.y)), data.bg)
|
||||
|
||||
var font
|
||||
if data.ff & (1 << Const.FONT_BOLD) and data.ff & (1 << Const.FONT_ITALIC):
|
||||
font = bold_italic_font
|
||||
elif data.ff & (1 << Const.FONT_BOLD):
|
||||
font = bold_font
|
||||
elif data.ff & (1 << Const.FONT_ITALIC):
|
||||
font = italic_font
|
||||
else:
|
||||
font = normal_font
|
||||
|
||||
# Draw the character using foreground color.
|
||||
draw_char(font, Vector2(col * cell.x, (row + 1) * cell.y), data.ch, '', data.fg)
|
||||
|
||||
|
||||
func _draw_cursor():
|
||||
draw_rect(Rect2(Vector2(buffer.ccol * cell.x, buffer.crow * cell.y), Vector2(cell.x, cell.y)), Color(1.0, 0.0, 1.0))
|
||||
|
||||
|
||||
func receive_data(data: PoolByteArray):
|
||||
var utf32 = []
|
||||
var length = decoder.decode(data, utf32)
|
||||
parser.parse(utf32, length)
|
||||
update()
|
||||
# Draw the background and foreground
|
||||
var buffer = _buffer_service.buffer
|
||||
for y in range(buffer.ybase, rows):
|
||||
var line = buffer.lines.get_el(y)
|
||||
for x in line.length:
|
||||
line.load_cell(x, _work_cell)
|
||||
draw_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
|
||||
(cols - x) * _scaled_cell_width, 1 * _scaled_cell_height), Color())
|
||||
var color = _color_manager.colors.ansi[_work_cell.get_fg_color()] if _work_cell.get_fg_color() >= 0 else Color(1, 1, 1)
|
||||
draw_char(options_service.options.font_family.regular,
|
||||
Vector2(x * _scaled_cell_width + _scaled_char_left,
|
||||
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2),
|
||||
_work_cell.get_chars() if _work_cell.get_chars() else ' ', "", color)
|
||||
# Draw the cursor
|
||||
# Draw selection
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue