Much awesome, very Wow!
* Import native PHP code as callable functions (load) * Include other scripts (include) * Multitude of fixes
This commit is contained in:
parent
a7278bd50a
commit
e23559fd72
16
examples/boxdraw.ftm
Normal file
16
examples/boxdraw.ftm
Normal file
@ -0,0 +1,16 @@
|
||||
set terminal 80x25
|
||||
|
||||
load boxdraw.php
|
||||
|
||||
writefile boxdraw.ftm
|
||||
|
||||
drawhline 5 0 70
|
||||
drawvline 5 0 5
|
||||
|
||||
drawbox 22 3 40 14
|
||||
|
||||
write <sgr fg:white bg:blue>
|
||||
drawfilledbox 37 5 30 8
|
||||
write <sgr>
|
||||
|
||||
savepng boxdraw.png
|
77
examples/boxdraw.php
Normal file
77
examples/boxdraw.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php return [
|
||||
|
||||
/**
|
||||
* Draw a box frame
|
||||
*/
|
||||
'drawbox' => function ($term, $column, $line, $width, $height) {
|
||||
fprintf(STDERR, "drawbox: {%d,%d}+[%d,%d]\n", $column, $line, $width, $height);
|
||||
$oldpos = $term->getCursorPosition();
|
||||
|
||||
$term->setCursor($line, $column);
|
||||
$term->write("\u{250c}" . str_repeat("\u{2500}", $width - 2) . "\u{2510}");
|
||||
|
||||
$term->setCursor($line + $height, $column);
|
||||
$term->write("\u{2514}" . str_repeat("\u{2500}", $width - 2) . "\u{2518}");
|
||||
|
||||
for ($l = $line + 1; $l < $line + $height; $l++) {
|
||||
$term->setCursor($l, $column);
|
||||
$term->write("\u{2502}");
|
||||
$term->setCursor($l, $column + $width - 1);
|
||||
$term->write("\u{2502}");
|
||||
}
|
||||
|
||||
$term->setCursor($oldpos[0], $oldpos[1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a box and fill it
|
||||
*/
|
||||
'drawfilledbox' => function ($term, $column, $line, $width, $height) {
|
||||
fprintf(STDERR, "drawbox: {%d,%d}+[%d,%d]\n", $column, $line, $width, $height);
|
||||
$oldpos = $term->getCursorPosition();
|
||||
|
||||
$term->setCursor($line, $column);
|
||||
$term->write("\u{250c}" . str_repeat("\u{2500}", $width - 2) . "\u{2510}");
|
||||
|
||||
$term->setCursor($line + $height, $column);
|
||||
$term->write("\u{2514}" . str_repeat("\u{2500}", $width - 2) . "\u{2518}");
|
||||
|
||||
for ($l = $line + 1; $l < $line + $height; $l++) {
|
||||
$term->setCursor($l, $column);
|
||||
$term->write("\u{2502}" . str_repeat(" ", $width - 2) . "\u{2502}");
|
||||
}
|
||||
|
||||
$term->setCursor($oldpos[0], $oldpos[1]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a horizontal line
|
||||
*/
|
||||
'drawhline' => function ($term, $line, $fromCol, $toCol) {
|
||||
|
||||
$oldpos = $term->getCursorPosition();
|
||||
|
||||
$term->setCursor($line, $fromCol);
|
||||
$term->write(str_repeat("\u{2500}", ($toCol-$fromCol)));
|
||||
|
||||
$term->setCursor($oldpos[0], $oldpos[1]);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw a vertical line
|
||||
*/
|
||||
'drawvline' => function ($term, $column, $fromLine, $toLine) {
|
||||
|
||||
$oldpos = $term->getCursorPosition();
|
||||
|
||||
for ($l = $fromLine; $l <= $toLine; $l++) {
|
||||
$term->setCursor($l, $column);
|
||||
$term->write("\u{2502}");
|
||||
}
|
||||
|
||||
$term->setCursor($oldpos[0], $oldpos[1]);
|
||||
|
||||
}
|
||||
|
||||
];
|
BIN
examples/boxdraw.png
Normal file
BIN
examples/boxdraw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -1,5 +1,5 @@
|
||||
set terminal 80x5
|
||||
set typedelay 250
|
||||
set terminal 80x10
|
||||
set typedelay 100
|
||||
set fps 30
|
||||
|
||||
sub prompt
|
||||
@ -7,33 +7,93 @@ sub prompt
|
||||
delay 500
|
||||
endsub
|
||||
|
||||
write <sgr bold> "dd" <sgr> " - convert and copy a file" <return>
|
||||
write u{2500} *80 <return>
|
||||
|
||||
mark %top
|
||||
|
||||
|
||||
|
||||
write <sgr fg:cyan bold> Example 1: <sgr fg:cyan> " Creating a disk image" <sgr> <return> <return>
|
||||
prompt "~"
|
||||
|
||||
type "dd "
|
||||
|
||||
delay 1000
|
||||
|
||||
mark dd-if
|
||||
type "if=/dev/sdb "
|
||||
annotate @dd-if "Input" at dd-if
|
||||
annotate @dd-if "Input file\n(Source)" at dd-if
|
||||
annotatelength @dd-if 11
|
||||
delay 2000
|
||||
|
||||
mark dd-of
|
||||
type "of=image.img "
|
||||
annotate @dd-of "Output" at dd-of
|
||||
annotate @dd-of "Output file\n(Destination)" at dd-of
|
||||
annotatelength @dd-of 12
|
||||
delay 2000
|
||||
|
||||
mark dd-bs
|
||||
type "bs=16M "
|
||||
annotate @dd-bs "Block\nsize" at dd-bs
|
||||
annotatelength @dd-bs 6
|
||||
delay 2000
|
||||
|
||||
mark dd-status
|
||||
type "status=progress "
|
||||
annotate @dd-status "Show progress" at dd-status
|
||||
annotatelength @dd-status 15
|
||||
delay 2000
|
||||
|
||||
write <return>
|
||||
|
||||
prompt "~"
|
||||
|
||||
delay 2000
|
||||
|
||||
clearannotate
|
||||
moveto %top
|
||||
clear to-bottom
|
||||
delay 500
|
||||
|
||||
|
||||
delay 500
|
||||
write <sgr fg:cyan bold> Example 2: <sgr fg:cyan> " Create a file with empty data" <sgr> <return> <return>
|
||||
prompt "~"
|
||||
|
||||
type "dd "
|
||||
|
||||
delay 1000
|
||||
|
||||
mark dd-if
|
||||
type "if=/dev/zero "
|
||||
annotate @dd-if "Reading from\n/dev/zero will\nreturn '0'\nindefinitely" at dd-if
|
||||
annotatelength @dd-if 12
|
||||
delay 2000
|
||||
|
||||
mark dd-bs
|
||||
type "bs=1M "
|
||||
annotate @dd-bs "Block\nsize" at dd-bs
|
||||
annotatelength @dd-bs 5
|
||||
delay 2000
|
||||
|
||||
mark dd-count
|
||||
type "count=100 "
|
||||
annotate @dd-count "Number of\nblocks to\ncopy:\n100*1M=100MB" at dd-count
|
||||
annotatelength @dd-count 9
|
||||
delay 2000
|
||||
|
||||
mark dd-of
|
||||
type "> "
|
||||
annotate @dd-of "Without of= the output\nwill go to standard output,\nwhich can be redirected" at dd-of
|
||||
annotatelength @dd-of 1
|
||||
delay 2000
|
||||
|
||||
type "empty.img"
|
||||
|
||||
write <return>
|
||||
|
||||
delay 2000
|
||||
|
||||
clearannotate
|
||||
moveto %top
|
||||
clear to-bottom
|
||||
delay 500
|
||||
|
||||
|
BIN
examples/dd.gif
BIN
examples/dd.gif
Binary file not shown.
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 1.0 MiB |
BIN
examples/dd.mp4
Normal file
BIN
examples/dd.mp4
Normal file
Binary file not shown.
5
examples/keys.ftm
Normal file
5
examples/keys.ftm
Normal file
@ -0,0 +1,5 @@
|
||||
set terminal 80x25
|
||||
|
||||
showkeys ctrl alt shift F4 up up down down left right left right B A
|
||||
|
||||
savepng keys.png
|
BIN
examples/keys.png
Normal file
BIN
examples/keys.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
@ -16,6 +16,8 @@ class FakeTerminal
|
||||
|
||||
private $subs = [];
|
||||
|
||||
private $funcs = [];
|
||||
|
||||
private $execMode = null;
|
||||
|
||||
private $execTarget = null;
|
||||
@ -32,6 +34,12 @@ class FakeTerminal
|
||||
|
||||
private $frameDuration = 0;
|
||||
|
||||
private $startTime;
|
||||
|
||||
private $nextStat;
|
||||
|
||||
private $lastFrames;
|
||||
|
||||
private $output;
|
||||
|
||||
private $bookmarks = [];
|
||||
@ -47,6 +55,20 @@ class FakeTerminal
|
||||
|
||||
}
|
||||
|
||||
private function showStats()
|
||||
{
|
||||
if ($this->nextStat === null) {
|
||||
$this->nextStat = microtime(true) + 2;
|
||||
return;
|
||||
} elseif ($this->nextStat > microtime(true)) {
|
||||
return;
|
||||
}
|
||||
$this->nextStat = microtime(true) + 2;
|
||||
$fps = ($this->frame - $this->lastFrames) / 2;
|
||||
$this->lastFrames = $this->frame;
|
||||
fprintf(STDERR, "rendering: f=%d fps=%.1f t=%.1d\n", $this->frame, $fps, microtime(true) - $this->startTime);
|
||||
}
|
||||
|
||||
public function setOutput(string $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
@ -57,9 +79,36 @@ class FakeTerminal
|
||||
$this->outputVideo = $outputVideo;
|
||||
}
|
||||
|
||||
public function loadModule(string $filename)
|
||||
{
|
||||
$funcs = include $filename;
|
||||
foreach ($funcs as $name=>$func) {
|
||||
$this->funcs[$name] = $func;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeFile(string $filename)
|
||||
{
|
||||
$this->startTime = microtime(true);
|
||||
|
||||
$this->includeFile($filename);
|
||||
|
||||
echo "\r\e[K";
|
||||
if ($this->outputVideo) {
|
||||
fprintf(STDERR, "Converting %s → %s\n", $this->output, $this->outputVideo);
|
||||
passthru(sprintf("ffmpeg -v error -r %d -i %s -y %s", $this->fps, $this->output, $this->outputVideo));
|
||||
fprintf(STDERR, "Cleaning up %d files...\n", count($this->writtenFiles));
|
||||
foreach ($this->writtenFiles as $fn) {
|
||||
unlink($fn);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function includeFile(string $filename)
|
||||
{
|
||||
$fp = fopen($filename, "r");
|
||||
if (!$fp) return;
|
||||
$lc = 0;
|
||||
while (!feof($fp)) {
|
||||
$lc++;
|
||||
@ -73,17 +122,7 @@ class FakeTerminal
|
||||
fprintf(STDERR, "Error executing %s line %d: %s\n", $filename, $lc, $t->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
echo "\r\e[K";
|
||||
if ($this->outputVideo) {
|
||||
fprintf(STDERR, "Converting %s → %s\n", $this->output, $this->outputVideo);
|
||||
passthru(sprintf("ffmpeg -v error -r %d -i %s -y %s", $this->fps, $this->output, $this->outputVideo));
|
||||
fprintf(STDERR, "Cleaning up %d files...\n", count($this->writtenFiles));
|
||||
foreach ($this->writtenFiles as $fn) {
|
||||
unlink($fn);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
public function evaluate(array $data)
|
||||
@ -108,6 +147,16 @@ class FakeTerminal
|
||||
$this->setProp($prop, $value);
|
||||
break;
|
||||
|
||||
case 'include':
|
||||
$file = array_shift($args);
|
||||
$this->includeFile($file);
|
||||
break;
|
||||
|
||||
case 'load':
|
||||
$file = array_shift($args);
|
||||
$this->loadModule($file);
|
||||
break;
|
||||
|
||||
case 'sub':
|
||||
$subname = array_shift($args);
|
||||
$subargs = $args;
|
||||
@ -202,11 +251,81 @@ class FakeTerminal
|
||||
$this->renderer->updateAnnotation($id, [ 'length' => intval(array_shift($args))]);
|
||||
break;
|
||||
|
||||
case 'showkeys':
|
||||
$this->renderer->setKeyOverlay($args);
|
||||
break;
|
||||
|
||||
case 'clear':
|
||||
$what = array_shift($args);
|
||||
$tw = $this->term->getColumns();
|
||||
$th = $this->term->getLines();
|
||||
[$cl,$cc] = $this->term->getCursorPosition();
|
||||
$sa = $this->term->getAttributes();
|
||||
switch ($what) {
|
||||
case 'screen':
|
||||
for ($l = 0; $l < $th; $l++) {
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($l, $c, [ ' ', $sa ]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'line':
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($cl, $c, [ ' ', $sa ]);
|
||||
}
|
||||
break;
|
||||
case 'to-eol':
|
||||
for ($c = $cc; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($cl, $c, [ ' ', $sa ]);
|
||||
}
|
||||
break;
|
||||
case 'to-bol':
|
||||
for ($c = 0; $c <= $cc; $c++) {
|
||||
$this->term->bufferSetRaw($cl, $c, [ ' ', $sa ]);
|
||||
}
|
||||
break;
|
||||
case 'above':
|
||||
for ($l = 0; $l < $cl; $l++) {
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($l, $c, [ ' ', $sa ]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'to-top':
|
||||
for ($l = 0; $l <= $cl; $l++) {
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($l, $c, [ ' ', $sa ]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'below':
|
||||
for ($l = $cl + 1; $l < $th; $l++) {
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($l, $c, [ ' ', $sa ]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'to-bottom':
|
||||
for ($l = $cl; $l < $th; $l++) {
|
||||
for ($c = 0; $c < $tw; $c++) {
|
||||
$this->term->bufferSetRaw($l, $c, [ ' ', $sa ]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(STDERR, "error: Can't clear '%s'\n", $what);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (array_key_exists($cmd, $this->subs)) {
|
||||
$this->callSub($cmd, $args);
|
||||
return;
|
||||
}
|
||||
if (array_key_exists($cmd, $this->funcs)) {
|
||||
$this->callFunc($cmd, $args);
|
||||
return;
|
||||
}
|
||||
throw new \LogicException("No such command ${cmd}");
|
||||
}
|
||||
|
||||
@ -224,8 +343,7 @@ class FakeTerminal
|
||||
$file = sprintf($this->output, $this->frame++);
|
||||
$this->renderer->writePng($file);
|
||||
|
||||
//$this->dumper->render($this->term);
|
||||
//printf("\e[0m\rwrite: \e[1m%s\e[0m", $file);
|
||||
$this->showStats();
|
||||
$this->writtenFiles[] = $file;
|
||||
}
|
||||
|
||||
@ -255,9 +373,10 @@ class FakeTerminal
|
||||
{
|
||||
//printf("write: %s\n", json_encode($args));
|
||||
$buf = null;
|
||||
$lastArg = null;
|
||||
while (count($args)) {
|
||||
$arg = array_shift($args);
|
||||
if ($arg[0] == "<" && strlen($arg) > 1) {
|
||||
if ($arg && $arg[0] == "<" && strlen($arg) > 1) {
|
||||
if (substr($arg, -1, 1) == ">") {
|
||||
$this->setupTerm(substr($arg, 1, -1));
|
||||
continue;
|
||||
@ -272,9 +391,11 @@ class FakeTerminal
|
||||
$buf = join(" ", $buf);
|
||||
$this->setupTerm(substr($buf, 1, -1));
|
||||
$buf = null;
|
||||
|
||||
} elseif (preg_match('/u\{([0-9a-f]+)\}/', $arg, $match)) {
|
||||
|
||||
$arg = mb_chr(hexdec($match[1]));
|
||||
$lastArg = $arg;
|
||||
//printf("write: '%s'\n", $arg);
|
||||
if (!$type) {
|
||||
$this->term->write($arg);
|
||||
@ -284,9 +405,26 @@ class FakeTerminal
|
||||
$this->delayMs($this->typeDelay + rand(-$this->typeJitter,$this->typeJitter));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} elseif (preg_match('/\*([0-9]+)/', $arg, $match)) {
|
||||
|
||||
$repeat = intval($match[1]) - 1;
|
||||
|
||||
//printf("repeat: '%s' * %d\n", $lastArg, $repeat);
|
||||
for ($n = 0; $n < $repeat; $n++) {
|
||||
if (!$type) {
|
||||
$this->term->write((string)$lastArg);
|
||||
} else {
|
||||
for ($c = 0; $c < mb_strlen($lastArg); $c++) {
|
||||
$this->term->write(mb_substr($lastArg, $c, 1));
|
||||
$this->delayMs($this->typeDelay + rand(-$this->typeJitter,$this->typeJitter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
//printf("write: '%s'\n", $arg);
|
||||
$lastArg = $arg;
|
||||
if (!$type) {
|
||||
$this->term->write($arg);
|
||||
} else {
|
||||
@ -310,6 +448,7 @@ class FakeTerminal
|
||||
$this->term->setCursor($this->term->getCursorPosition()[0], 0);
|
||||
break;
|
||||
|
||||
case 'br':
|
||||
case 'return':
|
||||
$this->term->setCursor($this->term->getCursorPosition()[0] + 1, 0);
|
||||
break;
|
||||
@ -371,13 +510,18 @@ class FakeTerminal
|
||||
foreach ($data as $k=>$v) {
|
||||
if ($v[0] == '$' && strlen($v) > 1) {
|
||||
$i = intval(substr($v, 1)) - 1;
|
||||
$data[$k] = array_key_exists($i, $args)?$args[$i]:"";
|
||||
$data[$k] = array_key_exists($i, $args)?$args[$i]:$v;
|
||||
}
|
||||
}
|
||||
$this->evaluate($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function callFunc(string $func, array $args=[])
|
||||
{
|
||||
call_user_func($this->funcs[$func], $this->term, ...$args);
|
||||
}
|
||||
|
||||
public function setProp(string $prop, $value)
|
||||
{
|
||||
switch ($prop) {
|
||||
|
Loading…
Reference in New Issue
Block a user