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