365 lines
12 KiB
PHP
365 lines
12 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace App\Classes\BBS;
|
||
|
|
||
|
use Illuminate\Support\Collection;
|
||
|
|
||
|
use App\Classes\BBS\Frame\Char;
|
||
|
|
||
|
/**
|
||
|
* Windows are elements of a Page object
|
||
|
*
|
||
|
* @param int $x - (int) starting x of it's parent [1..]
|
||
|
* @param int $y - (int) starting y of it's parent [1..]
|
||
|
* @param int $width - (int) full width of the window (text content will be smaller if there are scroll bars/boarder)
|
||
|
* @param int $height - (int) full height of the window (text content will be smaller if there are scroll bars/boarder)
|
||
|
* @param string $name - (string) internal name for the window (useful for debugging)
|
||
|
* @param Window $parent - (object) parent of this window
|
||
|
* @param bool $debug - (int) debug mode, which fills the window with debug content
|
||
|
*
|
||
|
* Pages have the following attributes:
|
||
|
* - bx/by - (int) right/bottom most boundary of the window representing the start + width/height of the window
|
||
|
* - child - (array) children in this window
|
||
|
* - height - (int) Window's height
|
||
|
* - name - (string) Windows name (useful for internal debugging)
|
||
|
* - parent - (object) Parent that this window belongs to
|
||
|
* - x/y - (int) start position of the window
|
||
|
* - visible - (bool) whether this window is visible
|
||
|
* - width - (int) Window's width
|
||
|
* - z - (int) Window's depth indicator
|
||
|
*
|
||
|
* Windows have the following public functions
|
||
|
* - build - Compile the frame for rendering
|
||
|
* - debug - Useful for debugging with properties of this Window
|
||
|
* - draw - Draw a part of this Window
|
||
|
*/
|
||
|
class Window
|
||
|
{
|
||
|
/** @var int X offset of parent that the canvas starts [1..width] */
|
||
|
private int $x;
|
||
|
/** @var int Y offset of parent that the canvas starts [1..height] */
|
||
|
private int $y;
|
||
|
/** @var int Window top-bottom position, higher z is shown [0..] */
|
||
|
private int $z = 0;
|
||
|
/** @var int When canvas width > width, this is the offset we display [0..] */
|
||
|
private int $ox = 0;
|
||
|
/** @var int When canvas height > height, this is the offset we display [0..] */
|
||
|
private int $oy = 0;
|
||
|
/** @var int Display Width + (1 char if scrollbars = true) */
|
||
|
private int $width;
|
||
|
/** @var int Display Height */
|
||
|
private int $height;
|
||
|
/** @var int Width of Canvas (default display width) */
|
||
|
private int $canvaswidth;
|
||
|
/** @var int Height of Canvas (default display height) */
|
||
|
private int $canvasheight;
|
||
|
/** @var array Window content - starting at 0,0 = 1,1 */
|
||
|
public array $content = [];
|
||
|
/** @var bool Window visible */
|
||
|
private bool $visible = TRUE;
|
||
|
/** @var string Window name */
|
||
|
private string $name;
|
||
|
/** @var bool Can this frame move outside the parent */
|
||
|
private bool $checkbounds = TRUE;
|
||
|
/** @var bool Can the content scroll vertically (takes up 1 line) [AUTO DETERMINE IF canvas > width] */
|
||
|
private bool $v_scroll = TRUE;
|
||
|
/** @var bool Can the content scroll horizontally (takes up 1 char) [AUTO DETERMINE IF canvas > height] */
|
||
|
private bool $h_scroll = FALSE;
|
||
|
/** @var int|bool Overflowed content is rendered with the next page */
|
||
|
private bool $pageable = FALSE;
|
||
|
private Page|Window|NULL $parent;
|
||
|
private Collection $child;
|
||
|
private bool $debug;
|
||
|
|
||
|
/*
|
||
|
Validation to implement:
|
||
|
+ X BOUNDARY
|
||
|
- x cannot be < parent.x if checkbounds is true [when moving window]
|
||
|
- x+width(-1 if h_scroll is true) cannot be greater than parent.width if checkbounds is true
|
||
|
- v_scroll must be true for canvaswidth > width
|
||
|
- when scrolling ox cannot be > width-x
|
||
|
- when layout.pageable is true, next page will only have windows included that have a y in the range
|
||
|
ie: if height is 44 (window is 22), next page is 23-44 and will only include children where y=23-44
|
||
|
+ Y BOUNDARY
|
||
|
- y cannot be < parent.y if checkbounds is true [when moving window]
|
||
|
- y+height(-1 if v_scroll is true) cannot be greater than parent.height if checkbounds is true
|
||
|
- h_scroll must be true for canvasheight > height
|
||
|
- when scrolling oy cannot be > height-y
|
||
|
- when layout.pageable is true, children height cannot be greater than parent.height - y.
|
||
|
*/
|
||
|
public function __construct(int $x,int $y,int $width,int $height,string $name,Window|Page $parent=NULL,bool $debug=FALSE) {
|
||
|
$this->x = $x;
|
||
|
$this->y = $y;
|
||
|
$this->name = $name;
|
||
|
$this->parent = $parent;
|
||
|
$this->debug = $debug;
|
||
|
$this->child = collect();
|
||
|
|
||
|
if ($parent instanceof self) {
|
||
|
$this->z = $parent->child->count()+1;
|
||
|
$this->parent = $parent;
|
||
|
|
||
|
$this->parent->child->push($this);
|
||
|
|
||
|
// Check that our height/widths is not outside our parent
|
||
|
if (($this->x < 1) || ($width > $this->parent->width))
|
||
|
throw new \Exception(sprintf('Window: %s width [%d] is beyond our parent\'s width [%d].',$name,$width,$this->parent->width));
|
||
|
if (($x > $this->parent->bx) || ($x+$width-1 > $this->parent->bx))
|
||
|
throw new \Exception(sprintf('Window: %s start x [%d] and width [%d] is beyond our parent\'s end x [%d].',$name,$x,$width,$this->parent->bx));
|
||
|
|
||
|
if (($this->y < 1) || ($height > $this->parent->height))
|
||
|
throw new \Exception(sprintf('Window: %s height [%d] is beyond our parent\'s height [%d].',$name,$height,$this->parent->height));
|
||
|
if (($y > $this->parent->by) || ($y+$height-1 > $this->parent->by))
|
||
|
throw new \Exception(sprintf('Window: %s start y [%d] and height [%d] is beyond our parent\'s end y [%s].',$name,$y,$height,$this->parent->by));
|
||
|
|
||
|
} elseif ($parent instanceof Page) {
|
||
|
$this->parent = $parent;
|
||
|
}
|
||
|
|
||
|
$this->width = $this->canvaswidth = $width;
|
||
|
$this->height = $this->canvasheight = $height;
|
||
|
|
||
|
if ($debug) {
|
||
|
$this->canvaswidth = $width*2;
|
||
|
$this->canvasheight = $height*2;
|
||
|
}
|
||
|
|
||
|
// Fill with data
|
||
|
for($y=1;$y<=$this->canvasheight;$y++) {
|
||
|
for($x=1;$x<=$this->canvaswidth;$x++) {
|
||
|
if (! isset($this->content[$y]))
|
||
|
$this->content[$y] = [];
|
||
|
|
||
|
$this->content[$y][$x] = $debug
|
||
|
? new Char((($x > $this->width) || ($y > $this->height)) ? strtoupper($this->name[0]) : strtolower($this->name[0]))
|
||
|
: new Char();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function __get($key): mixed
|
||
|
{
|
||
|
switch ($key) {
|
||
|
case 'bx': return $this->x+$this->width-1;
|
||
|
case 'by': return $this->y+$this->height-1;
|
||
|
case 'checkbounds': return $this->checkbounds;
|
||
|
case 'child':
|
||
|
return $this->child->sort(function($a,$b) {return ($a->z < $b->z) ? -1 : (($b->z < $a->z) ? 1 : 0); });
|
||
|
case 'name':
|
||
|
return $this->name;
|
||
|
case 'height':
|
||
|
case 'parent':
|
||
|
case 'visible':
|
||
|
case 'width':
|
||
|
case 'x':
|
||
|
case 'y':
|
||
|
case 'z':
|
||
|
return $this->{$key};
|
||
|
|
||
|
default:
|
||
|
throw new \Exception('Unknown key: '.$key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function __set($key,$value): void
|
||
|
{
|
||
|
switch ($key) {
|
||
|
case 'child':
|
||
|
if ($value instanceof self)
|
||
|
$this->child->push($value);
|
||
|
else
|
||
|
throw new \Exception('child not an instance of Window()');
|
||
|
break;
|
||
|
|
||
|
case 'content':
|
||
|
$this->content = $value;
|
||
|
break;
|
||
|
|
||
|
case 'parent':
|
||
|
if ($this->parent)
|
||
|
throw new \Exception('parent already DEFINED');
|
||
|
else
|
||
|
$this->parent = $value;
|
||
|
break;
|
||
|
|
||
|
case 'visible':
|
||
|
$this->visible = $value;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new \Exception('Unknown key: '.$key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Build this window, returning an array of Char that will be rendered by Page
|
||
|
*
|
||
|
* @param int $xoffset - (int) This windows x position for its parent
|
||
|
* @param int $yoffset - (int) This windows y position for its parent
|
||
|
* @param bool $debug - (int) debug mode, which fills the window with debug content
|
||
|
* @return array
|
||
|
*/
|
||
|
public function build(int $xoffset,int $yoffset,bool $debug=FALSE): array
|
||
|
{
|
||
|
$display = [];
|
||
|
|
||
|
if ($debug) {
|
||
|
dump('********* ['.$this->name.'] *********');
|
||
|
dump('name :'.$this->name);
|
||
|
dump('xoff :'.$xoffset);
|
||
|
dump('yoff :'.$yoffset);
|
||
|
dump('x :'.$this->x);
|
||
|
dump('bx :'.$this->bx);
|
||
|
dump('ox :'.$this->ox);
|
||
|
dump('y :'.$this->y);
|
||
|
dump('by :'.$this->by);
|
||
|
dump('oy :'.$this->oy);
|
||
|
dump('lines :'.count(array_keys($this->content)));
|
||
|
//dump('content:'.join('',$this->content[1]));
|
||
|
}
|
||
|
|
||
|
if ($debug)
|
||
|
dump('-------------');
|
||
|
|
||
|
for ($y=1;$y<=$this->height;$y++) {
|
||
|
if ($debug)
|
||
|
echo sprintf('%02d',$y).':';
|
||
|
|
||
|
$sy = $this->y-1+$y+$yoffset-1;
|
||
|
|
||
|
for ($x=1;$x<=$this->width;$x++) {
|
||
|
if ($debug)
|
||
|
dump('- Checking :'.$this->name.', y:'.($y+$this->oy).', x:'.($x+$this->ox));
|
||
|
|
||
|
$sx = $this->x-1+$x+$xoffset-1;
|
||
|
if (! isset($display[$sy]))
|
||
|
$display[$sy] = [];
|
||
|
|
||
|
if (isset($this->content[$y+$this->oy]) && isset($this->content[$y+$this->oy][$x+$this->ox])) {
|
||
|
$display[$sy][$sx] = $this->content[$y+$this->oy][$x+$this->ox];
|
||
|
|
||
|
if ($debug)
|
||
|
dump('- storing in y:'.($sy).', x:'.($sx).', ch:'.$display[$sy][$sx]->ch);
|
||
|
|
||
|
} else {
|
||
|
$display[$sy][$sx] = new Char();
|
||
|
|
||
|
if ($debug)
|
||
|
dump('- nothing for y:'.($sy).', x:'.($sx).', ch:'.$display[$sy][$sx]->ch);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($debug)
|
||
|
dump('---');
|
||
|
}
|
||
|
|
||
|
if ($debug)
|
||
|
dump('----LOOKING AT CHILDREN NOW---------');
|
||
|
|
||
|
if ($debug) {
|
||
|
dump('Window:'.$this->name.', has ['.$this->child->filter(function($child) { return $child->visible; })->count().'] children');
|
||
|
|
||
|
$this->child->each(function($child) {
|
||
|
dump(' - child:'.$child->name.', visible:'.$child->visible);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Fill the array with our values
|
||
|
foreach ($this->child->filter(function($child) { return $child->visible; }) as $child) {
|
||
|
if ($debug) {
|
||
|
dump('=========== ['.$child->name.'] =============');
|
||
|
dump('xoff :'.$xoffset);
|
||
|
dump('yoff :'.$yoffset);
|
||
|
dump('x :'.$this->x);
|
||
|
dump('y :'.$this->y);
|
||
|
}
|
||
|
|
||
|
$draw = $child->build($this->x+$xoffset-1,$this->y+$yoffset-1,$debug);
|
||
|
|
||
|
if ($debug)
|
||
|
dump('draw y:'.join(',',array_keys($draw)));
|
||
|
|
||
|
foreach (array_keys($draw) as $y) {
|
||
|
foreach (array_keys($draw[$y]) as $x) {
|
||
|
if (! isset($display[$y]))
|
||
|
$display[$y] = [];
|
||
|
|
||
|
$display[$y][$x] = $draw[$y][$x];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($debug) {
|
||
|
//dump('draw 1:'.join(',',array_keys($draw[1])));
|
||
|
dump('=========== END ['.$child->name.'] =============');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($debug) {
|
||
|
dump('this->name:'.$this->name);
|
||
|
dump('this->y:'.$this->y);
|
||
|
dump('display now:'.join(',',array_values($display[$this->y])));
|
||
|
dump('********* END ['.$this->name.'] *********');
|
||
|
|
||
|
foreach ($display as $y => $data) {
|
||
|
dump(sprintf("%02d:%s (%d)\r\n",$y,join('',$data),count($data)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $display;
|
||
|
}
|
||
|
|
||
|
public function xdebug(string $text) {
|
||
|
return '- '.$text.': '.$this->name.'('.$this->x.'->'.($this->bx).') width:'.$this->width.' ['.$this->y.'=>'.$this->by.'] with z:'.$this->z;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render this window
|
||
|
*
|
||
|
* @param $start - (int) Starting x position
|
||
|
* @param $end - (int) Ending x position
|
||
|
* @param $y - (int) Line to render
|
||
|
* @param $color - (bool) Whether to include color
|
||
|
* @returns {{x: number, content: string}}
|
||
|
*/
|
||
|
public function xdraw($start,$end,$y,$color): array
|
||
|
{
|
||
|
$content = '';
|
||
|
|
||
|
for ($x=$start;$x<=$end;$x++) {
|
||
|
$rx = $this->ox+$x;
|
||
|
$ry = $this->oy+$y;
|
||
|
|
||
|
// Check if we have an attribute to draw
|
||
|
if (! (isset($this->content[$ry])) || ! (isset($this->content[$ry][$rx]))) {
|
||
|
$content += ' ';
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ($color === NULL || $color === true) {
|
||
|
// Only write a new attribute if it has changed
|
||
|
if (($this->last === NULL) || ($this->last !== $this->content[$ry][$rx]->attr)) {
|
||
|
$this->last = $this->content[$ry][$rx]->attr;
|
||
|
|
||
|
$content += ($this->last === null ? BG_BLACK|LIGHTGRAY : $this->last);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
$content += ($this->content[$ry][$rx]->ch !== null ? $this->content[$ry][$rx]->ch : ' ');
|
||
|
|
||
|
} catch (\Exception $e) {
|
||
|
dump($e);
|
||
|
dump('---');
|
||
|
dump('x:'.($x-$this->x));
|
||
|
dump('y:'.($y-$this->y));
|
||
|
dump('ox:'.$this->ox);
|
||
|
dump('oy:'.$this->oy);
|
||
|
dump('$rx:'.$rx);
|
||
|
dump('$ry:'.$ry);
|
||
|
exit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ['content'=>$content, 'x'=>$end - $start + 1];
|
||
|
}
|
||
|
}
|