今天翻了翻PHP手册,无意中发现这个PHP7才加入的新扩展:图形用户界面(GUI) 扩展 PHP-UI,觉得挺有意思的,2016年10月才发布的,网上搜了一圈发现几乎没有相关资讯,连安装说明都没有一个。。。
官方介绍原文:
This extension wraps the very excellent libui to provide PHP 7 with an API for the creation of cross platform native look-and-feel interfaces.
大概说的是:该扩展是适用于 PHP7 基于 libui 开发的跨平台图形界面接口吧。。。
PHP扩展介绍
Github
经过一番折腾,终于能运行了,
下面说说我的安装过程:(仅以Windows7为例)
1. 先下载扩展
扩展下载:https://pecl.php.net/package/ui
按照你本地的PHP7版本情况选择下载对应的扩展文件,注意:仅支持 PHP7
哦
2. 安装扩展
解压刚才下载的压缩包
总共需要三个 .dll
文件
php_ui.dll
libui.dll
pthreadVC2.dll
将 php_ui.dll
复制到 PHP 的 ext
目录下,
其余两个文件 libui.dll
和 pthreadVC2.dll
放到 PHP 的根目录下,也就是与 php.exe
的同级目录
如果你之前操作不正确,在命令行中执行 PHP 时,将会弹出下面的错误提示:
无法启动此程序,因为计算机中丢失 libui.dll,尝试重新安装该程序以解决此问题。
这个提示出现说明你没有放入 libui 和 pthreadVC2 文件到 php 的根目录下,重新放入就正常了。
3. 开始测试
打开之前下载的压缩包,其中有三个已写好的测试代码:
贪吃蛇游戏
,无限星空动画
,趋势图
下面运行测试代码看看效果吧:
贪吃蛇:
这是个可操作的游戏哦,不过撞到自己的尾巴不会死,窗口可任意拉伸,越大行动速度就越快
无限星空图
趋势图
可以实时调节变化。
5.源码参考:
这是官方的测试案例源码,可以先参考下看看
贪吃蛇代码:
<?php
define ("PHP_UI_SECOND", 1000000);
define ("PHP_UI_SNAKE_FPS", 30);
use UI\Window;
use UI\Point;
use UI\Size;
use UI\Area;
use UI\Key;
use UI\Controls\Box;
use UI\Draw\Pen;
use UI\Draw\Brush;
use UI\Draw\Path;
use UI\Draw\Color;
use UI\Draw\Stroke;
use UI\Draw\Matrix;
use UI\Draw\Text\Font;
use UI\Draw\Text\Font\Descriptor;
use UI\Executor;
$win = new class("Snake", new Size(640, 480), false) extends Window {
public function addExecutor(Executor $executor) {
$this->executors[] = $executor;
}
protected function onClosing() {
foreach ($this->executors as $executor) {
$executor->kill();
}
$this->destroy();
UI\quit();
}
};
$box = new Box(Box::Vertical);
$win->add($box);
$snake = new class($box) extends Area{
public function __construct(Box $box) {
$this->newSnake();
$box->append($this, true);
}
public function setExecutor(Executor $executor) {
$this->executor = $executor;
}
protected function onKey(string $char, int $key, int $flags) {
if ($flags & Area::Down) {
switch ($key) {
case Key::Up:
if ($this->direction == Key::Down)
return;
$this->direction = $key;
break;
case Key::Down:
if ($this->direction == Key::Up)
return;
$this->direction = $key;
break;
case Key::Right:
if ($this->direction == Key::Left)
return;
$this->direction = $key;
break;
case Key::Left:
if ($this->direction == Key::Right)
return;
$this->direction = $key;
break;
default:
if ($char == " ") {
$this->pause = !$this->pause;
if ($this->pause) {
/* this allows the CPU to idle while paused */
$this->executor->setInterval(0);
} else {
/* this will (re)start the game */
$this->executor->setInterval(PHP_UI_SECOND/PHP_UI_SNAKE_FPS);
}
}
break;
}
}
}
protected function onDraw(Pen $pen, Size $size, Point $clip, Size $clipSize) {
$zero = new Point(0, 0);
$frame = $zero + 40;
$frameSize = $size - 80;
$path = new Path();
$path->addRectangle($zero, $size);
$path->end();
$pen->fill($path, 0xf5f5f5ff);
$stroke = new Stroke();
$pen->stroke($path, 0x000000FF, $stroke);
$path = new Path();
$path->addRectangle($frame, $frameSize);
$path->end();
$pen->stroke($path, 0x000000FF, $stroke);
$matrix = new Matrix();
$matrix->translate($frame);
$pen->transform($matrix);
if (!$this->food) {
$this->newFood($frameSize);
}
if (!$this->pause && ($run = microtime(true)) - $this->run > 0.1 / $this->level * 2) {
$this->run = $run;
$next = clone $this->snake[0];
switch ($this->direction) {
case Key::Right: $next->x++; break;
case Key::Left: $next->x--; break;
case Key::Up: $next->y--; break;
case Key::Down: $next->y++; break;
}
if ($next->x < 0 || $next->x >= ($frameSize->width)/10 ||
$next->y < 0 || $next->y >= ($frameSize->height)/10) {
$this->newSnake();
$this->newFood($frameSize);
foreach ($this->snake as $body) {
$this->newCell($pen, $body);
}
$this->newCell($pen, $this->food);
$this->pause = true;
$this->direction = Key::Right;
$this->score = 0;
$this->level = 1;
return;
}
if ($this->food == $next) {
$tail = $next;
$this->newFood($frameSize);
$this->score += 10;
$this->level = ceil($this->score / 100);
} else {
$tail = array_pop($this->snake);
$tail->x = $next->x;
$tail->y = $next->y;
}
array_unshift($this->snake, $tail);
}
foreach ($this->snake as $body) {
$this->newCell($pen, $body);
}
$this->newCell($pen, $this->food);
$matrix = new Matrix();
$matrix->translate($zero - 40);
$pen->transform($matrix);
if ($this->pause) {
$this->drawPause($pen, $size);
} else $this->drawScore($pen, $size);
}
private function newSnake() {
$this->snake = [];
for ($i = 0; $i < 5; $i++)
$this->snake[$i] = new Point($i, 0);
}
private function newFood(Size $size) {
$this->food = new Point(
floor(mt_rand(40, ($size->width ) - 10) / 10),
floor(mt_rand(40, ($size->height) - 10) / 10));
}
private function newCell(Pen $pen, Point $point) {
$path = new Path();
$path->addRectangle($point * 10, new Size(10, 10));
$path->end();
$pen->fill($path, 0x0000FFFF);
$stroke = new Stroke();
$stroke->setThickness(2);
$pen->stroke($path, 0x000000FF, $stroke);
}
private function drawPause(Pen $pen, Size $size) {
$layout = new UI\Draw\Text\Layout(sprintf(
"Press space bar to play ...",
$this->level,
$this->score
), new Font(new Descriptor("arial", 12)), $size->width);
$layout->setColor(0x000000FF);
$pen->write(new Point(20, 10), $layout);
}
private function drawScore(Pen $pen, Size $size) {
$layout = new UI\Draw\Text\Layout(sprintf(
"Level: %d Score: %d",
$this->level,
$this->score
), new Font(new Descriptor("arial", 12)), $size->width);
$layout->setColor(0x000000FF);
$pen->write(new Point(20, 10), $layout);
}
private $snake;
private $food;
private $direction = Key::Right;
private $level = 1;
private $score = 0;
private $pause = true;
private $run = 0;
};
$animator = new class ($snake) extends Executor {
public function __construct(Area $area) {
$this->area = $area;
/* construct executor with infinite timeout */
parent::__construct();
}
protected function onExecute() {
$this->area->redraw();
}
};
$win->addExecutor($animator);
$snake->setExecutor($animator);
$win->show();
UI\run();
无限星空
<?php
use UI\Window;
use UI\Point;
use UI\Size;
use UI\Area;
use UI\Key;
use UI\Controls\Box;
use UI\Draw\Pen;
use UI\Draw\Brush;
use UI\Draw\Path;
use UI\Draw\Color;
use UI\Executor;
$win = new class("Starfield", new Size(640, 480), false) extends Window {
public function addExecutor(Executor $executor) {
$this->executors[] = $executor;
}
protected function onClosing() {
foreach ($this->executors as $executor) {
$executor->kill();
}
$this->destroy();
UI\quit();
}
};
$box = new Box(Box::Vertical);
$win->add($box);
$font = new UI\Draw\Text\Font(
new UI\Draw\Text\Font\Descriptor("arial", 12)
);
$stars = new class($box, 1024, 64, $font) extends Area {
protected function onKey(string $key, int $ext, int $flags) {
if ($flags & Area::Down) {
switch ($ext) {
case Key::Up: if ($this->velocity < 40) {
$this->velocity++;;
} break;
case Key::Down: if ($this->velocity) {
$this->velocity--;
} break;
}
}
}
protected function onDraw(UI\Draw\Pen $pen, UI\Size $size, UI\Point $clip, UI\Size $clipSize) {
$hSize = $size / 2;
$path = new Path();
$path->addRectangle(Point::at(0), $size);
$path->end();
$pen->fill($path, 0x000000FF);
foreach ($this->stars as $idx => &$star) {
$star[1] -= $this->velocity / 10;
if ($star[1] <= 0) {
$star[0]->x = mt_rand(-25, 25);
$star[0]->y = mt_rand(-25, 25);
$star[1] = $this->depth;
}
$pos = $star[0] * (128 / $star[1]) + $hSize;
if ($pos->x >= 0 && $pos->x <= $size->width && $pos->y >= 0 && $pos->y <= $size->height) {
$starSize = (1 - $star[1] / 32) * 5;
$path = new Path();
if (PHP_OS == "WINNT") {
$path->addRectangle($pos, new Size($starSize, $starSize));
} else {
$path->newFigureWithArc($pos, $starSize/2, 0, M_PI*2, 0);
}
$path->end();
$color = new Color();
$color->r = $starSize;
$color->g = $starSize;
$color->b = $starSize;
if ($star[2] && $star[3]++ % 3 == 0) {
$color->a = mt_rand(0,10) / 10;
}
$pen->fill($path, $color);
}
}
$this->writeRenderSpeed($pen, $size);
}
private function writeRenderSpeed(UI\Draw\Pen $pen, UI\Size $size) {
$now = time();
@$this->frames[$now]++;
$layout = new UI\Draw\Text\Layout(sprintf(
"%d fps",
isset($this->frames[$now - 1]) ?
$this->frames[$now-1] : $this->frames[$now]
), $this->font, $size->width);
$layout->setColor(0xFFFFFFFF);
$pen->write(new Point(20, 20), $layout);
unset($this->frames[$now-2]);
}
public function __construct($box, $max, $depth, $font, $velocity = 2) {
$this->box = $box;
$this->max = $max;
$this->depth = $depth;
$this->font = $font;
$this->velocity = $velocity;
for ($i = 0; $i < $this->max; $i++) {
$this->stars[] = [
new Point(mt_rand(-25, 25), mt_rand(-25, 25)),
mt_rand(1, $this->depth),
mt_rand(0, 1),
0
];
}
$this->box->append($this, true);
}
};
$animator = new class(1000000/60, $stars) extends Executor {
protected function onExecute() {
$this->area->redraw();
}
public function __construct(int $microseconds, Area $area) {
$this->area = $area;
parent::__construct($microseconds);
}
};
$win->addExecutor($animator);
$win->show();
UI\run();
趋势图:
<?php
use UI\Point;
use UI\Size;
use UI\Window;
use UI\Controls\Box;
use UI\Controls\Spin;
use UI\Controls\ColorButton;
use UI\Controls\Button;
use UI\Controls\Entry;
use UI\Controls\Label;
use UI\Controls\Combo;
use UI\Area;
use UI\Draw\Pen;
use UI\Draw\Path;
use UI\Draw\Color;
use UI\Draw\Brush;
use UI\Draw\Stroke;
use UI\Draw\Matrix;
use UI\Draw\Text\Font\Descriptor;
use UI\Draw\Text\Font;
use UI\Draw\Text\Layout;
$window = new Window("libui Histogram Example", new Size(640, 480), true);
$window->setMargin(true);
$hBox = new Box(Box::Horizontal);
$hBox->setPadded(true);
$vBox = new Box(Box::Vertical);
$vBox->setPadded(true);
$window->add($hBox);
$hBox->append($vBox);
$dataSources = [];
$histogram = new class($dataSources) extends Area {
private function getGraphPoints(Size $size) : array {
$xincr = $size->width / 9;
$yincr = $size->height / 100;
$points = [];
foreach ($this->sources as $i => $source) {
$points[$i] = new Point($xincr * $i, $yincr * (100 - $source->getValue()));
}
return $points;
}
private function getGraphPath(array $locations, Size $size, bool $extend = false) : Path {
$path = new Path();
$path->newFigure(array_shift($locations));
foreach ($locations as $location) {
$path->lineTo($location);
}
if ($extend) {
$path->lineTo(Point::at($size));
$path->lineTo(new Point(0, $size->height));
$path->closeFigure();
}
$path->end();
return $path;
}
protected function onDraw(Pen $pen, Size $areaSize, Point $clipPoint, Size $clipSize) {
$start = microtime(true);
$path = new Path();
$path->addRectangle($clipPoint, $areaSize);
$path->end();
$pen->fill($path, 0xFFFFFFFF);
$graphSize = $areaSize - 40;
$zero = Point::at(20);
$path = new Path();
$path->newFigure($zero);
$path->lineTo(new Point(20, 20 + $graphSize->height));
$path->lineTo(Point::at($graphSize + 20));
$path->end();
$stroke = new Stroke();
$stroke->setThickness(2);
$pen->stroke($path, 0x000000FF, $stroke);
$matrix = new Matrix();
$matrix->translate($zero);
$pen->transform($matrix);
$points = $this->getGraphPoints($graphSize);
$path = $this->getGraphPath($points, $graphSize, true);
$brush = new Brush($this->color->getColor());
$pen->fill($path, $brush);
$path = $this->getGraphPath($points, $graphSize, false);
$strokeColor = $brush->getColor();
$strokeColor->a /= 2;
$brush->setColor($strokeColor);
$pen->stroke($path, $brush, $stroke);
$layout = new Layout(sprintf(
"Drawn in %.5f seconds",
microtime(true) - $start),
$this->font->getFont(),
$clipSize->width
);
$layout->setColor(0x000000FF);
$pen->write(new Point(10, $graphSize->height - 30), $layout);
}
public function setColorSource(ColorButton $source) {
$this->color = $source;
}
public function getColorSource() {
return $this->color;
}
public function setFontSource(Combo $font) {
$this->font = $font;
}
public function getFontSource() {
return $this->font;
}
public function __construct(array &$sources, ColorButton $color = null, Font $font = null) {
$this->sources =& $sources;
$this->color = $color;
$this->font = $font;
}
private $sources;
private $color;
private $font;
};
$colorBox = new Entry();
$colorBox->setText("0x8892BFFF");
$colorButton = new class($histogram, $colorBox, new Color(0x8892BFFF)) extends ColorButton {
protected function onChange() {
$redrawColor = $this->getColor();
$this->entry->setText(sprintf(
"0x%02X%02X%02X%02X",
$redrawColor->r * 255,
$redrawColor->g * 255,
$redrawColor->b * 255,
$redrawColor->a * 255));
$this->histogram->redraw();
}
public function __construct(Area $histogram, Entry $entry, Color $color) {
$this->histogram = $histogram;
$this->entry = $entry;
$this->setColor($color);
$this->histogram->setColorSource($this);
}
private $histogram;
};
$redrawHistogram = function() use($histogram, $colorBox, $colorButton) {
$redrawColor = $colorButton->getColor();
$colorBox->setText(sprintf(
"0x%02X%02X%02X%02X",
$redrawColor->r * 255,
$redrawColor->g * 255,
$redrawColor->b * 255,
$redrawColor->a * 255));
$histogram->redraw();
};
$vBox->append(new Label("Change Data:"));
for ($i = 0; $i < 10; $i++) {
$dataSources[$i] = new class(0, 100, $redrawHistogram) extends Spin {
protected function onChange() {
($this->redraw)();
}
public function __construct($min, $max, Closure $redraw) {
parent::__construct($min, $max);
$this->redraw = $redraw;
}
private $redraw;
};
$dataSources[$i]->setValue(mt_rand(0, 100));
$vBox->append($dataSources[$i]);
}
$vBox->append(new Label("Choose Color:"));
$vBox->append($colorButton);
$colorBoxButton = new class("Set Color", $colorButton, $colorBox, $redrawHistogram) extends Button {
protected function onClick() {
$this->button->setColor(
new Color(
hexdec($this->entry->getText())));
($this->redraw)();
}
public function __construct(string $text, ColorButton $button, Entry $entry, Closure $redraw) {
$this->button = $button;
$this->entry = $entry;
$this->redraw = $redraw;
parent::__construct($text);
}
private $button;
private $entry;
private $redraw;
};
$vBox->append($colorBox);
$vBox->append($colorBoxButton);
$vBox->append(new Label("Choose Font:"));
$fontCombo = new class($histogram) extends Combo {
public function onSelected() {
$this->histogram->redraw();
}
public function __construct(Area $histogram) {
$this->families = UI\Draw\Text\Font\fontFamilies();
$this->histogram = $histogram;
sort($this->families);
foreach ($this->families as $family) {
$this->append($family);
}
$this->setSelected(0);
$this->histogram->setFontSource($this);
}
public function getFont(int $selected = -1, int $size = 12) {
return new Font(
new Descriptor($this->families[
$selected > -1 ? $families : $this->getSelected()
], $size));
}
private $items;
private $families;
};
$vBox->append($fontCombo);
$hBox->append($histogram, true);
$window->show();
UI\run();
?>