Files
jsonedit/src/Editor/Menu.php
T

239 lines
7.8 KiB
PHP
Raw Normal View History

2024-10-01 22:31:51 +02:00
<?php
namespace NoccyLabs\JsonEdit\Editor;
2024-10-01 22:31:51 +02:00
use NoccyLabs\JsonEdit\Terminal\Terminal;
2024-10-01 22:31:51 +02:00
class Menu
{
const U_CORNER_TOPLEFT = "\u{250c}";
const U_CORNER_TOPRIGHT = "\u{2510}";
const U_CORNER_BOTTOMLEFT = "\u{2514}";
const U_CORNER_BOTTOMRIGHT = "\u{2518}";
const U_CORNER_ROUNDED_TOPLEFT = "\u{256d}";
const U_CORNER_ROUNDED_TOPRIGHT = "\u{256e}";
const U_CORNER_ROUNDED_BOTTOMLEFT = "\u{2570}";
const U_CORNER_ROUNDED_BOTTOMRIGHT = "\u{256f}";
const U_EDGE_HORIZONTAL = "\u{2500}";
const U_EDGE_VERTICAL = "\u{2502}";
2024-10-05 17:37:45 +02:00
const U_EDGE_VERTICAL_SCROLL = "\u{2591}";
const U_EDGE_VERTICAL_THUMB = "\u{2593}";
2024-10-01 22:31:51 +02:00
const U_EDGE_VERTICAL_LEFT = "\u{2524}";
const U_EDGE_VERTICAL_RIGHT = "\u{251c}";
private int $index = 0;
private int $scroll = 0;
2024-10-06 16:22:46 +02:00
private $keyhandler = null;
2024-10-05 17:37:45 +02:00
/**
* constructor
*
* @param Terminal $terminal
* @param array $items
* @param string $title
*/
2024-10-01 22:31:51 +02:00
public function __construct(private Terminal $terminal, private array $items, private string $title = 'Menu')
{
}
2024-10-06 16:22:46 +02:00
public function setKeyhandler(?callable $handler): self
{
$this->keyhandler = $handler;
return $this;
}
2024-10-05 17:37:45 +02:00
/**
* Update the menu items
*
* @param array $items
* @return self
*/
public function setItems(array $items): self
{
$this->items = $items;
return $this;
}
/**
* Set the menu title
*
* @param string $title
* @return self
*/
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
/**
* Display the menu
*
* @param int $left
* @param int $top
* @param int $width
* @param int $height
* @param string|int|null $value
* @return mixed
*/
2024-10-07 00:30:09 +02:00
public function display(int $left, int $top, int $width, int $height = 0, string|int|null $value = null): mixed
2024-10-01 22:31:51 +02:00
{
2024-10-06 16:22:46 +02:00
//$this->index = 0;
$keys = array_keys($this->items);
2024-10-07 00:30:09 +02:00
$this->index = array_search($value, $keys) ?: 0;
2024-10-01 22:31:51 +02:00
[$w,$h] = $this->terminal->getSize();
if ($height == 0) {
2024-10-03 15:29:56 +02:00
$height = min($h - 5, count($this->items) + 2);
}
if ($left == 0) {
$left = round(($w - $width) / 2);
}
if ($top == 0) {
$top = round(($h - $height) / 2);
}
2024-10-01 22:31:51 +02:00
$this->redraw($left, $top, $width, $height);
$showing = true;
while ($showing) {
while (null === ($ch = $this->terminal->readKey())) {
usleep(10000);
}
2024-10-06 16:22:46 +02:00
if (is_callable($this->keyhandler)) {
$ret = call_user_func($this->keyhandler, $ch);
if ($ret === true) {
return false;
}
}
2024-10-01 22:31:51 +02:00
if (mb_strlen($ch) == 1) {
if ($ch == "\x03") {
$showing = false;
}
if ($ch == "\r") {
return array_keys($this->items)[$this->index];
}
} else {
2024-10-05 17:54:49 +02:00
switch ($ch) {
case "k{UP}":
$this->index--;
if ($this->index < 0) $this->index = count($this->items) - 1;
$this->redraw($left, $top, $width, $height);
break;
case "k{DOWN}":
$this->index++;
if ($this->index >= count($this->items)) $this->index = 0;
$this->redraw($left, $top, $width, $height);
break;
case "k{PGUP}":
$this->index -= $height - 2;
if ($this->index < 0) $this->index = count($this->items) - 1;
$this->redraw($left, $top, $width, $height);
break;
case "k{PGDN}":
$this->index += $height - 2;
if ($this->index >= count($this->items)) $this->index = 0;
$this->redraw($left, $top, $width, $height);
break;
case "k{HOME}":
$this->index = 0;
if ($this->index >= count($this->items)) $this->index = 0;
$this->redraw($left, $top, $width, $height);
break;
case "k{END}":
$this->index = count($this->items) - 1;
if ($this->index >= count($this->items)) $this->index = 0;
$this->redraw($left, $top, $width, $height);
break;
2024-10-01 22:31:51 +02:00
}
}
}
return null;
}
2024-10-05 17:37:45 +02:00
/**
* Redraw menu
*
* @param int $left
* @param int $top
* @param int $width
* @param int $height
* @return void
*/
2024-10-01 22:31:51 +02:00
private function redraw(int $left, int $top, int $width, int $height) {
$visibleItems = $height - 2;
// scroll if index is out of view
if ($this->index < $this->scroll) {
$this->scroll = $this->index;
}
if ($this->index - $visibleItems >= $this->scroll) {
$this->scroll = $this->index - $visibleItems + 1;
}
$scrollTop = $this->scroll;
2024-10-05 17:37:45 +02:00
$scrollBottom = $scrollTop + $visibleItems - 1;
$thumbTop = round(($visibleItems - 1) / count($this->items) * $scrollTop);
2024-10-05 17:54:49 +02:00
$thumbBottom = round(($visibleItems) / count($this->items) * $scrollBottom);
2024-10-07 00:30:09 +02:00
$tleft = (int)round(($width / 2) - ((mb_strlen($this->title) + 2) / 2));
2024-10-01 22:31:51 +02:00
// draw head
echo "\e[40;37m";
$this->terminal
->writeAt($left, $top + 0, self::U_CORNER_ROUNDED_TOPLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_ROUNDED_TOPRIGHT)
->writeAt($left + $tleft, $top + 0, " \e[1m{$this->title}\e[22m ")
//->writeAt($left, $top + 1, self::U_EDGE_VERTICAL.str_repeat(" ",$width - 2).self::U_EDGE_VERTICAL)
//->writeAt($left + 2, $top + 1, "\e[1m" . $this->title . "\e[22m")
//->writeAt($left, $top + 2, self::U_EDGE_VERTICAL_RIGHT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_EDGE_VERTICAL_LEFT)
2024-10-01 22:31:51 +02:00
->writeAt($left, $top + $height - 1, self::U_CORNER_BOTTOMLEFT.str_repeat(self::U_EDGE_HORIZONTAL,$width - 2).self::U_CORNER_BOTTOMRIGHT)
;
$keys = array_keys($this->items);
for ($n = 0; $n < $visibleItems; $n++) {
$key = $keys[$n + $this->scroll]??null;
$item = ($key ? ($this->items[$key]) : null);
if ($item === "---") {
$item = "\e[2m".str_repeat("\u{2500}", $width - 2)."\e[22m";
} elseif (is_null($item)) {
$item = str_repeat(" ", $width - 2);
} else {
2024-10-04 17:29:36 +02:00
$item = mb_substr($item, 0, $width);
$padlen = $width - 3 - $this->itemlen($item);
$item = " " . $item . ($padlen>0?str_repeat(" ", $padlen):"") . "\e[40;37m";
$item = (($n + $this->scroll == $this->index)?"\e[37;44m":"\e[40;37m") . $item;
}
if ($n >= $thumbTop && $n <= $thumbBottom) {
$scrollbar = "\e[97;1m".self::U_EDGE_VERTICAL_THUMB."\e[40;37;22m";
} else {
$scrollbar = "\e[37;2m".self::U_EDGE_VERTICAL_SCROLL."\e[40;37;22m";
}
2024-10-01 22:31:51 +02:00
$this->terminal
->writeAt($left, $top + 1 + $n, self::U_EDGE_VERTICAL.$item.$scrollbar);
2024-10-01 22:31:51 +02:00
}
echo "\e[0m";
}
2024-10-05 17:37:45 +02:00
/**
* Calculate item length without markup
*
* @param string $item
* @return int
*/
private function itemlen(string $item): int
{
$item = preg_replace('<\e\[.+?m>', '', $item);
return mb_strlen($item);
}
2024-10-01 22:31:51 +02:00
}