godot-xterm/examples/menu/menu.gd
2024-03-30 22:52:39 +13:00

176 lines
4.6 KiB
GDScript

@tool
extends Control
# This scene demonstrates how we can control the Terminal node directly by
# sending and receiving strings and ANSI escape sequences to the terminal
# directly.
# References:
# - https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
# - https://www.youtube.com/watch?v=jTSQlIK_92w
const TPut = preload("res://addons/godot_xterm/util/tput.gd")
# Title generated using command: toilet -f pagga GODOTXTERM
const TITLE = """
░█▀▀░█▀█░█▀▄░█▀█░▀█▀░█░█░▀█▀░█▀▀░█▀▄░█▄█\r
░█░█░█░█░█░█░█░█░░█░░▄▀▄░░█░░█▀▀░█▀▄░█░█\r
░▀▀▀░▀▀▀░▀▀░░▀▀▀░░▀░░▀░▀░░▀░░▀▀▀░▀░▀░▀░▀\r
"""
const TITLE_WIDTH = 42
var menu_items := [
{"name": "Asciicast", "scene": preload("../asciicast/asciicast.tscn")},
{
"name": "Terminal",
"scene":
(
preload("../web_console/web_console.tscn")
if OS.has_feature("web")
else preload("../terminal/terminal.tscn")
)
},
{"name": "Exit"}
]
var selected_index := 0
var row: int
var menu_start_row: int
var offset: int
@onready var tput = TPut.new($Terminal)
func _ready():
if not $Terminal.is_connected("key_pressed", Callable(self, "_on_Terminal_key_pressed")):
# warning-ignore:return_value_discarded
$Terminal.connect("key_pressed", Callable(self, "_on_Terminal_key_pressed"))
# warning-ignore:return_value_discarded
$Terminal.connect("size_changed", Callable(self, "draw_all"))
$Terminal.grab_focus()
draw_all()
func draw_all(_size = Vector2.ZERO):
offset = int(floor(($Terminal.get_cols() / 2.0) - (TITLE_WIDTH / 2.0)))
tput.reset()
row = 5
tput.civis() # Hide the cursor.
draw_title()
draw_menu()
tput.sgr0()
row += 1
func draw_title():
tput.setaf(tput.ANSIColor.bright_yellow)
tput.cup(row, 0)
for line in TITLE.split("\r"):
row += 1
tput.cup(row, offset)
$Terminal.write(line)
tput.sgr0()
# Get the plugin version from the plugin's config file.
var config = ConfigFile.new()
var err = config.load("res://addons/godot_xterm/plugin.cfg")
if err == OK:
$Terminal.write("\n")
$Terminal.write("Version: %s" % config.get_value("plugin", "version", "unknown"))
row += 2
func draw_menu():
if not menu_start_row:
menu_start_row = row + 1
row = menu_start_row
var col_offset: int
for i in range(menu_items.size()):
row += 1
var item = menu_items[i]
if not col_offset:
col_offset = int(floor(($Terminal.get_cols() / 2) - (item.name.length() / 2)))
tput.cup(row, offset)
if selected_index == i:
tput.setab(tput.ANSIColor.red)
tput.setaf(tput.ANSIColor.black)
$Terminal.write("%s. %s" % [i + 1, item.name])
if selected_index == i:
tput.sgr0()
func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
match data:
TPut.CURSOR_UP: # Up arrow key
selected_index = int(clamp(selected_index - 1, 0, menu_items.size() - 1))
draw_menu()
TPut.CURSOR_DOWN: # Down arrow key
selected_index = int(clamp(selected_index + 1, 0, menu_items.size() - 1))
draw_menu()
"1":
selected_index = 0
draw_menu()
"2":
selected_index = 1
draw_menu()
"3":
selected_index = 2
draw_menu()
# We can also match against the raw InputEventKey.
if event.keycode == KEY_ENTER:
var item = menu_items[selected_index]
match item.name:
"Asciicast":
var scene = item.scene.instantiate()
var animation_player: AnimationPlayer = scene.get_node("AnimationPlayer")
scene.connect(
"key_pressed",
Callable(self, "_on_Asciicast_key_pressed").bind(animation_player)
)
add_child(scene)
scene.grab_focus()
await animation_player.animation_finished
remove_child(scene)
$Terminal.grab_focus()
scene.queue_free()
"Terminal":
if OS.get_name() == "Windows":
OS.call_deferred(
"alert",
(
"Psuedoterminal node currently"
+ " uses pty.h but needs to use either winpty or conpty"
+ " to work on Windows."
),
"Terminal not Supported on Windows"
)
return
var scene = item.scene.instantiate()
var pty = scene if OS.has_feature("web") else scene.get_node("PTY")
add_child(scene)
scene.grab_focus()
await pty.exited
$Terminal.grab_focus()
scene.queue_free()
"Exit":
if OS.has_feature("web"):
JavaScriptBridge.eval("window.history.back() || window.close()")
get_tree().quit()
func _on_Asciicast_key_pressed(
data: String, _event: InputEventKey, animation_player: AnimationPlayer
) -> void:
if data == "\u001b":
animation_player.emit_signal("animation_finished")