clrghouz/app/Classes/BBS/Window.php
2023-09-13 20:59:40 +10:00

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];
}
}