First Commit

This commit is contained in:
Thomas Peterson 2025-10-22 18:11:46 +02:00
commit 949cd47c96
54 changed files with 2351 additions and 0 deletions

51
.gitignore vendored Normal file
View File

@ -0,0 +1,51 @@
# Composer
/vendor/
composer.phar
composer.lock
# PHP
*.log
*.cache
.phpunit.result.cache
# IDE & Editor
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
*.sublime-project
*.sublime-workspace
# System Files
Thumbs.db
.DS_Store
desktop.ini
# Build & Distribution
/build/
/dist/
*.phar
# Environment
.env
.env.local
.env.*.local
# Temporary Files
/tmp/
/temp/
*.tmp
# Logs
/logs/
*.log
# Coverage & Testing
/coverage/
.phpunit.cache
# OS
.Trash-*
.nfs*

22
composer.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "phpnative/framework",
"type": "library",
"require": {
"ext-parallel": "^1.2",
"monolog/monolog": "^3.9",
"php": "^8.4",
"symfony/var-dumper": "^7.3"
},
"license": "MIT",
"autoload": {
"psr-4": {
"PHPNative\\": "src/"
}
},
"authors": [
{
"name": "Thomas Peterson",
"email": "info@thomas-peterson.de"
}
]
}

15
examples/Todo.php Normal file
View File

@ -0,0 +1,15 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
// Check PHP version
if (PHP_VERSION_ID < 80100) {
die("This demo requires PHP 8.1+ for Fiber support.\nYour version: " . PHP_VERSION . "\n");
}
$app = new \PHPNative\Framework\Application('ToDo Demo', 700, 500);
$container = new \PHPNative\Ui\Widget\Container(style: 'm-10 p-10 rounded-xl bg-lime-200');
$containerMenu = new \PHPNative\Ui\Widget\Container(style: 'bg-lime-700');
$container->addComponent($containerMenu);
$app->setRoot($container);
$app->run();

129
src/Core/Collection.php Normal file
View File

@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace PHPNative\Core;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
abstract class Collection implements IteratorAggregate
{
public function __construct(
private array $elements,
) {}
public static function createEmpty(): static
{
return new static([]);
}
public static function fromMap(array $items, callable $fn): static
{
return new static(array_map($fn, $items));
}
public function reduce(callable $fn, mixed $initial): mixed
{
return array_reduce($this->elements, $fn, $initial);
}
public function map(callable $fn): array
{
return array_map($fn, $this->elements);
}
public function each(callable $fn): void
{
array_walk($this->elements, $fn);
}
public function some(callable $fn): bool
{
foreach ($this->elements as $index => $element) {
if ($fn($element, $index, $this->elements)) {
return true;
}
}
return false;
}
public function setElements($elements): void
{
$this->elements = $elements;
}
public function filter(callable $fn): static
{
return new static(array_filter($this->elements, $fn, ARRAY_FILTER_USE_BOTH));
}
public function first(): mixed
{
return reset($this->elements);
}
public function last(): mixed
{
return end($this->elements);
}
public function count(): int
{
return count($this->elements);
}
public function isEmpty(): bool
{
return empty($this->elements);
}
public function add(mixed $element): void
{
$this->elements[] = $element;
}
public function values(): array
{
return array_values($this->elements);
}
public function items(): array
{
return $this->elements;
}
#[\Override]
public function getIterator(): Traversable
{
return new ArrayIterator($this->elements);
}
public function removeFirstItem(): void
{
if (count($this->elements) > 0) {
array_shift($this->elements);
}
}
public function sort(callable $callback): void
{
usort($this->elements, $callback);
}
public function removeItem($item): void
{
$temp = $this->filter(function ($value, $key) use ($item) {
return $value::class != $item::class;
});
$this->elements = $temp->items();
}
public function clear(): void
{
$this->elements = [];
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace PHPNative\Core;
abstract class TypedCollection extends Collection
{
public function __construct(array $elements = [])
{
parent::__construct($elements);
}
abstract protected function type(): string;
public function add(mixed $element): void
{
parent::add($element);
}
}

View File

@ -0,0 +1,209 @@
<?php
namespace PHPNative\Framework;
use PHPNative\Ui\Component;
use PHPNative\Ui\Viewport;
class Application
{
protected array $windows = [];
protected int $nextWindowId = 0;
protected $window;
protected $windowWidth;
protected $windowHeight;
protected $title;
protected $running = true;
protected $rootComponent = null;
private TextRenderer $textRenderer;
private $mouseX;
private $mouseY;
private Viewport $viewport;
private bool $shouldBeReLayouted = true;
public function __construct(string $title, int $width = 800, int $height = 600)
{
$this->title = $title;
$this->windowWidth = $width;
$this->windowHeight = $height;
// Create window
$this->window = rgfw_createWindow($title, 100, 100, $width, $height, RGFW_CENTER);
if (!$this->window) {
throw new \Exception('Failed to create window');
}
// Initialize RSGL renderer
if (!rsgl_init($this->window)) {
throw new \Exception('Failed to initialize RSGL renderer');
}
// Initialize text renderer
$this->textRenderer = new TextRenderer($this->window);
if (!$this->textRenderer->init()) {
error_log('Warning: Failed to initialize text renderer. Text rendering will not be available.');
}
// Get actual window size
$size = rgfw_window_getSize($this->window);
$this->windowWidth = $size[0];
$this->windowHeight = $size[1];
$this->viewport = new Viewport(
windowWidth: $this->windowWidth,
windowHeight: $this->windowHeight,
width: $this->windowWidth,
height: $this->windowHeight,
);
}
public function createWindow(): void
{
}
public function run(): void
{
while ($this->running && !rgfw_window_shouldClose($this->window)) {
$this->handleEvents();
$this->update();
$this->layout();
$this->render();
// Limit frame rate to ~60 FPS
usleep(16666);
}
$this->cleanup();
}
/**
* Handle window and input events
*/
protected function handleEvents(): void
{
while ($event = rgfw_window_checkEvent($this->window)) {
switch ($event['type']) {
case RGFW_quit:
$this->running = false;
break;
case RGFW_keyPressed:
$keyCode = $event['keyCode'] ?? 0;
if ($keyCode == RGFW_Escape) {
$this->running = false;
}
break;
case RGFW_windowResized:
// Update window dimensions (from event data)
$newWidth = $event[0] ?? $this->windowWidth;
$newHeight = $event[1] ?? $this->windowHeight;
$this->windowWidth = $newWidth;
$this->windowHeight = $newHeight;
// Update RSGL renderer size and viewport
// This ensures the renderer is properly configured for the new window size
rsgl_updateRendererSize($this->window);
// Update text renderer framebuffer
if ($this->textRenderer && $this->textRenderer->isInitialized()) {
$this->textRenderer->updateFramebuffer($newWidth, $newHeight);
}
$this->viewport->x = 0;
$this->viewport->y = 0;
$this->viewport->windowWidth = $newWidth;
$this->viewport->width = $newWidth;
$this->viewport->height = $newHeight;
$this->viewport->windowHeight = $newHeight;
$this->shouldBeReLayouted = true;
break;
case RGFW_mousePosChanged:
$this->mouseX = $event[0] ?? 0;
$this->mouseY = $event[1] ?? 0;
// Propagate mouse move to root component
if ($this->rootComponent) {
// $this->rootComponent->handleMouseMove($this->mouseX, $this->mouseY);
}
break;
case RGFW_mouseButtonPressed:
$button = $event['button'] ?? 0;
// Propagate click to root component
if ($this->rootComponent) {
$this->rootComponent->handleMouseClick($this->mouseX, $this->mouseY, $button);
}
break;
}
}
}
/**
* Update application state
*/
protected function update(): void
{
if ($this->rootComponent) {
$this->rootComponent->update();
}
}
/**
* Render the application
*/
protected function render(): void
{
rsgl_clear($this->window, 255, 255, 255, 0);
// Render root component tree
if ($this->rootComponent) {
$this->rootComponent->render($this->textRenderer);
$this->rootComponent->renderContent($this->textRenderer);
}
// Render to screen
rsgl_render($this->window);
rgfw_window_swapBuffers($this->window);
}
protected function layout(): void
{
// Render root component tree
if ($this->rootComponent && $this->shouldBeReLayouted) {
$this->rootComponent->setViewport($this->viewport);
$this->rootComponent->setWindow($this->window);
$this->rootComponent->layout($this->textRenderer);
$this->shouldBeReLayouted = false;
}
}
/**
* Clean up resources
*/
protected function cleanup(): void
{
if ($this->textRenderer) {
$this->textRenderer->free();
}
rsgl_close($this->window);
rgfw_window_close($this->window);
}
/**
* Stop the application
*/
public function quit(): void
{
$this->running = false;
}
public function setRoot(Component $component): self
{
$this->rootComponent = $component;
return $this;
}
}

View File

@ -0,0 +1,177 @@
<?php
/**
* TextRenderer - Wrapper for RFont text rendering
*/
namespace PHPNative\Framework;
class TextRenderer
{
private $window;
private bool $initialized = false;
private string $fontPath;
private int $fontSize;
/**
* Constructor
*
* @param resource $window RGFW window resource
*/
public function __construct($window)
{
$this->window = $window;
}
/**
* Initialize RFont with a font file
*
* @param string|null $fontPath Path to TTF font file
* @param int $fontSize Default font size
* @param int $atlasWidth Font atlas texture width (default 512)
* @param int $atlasHeight Font atlas texture height (default 512)
* @return bool Success
*/
public function init(
null|string $fontPath = null,
int $fontSize = 16,
int $atlasWidth = 512,
int $atlasHeight = 512,
): bool {
// Try to find a suitable font if none provided
if ($fontPath === null) {
$fontPath = $this->findSystemFont();
}
if (!file_exists($fontPath)) {
error_log("Font file not found: {$fontPath}");
return false;
}
$this->fontPath = $fontPath;
$this->fontSize = $fontSize;
$this->initialized = rfont_init($this->window, $fontPath, $fontSize, $atlasWidth, $atlasHeight);
if (!$this->initialized) {
error_log("Failed to initialize RFont with font: {$fontPath}");
return false;
}
// Set default color to white
$this->setColor(1.0, 1.0, 1.0, 1.0);
return true;
}
/**
* Find a system font
*
* @return string|null Font path or null if not found
*/
private function findSystemFont(): null|string
{
$fontPaths = [
// Linux
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
'/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf',
'/usr/share/fonts/TTF/DejaVuSans.ttf',
// macOS
'/System/Library/Fonts/Helvetica.ttc',
'/Library/Fonts/Arial.ttf',
// Windows
'C:/Windows/Fonts/arial.ttf',
'C:/Windows/Fonts/calibri.ttf',
];
foreach ($fontPaths as $path) {
if (file_exists($path)) {
return $path;
}
}
return null;
}
/**
* Draw text at position
*
* @param string $text Text to draw
* @param int $x X position
* @param int $y Y position
* @param int|null $size Font size
*/
public function drawText(string $text, int $x, int $y, null|int $size = null): void
{
if (!$this->initialized) {
return;
}
if ($size === null) {
$size = $this->fontSize;
}
rfont_drawText($this->window, $text, $x, $y, $size);
}
/**
* Set text color
*
* @param float $r Red (0.0 - 1.0)
* @param float $g Green (0.0 - 1.0)
* @param float $b Blue (0.0 - 1.0)
* @param float $a Alpha (0.0 - 1.0)
*/
public function setColor(float $r, float $g, float $b, float $a = 1.0): void
{
if (!$this->initialized) {
return;
}
rfont_setColor($this->window, $r, $g, $b, $a);
}
/**
* Update framebuffer size (call on window resize)
*
* @param int $width Window width
* @param int $height Window height
*/
public function updateFramebuffer(int $width, int $height): void
{
if (!$this->initialized) {
return;
}
rfont_setFramebuffer($this->window, $width, $height);
}
/**
* Free RFont resources
*/
public function free(): void
{
if ($this->initialized) {
rfont_free($this->window);
$this->initialized = false;
}
}
/**
* Check if initialized
*
* @return bool
*/
public function isInitialized(): bool
{
return $this->initialized;
}
/**
* Destructor
*/
public function __destruct()
{
$this->free();
}
}

11
src/Renderer/Widget.php Normal file
View File

@ -0,0 +1,11 @@
<?php
class Widget
{
public static function render(ViewPort $viewPort, Component $component, int $index = 1): Viewport
{
if ($component instanceof Container) {
return \PHPNative\Renderer\Widgets\Container::layout($viewPort, $view, $index);
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace PHPNative\Renderer\Widget;
use PHPNative\Ui\Viewport;
class Container
{
public static function layout(Viewport $viewport, \PHPNative\UI\Widget\Container $view, int $index = 0): Viewport
{
var_dump('test');
return $viewport;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PHPNative\Tailwind\Data;
enum Icon:int
{
case plus = 57669;
case save = 57697;
}

View File

@ -0,0 +1,288 @@
{
"slate": {
"50": "#f8fafc",
"100": "#f1f5f9",
"200": "#e2e8f0",
"300": "#cbd5e1",
"400": "#94a3b8",
"500": "#64748b",
"600": "#475569",
"700": "#334155",
"800": "#1e293b",
"900": "#0f172a",
"950": "#020617"
},
"gray": {
"50": "#f9fafb",
"100": "#f3f4f6",
"200": "#e5e7eb",
"300": "#d1d5db",
"400": "#9ca3af",
"500": "#6b7280",
"600": "#4b5563",
"700": "#374151",
"800": "#1f2937",
"900": "#111827",
"950": "#030712"
},
"zinc": {
"50": "#fafafa",
"100": "#f4f4f5",
"200": "#e4e4e7",
"300": "#d4d4d8",
"400": "#a1a1aa",
"500": "#71717a",
"600": "#52525b",
"700": "#3f3f46",
"800": "#27272a",
"900": "#18181b",
"950": "#09090b"
},
"neutral": {
"50": "#fafafa",
"100": "#f5f5f5",
"200": "#e5e5e5",
"300": "#d4d4d4",
"400": "#a3a3a3",
"500": "#737373",
"600": "#525252",
"700": "#404040",
"800": "#262626",
"900": "#171717",
"950": "#0a0a0a"
},
"stone": {
"50": "#fafaf9",
"100": "#f5f5f4",
"200": "#e7e5e4",
"300": "#d6d3d1",
"400": "#a8a29e",
"500": "#78716c",
"600": "#57534e",
"700": "#44403c",
"800": "#292524",
"900": "#1c1917",
"950": "#0c0a09"
},
"red": {
"50": "#fef2f2",
"100": "#fee2e2",
"200": "#fecaca",
"300": "#fca5a5",
"400": "#f87171",
"500": "#ef4444",
"600": "#dc2626",
"700": "#b91c1c",
"800": "#991b1b",
"900": "#7f1d1d",
"950": "#450a0a"
},
"orange": {
"50": "#fff7ed",
"100": "#ffedd5",
"200": "#fed7aa",
"300": "#fdba74",
"400": "#fb923c",
"500": "#f97316",
"600": "#ea580c",
"700": "#c2410c",
"800": "#9a3412",
"900": "#7c2d12",
"950": "#431407"
},
"amber": {
"50": "#fffbeb",
"100": "#fef3c7",
"200": "#fde68a",
"300": "#fcd34d",
"400": "#fbbf24",
"500": "#f59e0b",
"600": "#d97706",
"700": "#b45309",
"800": "#92400e",
"900": "#78350f",
"950": "#451a03"
},
"yellow": {
"50": "#fefce8",
"100": "#fef9c3",
"200": "#fef08a",
"300": "#fde047",
"400": "#facc15",
"500": "#eab308",
"600": "#ca8a04",
"700": "#a16207",
"800": "#854d0e",
"900": "#713f12",
"950": "#422006"
},
"lime": {
"50": "#f7fee7",
"100": "#ecfccb",
"200": "#d9f99d",
"300": "#bef264",
"400": "#a3e635",
"500": "#84cc16",
"600": "#65a30d",
"700": "#4d7c0f",
"800": "#3f6212",
"900": "#365314",
"950": "#1a2e05"
},
"green": {
"50": "#f0fdf4",
"100": "#dcfce7",
"200": "#bbf7d0",
"300": "#86efac",
"400": "#4ade80",
"500": "#22c55e",
"600": "#16a34a",
"700": "#15803d",
"800": "#166534",
"900": "#14532d",
"950": "#052e16"
},
"emerald": {
"50": "#ecfdf5",
"100": "#d1fae5",
"200": "#a7f3d0",
"300": "#6ee7b7",
"400": "#34d399",
"500": "#10b981",
"600": "#059669",
"700": "#047857",
"800": "#065f46",
"900": "#064e3b",
"950": "#022c22"
},
"teal": {
"50": "#f0fdfa",
"100": "#ccfbf1",
"200": "#99f6e4",
"300": "#5eead4",
"400": "#2dd4bf",
"500": "#14b8a6",
"600": "#0d9488",
"700": "#0f766e",
"800": "#115e59",
"900": "#134e4a",
"950": "#042f2e"
},
"cyan": {
"50": "#ecfeff",
"100": "#cffafe",
"200": "#a5f3fc",
"300": "#67e8f9",
"400": "#22d3ee",
"500": "#06b6d4",
"600": "#0891b2",
"700": "#0e7490",
"800": "#155e75",
"900": "#164e63",
"950": "#083344"
},
"sky": {
"50": "#f0f9ff",
"100": "#e0f2fe",
"200": "#bae6fd",
"300": "#7dd3fc",
"400": "#38bdf8",
"500": "#0ea5e9",
"600": "#0284c7",
"700": "#0369a1",
"800": "#075985",
"900": "#0c4a6e",
"950": "#082f49"
},
"blue": {
"50": "#eff6ff",
"100": "#dbeafe",
"200": "#bfdbfe",
"300": "#93c5fd",
"400": "#60a5fa",
"500": "#3b82f6",
"600": "#2563eb",
"700": "#1d4ed8",
"800": "#1e40af",
"900": "#1e3a8a",
"950": "#172554"
},
"indigo": {
"50": "#eef2ff",
"100": "#e0e7ff",
"200": "#c7d2fe",
"300": "#a5b4fc",
"400": "#818cf8",
"500": "#6366f1",
"600": "#4f46e5",
"700": "#4338ca",
"800": "#3730a3",
"900": "#312e81",
"950": "#1e1b4b"
},
"violet": {
"50": "#f5f3ff",
"100": "#ede9fe",
"200": "#ddd6fe",
"300": "#c4b5fd",
"400": "#a78bfa",
"500": "#8b5cf6",
"600": "#7c3aed",
"700": "#6d28d9",
"800": "#5b21b6",
"900": "#4c1d95",
"950": "#2e1065"
},
"purple": {
"50": "#faf5ff",
"100": "#f3e8ff",
"200": "#e9d5ff",
"300": "#d8b4fe",
"400": "#c084fc",
"500": "#a855f7",
"600": "#9333ea",
"700": "#7e22ce",
"800": "#6b21a8",
"900": "#581c87",
"950": "#3b0764"
},
"fuchsia": {
"50": "#fdf4ff",
"100": "#fae8ff",
"200": "#f5d0fe",
"300": "#f0abfc",
"400": "#e879f9",
"500": "#d946ef",
"600": "#c026d3",
"700": "#a21caf",
"800": "#86198f",
"900": "#701a75",
"950": "#4a044e"
},
"pink": {
"50": "#fdf2f8",
"100": "#fce7f3",
"200": "#fbcfe8",
"300": "#f9a8d4",
"400": "#f472b6",
"500": "#ec4899",
"600": "#db2777",
"700": "#be185d",
"800": "#9d174d",
"900": "#831843",
"950": "#500724"
},
"rose": {
"50": "#fff1f2",
"100": "#ffe4e6",
"200": "#fecdd3",
"300": "#fda4af",
"400": "#fb7185",
"500": "#f43f5e",
"600": "#e11d48",
"700": "#be123c",
"800": "#9f1239",
"900": "#881337",
"950": "#4c0519"
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace PHPNative\Tailwind;
use PHPNative\Renderer\Cache\Styles;
use PHPNative\Renderer\Viewport;
use PHPNative\Tailwind\Style\DirectionEnum;
use PHPNative\Tailwind\Style\Flex;
use PHPNative\Tailwind\Style\FlexTypeEnum;
use PHPNative\UI\View;
class LayoutParser
{
public static function sortByStyles(Styles $stylesCache, View $container, Viewport $viewport): View
{
$i = 0;
$flexOne = 0;
$container->getViews()->map(function (View $a) use (&$flexOne, &$i, &$stylesCache, $viewport) {
$aStyles = $stylesCache->getStyle($a->getId(), $viewport->windowMediaQuery, $a->getState(), $a->getStyle());
if(isset($aStyles[Flex::class]) && $aStyles[Flex::class]->type == FlexTypeEnum::one) {
$flexOne++;
}
$a->setRenderSort($i);
$i++;
});
$container->getViews()->sort(function (View $a, View $b) use (&$stylesCache, $viewport) {
$aStyles = $stylesCache->getStyle($a->getId(), $viewport->windowMediaQuery, $a->getState(), $a->getStyle());
$bStyles = $stylesCache->getStyle($b->getId(), $viewport->windowMediaQuery, $b->getState(), $b->getStyle());
if(isset($aStyles[Flex::class]) && $aStyles[Flex::class]->type == FlexTypeEnum::none &&
isset($bStyles[Flex::class]) && $bStyles[Flex::class]->type == FlexTypeEnum::one) {
return -1;
}
return 1;
});
$container->getViews()->countOne = $flexOne;
return $container;
}
public static function sortByRenderSort(View $container)
{
$container->getViews()->sort(function (View $a, View $b) {
if($a->getRenderSort() == $b->getRenderSort()) {
return 0;
}
return $a->getRenderSort() <=> $b->getRenderSort();
});
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace PHPNative\Tailwind\Model;
use PHPNative\Tailwind\Style\MediaQueryEnum;
use PHPNative\Tailwind\Style\StateEnum;
class Style
{
public function __construct(
public \PHPNative\Tailwind\Style\Style $style,
public MediaQueryEnum $mediaQuery = MediaQueryEnum::normal,
public StateEnum $state = StateEnum::normal
)
{
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace PHPNative\Tailwind\Model;
use PHPNative\Core\TypedCollection;
use PHPNative\Tailwind\Style\Border;
use PHPNative\Tailwind\Style\Flex;
use PHPNative\Tailwind\Style\Margin;
use PHPNative\Tailwind\Style\MediaQueryEnum;
use PHPNative\Tailwind\Style\Padding;
use PHPNative\Tailwind\Style\StateEnum;
use PHPNative\Tailwind\Style\Text;
class StyleCollection extends TypedCollection
{
protected function type(): string
{
return Style::class;
}
public function getValidStyles(MediaQueryEnum $mediaQueryEnum, StateEnum $state): array
{
$items = [];
foreach($this->items() as $style) {
if(($style->state == StateEnum::normal || $style->state === $state) && ($style->mediaQuery->value === 0 || $style->mediaQuery->value <= $mediaQueryEnum->value)) {
$items[] = $style;
}
}
return $this->merge($items);
}
private function merge(array $styles): array
{
$tmp = [];
foreach($styles as $style) {
if(isset($tmp[$style->style::class]) && $style->style::class === Padding::class) {
\PHPNative\Tailwind\Parser\Padding::merge($tmp[$style->style::class], $style->style);
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Margin::class) {
\PHPNative\Tailwind\Parser\Margin::merge($tmp[$style->style::class], $style->style);
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Border::class) {
\PHPNative\Tailwind\Parser\Border::merge($tmp[$style->style::class], $style->style);
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Text::class) {
\PHPNative\Tailwind\Parser\Text::merge($tmp[$style->style::class], $style->style);
}elseif(isset($tmp[$style->style::class]) && $style->style::class === Flex::class) {
\PHPNative\Tailwind\Parser\Flex::merge($tmp[$style->style::class], $style->style);
}else{
$tmp[$style->style::class] = $style->style;
}
}
return $tmp;
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
class Background implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Background
{
$color = new \PHPNative\Tailwind\Style\Color();
preg_match_all('/bg-(.*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$colorStyle = $output_array[1][0];
$color = Color::parse($colorStyle);
return new \PHPNative\Tailwind\Style\Background($color);
}
return null;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\Unit;
class Basis implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Basis
{
$value = -1;
$unit = Unit::Pixel;
$found = false;
preg_match_all('/basis-(\d*)\/(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$value1 = (int)$output_array[1][0];
$value2 = (int)$output_array[2][0];
$unit = Unit::Percent;
$value = (int)round(100/$value2*$value1,0);
$found = true;
}
preg_match_all('/basis-(\d*)/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = (int)$output_array[1][0];
}
preg_match_all('/basis-full/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = 100;
$unit = Unit::Percent;
}
if($value != -1) {
return new \PHPNative\Tailwind\Style\Basis($unit, $value);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
{
if($style->left != null) {
$class->left = $style->left;
}
if($style->right != null) {
$class->right = $style->right;
}
if($style->top != null) {
$class->top = $style->top;
}
if($style->bottom != null) {
$class->bottom = $style->bottom;
}
}
}

View File

@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
class Border implements Parser
{
public static function parse(string $style): null|\PHPNative\Tailwind\Style\Border
{
$color = new \PHPNative\Tailwind\Style\Color();
preg_match_all('/rounded-(t|b|l|r)-(none|sm|md|lg|xl|2xl|3xl)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$size = match ((string) $output_array[2][0]) {
'none' => 0,
'sm' => 2,
'md' => 6,
'lg' => 8,
'xl' => 12,
'2xl' => 16,
'3xl' => 24,
};
return match ((string) $output_array[1][0]) {
't' => new \PHPNative\Tailwind\Style\Border(
roundTopLeft: $size,
roundTopRight: $size,
),
'b' => new \PHPNative\Tailwind\Style\Border(
roundBottomLeft: $size,
roundBottomRight: $size,
),
'r' => new \PHPNative\Tailwind\Style\Border(
roundTopRight: $size,
roundBottomRight: $size,
),
'l' => new \PHPNative\Tailwind\Style\Border(
roundTopLeft: $size,
roundBottomLeft: $size,
),
};
}
preg_match_all('/rounded-(none|sm|md|lg|xl|2xl|3xl)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$size = match ((string) $output_array[1][0]) {
'none' => 0,
'sm' => 2,
'md' => 6,
'lg' => 8,
'xl' => 12,
'2xl' => 16,
'3xl' => 24,
};
return new \PHPNative\Tailwind\Style\Border(
roundTopLeft: $size,
roundTopRight: $size,
roundBottomLeft: $size,
roundBottomRight: $size,
);
}
preg_match_all('/rounded/', $style, $output_array);
if (count($output_array[0]) > 0) {
$size = 4;
return new \PHPNative\Tailwind\Style\Border(
roundTopLeft: $size,
roundTopRight: $size,
roundBottomLeft: $size,
roundBottomRight: $size,
);
}
preg_match_all('/border-([tblr])-(.*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
return match ((string) $output_array[1][0]) {
't' => new \PHPNative\Tailwind\Style\Border(true, top: (int) $output_array[2][0]),
'b' => new \PHPNative\Tailwind\Style\Border(true, bottom: (int) $output_array[2][0]),
'r' => new \PHPNative\Tailwind\Style\Border(true, right: (int) $output_array[2][0]),
'l' => new \PHPNative\Tailwind\Style\Border(true, left: (int) $output_array[2][0]),
};
}
preg_match_all('/border-(.*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$colorStyle = $output_array[1][0];
$color = Color::parse($colorStyle);
return new \PHPNative\Tailwind\Style\Border(false, $color);
}
preg_match_all('/border/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Border(
enabled: true,
left: 1,
right: 1,
top: 1,
bottom: 1,
);
}
return null;
}
public static function merge(
\PHPNative\Tailwind\Style\Border $style1,
\PHPNative\Tailwind\Style\Border $style2,
): void {
if ($style2->enabled && !$style1->enabled) {
$style1->enabled = true;
}
if ($style2->color->red != null) {
$style1->color->red = $style2->color->red;
}
if ($style2->color->green != null) {
$style1->color->green = $style2->color->green;
}
if ($style2->color->blue != null) {
$style1->color->blue = $style2->color->blue;
}
if ($style2->color->alpha != null) {
$style1->color->alpha = $style2->color->alpha;
}
if ($style2->top != null) {
$style1->top = $style2->top;
}
if ($style2->bottom != null) {
$style1->bottom = $style2->bottom;
}
if ($style2->left != null) {
$style1->left = $style2->left;
}
if ($style2->right != null) {
$style1->right = $style2->right;
}
if ($style2->roundTopLeft != null) {
$style1->roundTopLeft = $style2->roundTopLeft;
}
if ($style2->roundTopRight != null) {
$style1->roundTopRight = $style2->roundTopRight;
}
if ($style2->roundBottomLeft != null) {
$style1->roundBottomLeft = $style2->roundBottomLeft;
}
if ($style2->roundBottomRight != null) {
$style1->roundBottomRight = $style2->roundBottomRight;
}
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
class Color implements Parser
{
public static function parse(string $style): \PHPNative\Tailwind\Style\Color
{
$red = 0;
$green = 0;
$blue = 0;
$data = json_decode(file_get_contents(__DIR__ . '/../Data/colors.json'), true);
if($style == "black") {
return new \PHPNative\Tailwind\Style\Color(0, 0, 0);
}
if($style == "white") {
return new \PHPNative\Tailwind\Style\Color(255, 255, 255);
}
preg_match_all('/(\w{1,8})/', $style, $output_array);
if (count($output_array[0]) > 0) {
$color = (string)$output_array[1][0];
[$red, $green, $blue] = sscanf($data[$color]['500'], "#%02x%02x%02x");
}
preg_match_all('/(\w{1,8})-(\d{1,3})/', $style, $output_array);
if (count($output_array[0]) > 0) {
$color = (string)$output_array[1][0];
$variant = (string)$output_array[2][0];
[$red, $green, $blue] = sscanf($data[$color][$variant], "#%02x%02x%02x");
}
return new \PHPNative\Tailwind\Style\Color($red, $green, $blue);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\DirectionEnum;
use PHPNative\Tailwind\Style\FlexTypeEnum;
class Flex implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Style
{
preg_match_all('/flex-none/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Flex(type:FlexTypeEnum::none);
}
preg_match_all('/flex-1/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Flex(type:FlexTypeEnum::one);
}
preg_match_all('/flex-auto/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Flex(type:FlexTypeEnum::auto);
}
preg_match_all('/flex-col/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Flex(DirectionEnum::column);
}
preg_match_all('/(?!flex-col)(flex-row|flex)/', $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Flex(DirectionEnum::row);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Flex $class, \PHPNative\Tailwind\Style\Flex $style)
{
if($style->type != FlexTypeEnum::none) {
$class->type = $style->type;
}
if($style->direction != DirectionEnum::row) {
$class->direction = $style->direction;
}
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\Unit;
class Height implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Height
{
$value = -1;
$unit = Unit::Pixel;
$found = false;
preg_match_all('/h-(\d*)\/(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$value1 = (int)$output_array[1][0];
$value2 = (int)$output_array[2][0];
$unit = Unit::Percent;
$value = 100/$value2*$value1;
$found = true;
}
preg_match_all('/h-(\d*)/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = (int)$output_array[1][0];
}
preg_match_all('/(h-full|h-screen)/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = 100;
$unit = Unit::Percent;
}
if($value != -1) {
return new \PHPNative\Tailwind\Style\Height($unit, $value);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
{
if($style->left != null) {
$class->left = $style->left;
}
if($style->right != null) {
$class->right = $style->right;
}
if($style->top != null) {
$class->top = $style->top;
}
if($style->bottom != null) {
$class->bottom = $style->bottom;
}
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
class Margin implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Margin
{
$l = null;
$r = null;
$t = null;
$b = null;
preg_match_all('/m-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
$r = (int)$output_array[1][0];
$t = (int)$output_array[1][0];
$b = (int)$output_array[1][0];
}
preg_match_all('/mx-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
$r = (int)$output_array[1][0];
}
preg_match_all('/my-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$t = (int)$output_array[1][0];
$b = (int)$output_array[1][0];
}
preg_match_all('/mt-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$t = (int)$output_array[1][0];
}
preg_match_all('/mb-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$b = (int)$output_array[1][0];
}
preg_match_all('/ml-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
}
preg_match_all('/mr-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$r = (int)$output_array[1][0];
}
if($l != null || $r != null || $t != null || $b != null) {
return new \PHPNative\Tailwind\Style\Margin($l, $r, $t, $b);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Margin $class, \PHPNative\Tailwind\Style\Margin $style)
{
if($style->left != null) {
$class->left = $style->left;
}
if($style->right != null) {
$class->right = $style->right;
}
if($style->top != null) {
$class->top = $style->top;
}
if($style->bottom != null) {
$class->bottom = $style->bottom;
}
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace PHPNative\Tailwind\Parser;
class MediaQuery
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\MediaQueryEnum
{
preg_match_all("/^(sm|md|lg|xl|2xl)\:(.*)/", $style, $output_array);
if (count($output_array[0]) > 0) {
$query = strtolower(strrev($output_array[1][0]));
return \PHPNative\Tailwind\Style\MediaQueryEnum::{$query};
}
return null;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\OverflowEnum;
class Overflow
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Overflow
{
preg_match_all("/overflow-auto/", $style, $output_array);
if (count($output_array[0]) > 0) {
return new \PHPNative\Tailwind\Style\Overflow(x: OverflowEnum::auto, y: OverflowEnum::auto);
}
preg_match_all("/overflow-x-(auto|scroll|hidden|clip)/", $style, $output_array);
if (count($output_array[0]) > 0) {
$value = match($output_array[1][0]) {
'auto' => OverflowEnum::auto,
'scroll' => OverflowEnum::scroll,
'hidden' => OverflowEnum::hidden,
'clip' => OverflowEnum::clip
};
return new \PHPNative\Tailwind\Style\Overflow(x: $value);
}
preg_match_all("/overflow-y-(auto|scroll|hidden|clip)/", $style, $output_array);
if (count($output_array[0]) > 0) {
$value = match($output_array[1][0]) {
'auto' => OverflowEnum::auto,
'scroll' => OverflowEnum::scroll,
'hidden' => OverflowEnum::hidden,
'clip' => OverflowEnum::clip
};
return new \PHPNative\Tailwind\Style\Overflow(y: $value);
}
return null;
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
class Padding implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Padding
{
$l = null;
$r = null;
$t = null;
$b = null;
preg_match_all('/p-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
$r = (int)$output_array[1][0];
$t = (int)$output_array[1][0];
$b = (int)$output_array[1][0];
}
preg_match_all('/px-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
$r = (int)$output_array[1][0];
}
preg_match_all('/py-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$t = (int)$output_array[1][0];
$b = (int)$output_array[1][0];
}
preg_match_all('/pt-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$t = (int)$output_array[1][0];
}
preg_match_all('/pb-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$b = (int)$output_array[1][0];
}
preg_match_all('/pl-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$l = (int)$output_array[1][0];
}
preg_match_all('/pr-(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$r = (int)$output_array[1][0];
}
if($l != null || $r != null || $t != null || $b != null) {
return new \PHPNative\Tailwind\Style\Padding($l, $r, $t, $b);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
{
if($style->left != null) {
$class->left = $style->left;
}
if($style->right != null) {
$class->right = $style->right;
}
if($style->top != null) {
$class->top = $style->top;
}
if($style->bottom != null) {
$class->bottom = $style->bottom;
}
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\Style;
interface Parser
{
public static function parse(string $style): ?Style;
}

View File

@ -0,0 +1,18 @@
<?php
namespace PHPNative\Tailwind\Parser;
class State
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\StateEnum
{
preg_match_all("/(hover|focus|active)\:(.*)/", $style, $output_array);
if (count($output_array[0]) > 0) {
$query = strtolower($output_array[1][0]);
return \PHPNative\Tailwind\Style\StateEnum::{$query};
}
return null;
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\AlignEnum;
class Text implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Text
{
$color = new \PHPNative\Tailwind\Style\Color();
preg_match_all('/text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$size = match ((string)$output_array[1][0]) {
'xs' => 12,
'sm' => 14,
'base' => 16,
'lg' => 18,
'xl' => 20,
'2xl' => 24,
'3xl' => 30,
'4xl' => 36,
'5xl' => 48,
'6xl' => 60,
'7xl' => 72,
'8xl' => 96,
'9xl' => 128,
};
return new \PHPNative\Tailwind\Style\Text(size: $size);
}
preg_match_all('/text-(center|right|left)/', $style, $output_array);
if (count($output_array[0]) > 0) {
return match ((string)$output_array[1][0]) {
'left' => new \PHPNative\Tailwind\Style\Text(align: AlignEnum::left),
'right' => new \PHPNative\Tailwind\Style\Text(align: AlignEnum::right),
'center' => new \PHPNative\Tailwind\Style\Text(align: AlignEnum::center),
};
}
preg_match_all('/text-(.*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$colorStyle = $output_array[1][0];
$color = Color::parse($colorStyle);
return new \PHPNative\Tailwind\Style\Text($color);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Text $style1, \PHPNative\Tailwind\Style\Text $style2): void
{
if($style2->color->red != -1) {
$style1->color->red = $style2->color->red;
}
if($style2->color->green != -1) {
$style1->color->green = $style2->color->green;
}
if($style2->color->blue != -1) {
$style1->color->blue = $style2->color->blue;
}
if($style2->color->alpha != -1) {
$style1->color->alpha = $style2->color->alpha;
}
if($style2->size != 16) {
$style1->size = $style2->size;
}
if($style2->align != AlignEnum::left) {
$style1->align = $style2->align;
}
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Parser;
use PHPNative\Tailwind\Style\Unit;
class Width implements Parser
{
public static function parse(string $style): ?\PHPNative\Tailwind\Style\Width
{
$value = -1;
$unit = Unit::Pixel;
$found = false;
preg_match_all('/w-(\d*)\/(\d*)/', $style, $output_array);
if (count($output_array[0]) > 0) {
$value1 = (int)$output_array[1][0];
$value2 = (int)$output_array[2][0];
$unit = Unit::Percent;
$value = 100/$value2*$value1;
$found = true;
}
preg_match_all('/w-(\d*)/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = (int)$output_array[1][0];
}
preg_match_all('/(w-screen|w-full)/', $style, $output_array);
if (!$found && count($output_array[0]) > 0) {
$value = 100;
$unit = Unit::Percent;
}
if($value != -1) {
return new \PHPNative\Tailwind\Style\Width($unit, $value);
}
return null;
}
public static function merge(\PHPNative\Tailwind\Style\Padding $class, \PHPNative\Tailwind\Style\Padding $style)
{
if($style->left != null) {
$class->left = $style->left;
}
if($style->right != null) {
$class->right = $style->right;
}
if($style->top != null) {
$class->top = $style->top;
}
if($style->bottom != null) {
$class->bottom = $style->bottom;
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PHPNative\Tailwind\Style;
enum AlignEnum
{
case left;
case center;
case right;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Background implements Style
{
public function __construct(public Color $color = new Color())
{
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Basis implements Style
{
public function __construct(public Unit $unit = Unit::Pixel, public int $value = 0)
{
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Border implements Style
{
public function __construct(
public bool $enabled = false,
public Color $color = new Color(),
public null|int $left = null,
public null|int $right = null,
public null|int $top = null,
public null|int $bottom = null,
public null|int $roundTopLeft = null,
public null|int $roundTopRight = null,
public null|int $roundBottomLeft = null,
public null|int $roundBottomRight = null,
) {}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Color implements Style
{
public function __construct(public int $red = -1, public int $green = -1, public int $blue = -1, public int $alpha = 255)
{
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace PHPNative\Tailwind\Style;
enum DirectionEnum
{
case row;
case column;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Flex implements Style
{
public function __construct(public DirectionEnum $direction = DirectionEnum::row, public FlexTypeEnum $type = FlexTypeEnum::none)
{
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
enum FlexTypeEnum
{
case none;
case auto;
case initial;
case one;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Height implements Style
{
public function __construct(public Unit $unit = Unit::Pixel, public int $value = 0)
{
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Margin implements Style
{
public function __construct(public int|null $left = null, public int|null $right = null, public int|null $top = null, public int|null $bottom = null)
{
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PHPNative\Tailwind\Style;
class MediaQuery
{
public function __construct(public MediaQueryEnum $mediaQuery, public string $restStyle)
{
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace PHPNative\Tailwind\Style;
enum MediaQueryEnum: int
{
case normal = 0;
case ms = 640;
case dm = 768;
case gl = 1024;
case lx = 1280;
case lx2 = 1536;
public static function getFromPixel(int $windowWidth)
{
if($windowWidth > self::lx2->value) {
return self::lx2;
}
if($windowWidth > self::lx->value) {
return self::lx;
}
if($windowWidth > self::gl->value) {
return self::gl;
}
if($windowWidth > self::dm->value) {
return self::dm;
}
if($windowWidth > self::ms->value) {
return self::ms;
}
return self::normal;
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
use PHPNative\Tailwind\Style\OverflowEnum;
class Overflow implements Style
{
public function __construct(public OverflowEnum $x = OverflowEnum::hidden, public OverflowEnum $y = OverflowEnum::hidden)
{
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace PHPNative\Tailwind\Style;
enum OverflowEnum
{
case hidden;
case clip;
case scroll;
case auto;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Padding implements Style
{
public function __construct(public int|null $left = null, public int|null $right = null, public int|null $top = null, public int|null $bottom = null)
{
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace PHPNative\Tailwind\Style;
class State
{
public function __construct(public StateEnum $state, public string $restStyle)
{
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace PHPNative\Tailwind\Style;
enum StateEnum
{
case normal;
case hover;
case hoverfocus;
case focus;
case active;
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
interface Style
{
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Text implements Style
{
public function __construct(public Color $color = new Color(), public AlignEnum $align = AlignEnum::left, public int $size = 16)
{
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
enum Unit
{
case Pixel;
case Point;
case Percent;
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind\Style;
class Width implements Style
{
public function __construct(public Unit $unit = Unit::Pixel, public int $value = 0)
{
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace PHPNative\Tailwind;
use PHPNative\Tailwind\Model\Size;
use PHPNative\Tailwind\Model\StyleCollection;
use PHPNative\Tailwind\Style\Style;
class StyleParser
{
public static function parse($style): StyleCollection
{
$computed = new StyleCollection();
if($style === null || strlen(trim($style)) === 0) {
return $computed;
}
$styles = explode(" ", $style);
foreach($styles as $styleStr) {
$styleStr = trim($styleStr);
$style = self::parseSimpleStyle($styleStr);
$s = new \PHPNative\Tailwind\Model\Style($style);
$mq = \PHPNative\Tailwind\Parser\MediaQuery::parse($styleStr);
if($mq) {
$s->mediaQuery = $mq;
}
$state = \PHPNative\Tailwind\Parser\State::parse($styleStr);
if($state) {
$s->state = $state;
}
$computed->add($s);
}
return $computed;
}
private static function parseSimpleStyle(string $style): ?Style
{
if($pd = \PHPNative\Tailwind\Parser\Padding::parse($style)) {
return $pd;
}
if($m = \PHPNative\Tailwind\Parser\Margin::parse($style)) {
return $m;
}
if($o = \PHPNative\Tailwind\Parser\Overflow::parse($style)) {
return $o;
}
if($w = \PHPNative\Tailwind\Parser\Width::parse($style)) {
return $w;
}
if($h = \PHPNative\Tailwind\Parser\Height::parse($style)) {
return $h;
}
if($b = \PHPNative\Tailwind\Parser\Basis::parse($style)) {
return $b;
}
if($f = \PHPNative\Tailwind\Parser\Flex::parse($style)) {
return $f;
}
if($bg = \PHPNative\Tailwind\Parser\Background::parse($style)) {
return $bg;
}
if($t = \PHPNative\Tailwind\Parser\Text::parse($style)) {
return $t;
}
if($b = \PHPNative\Tailwind\Parser\Border::parse($style)) {
return $b;
}
return null;
}
}

134
src/Ui/Component.php Normal file
View File

@ -0,0 +1,134 @@
<?php
namespace PHPNative\Ui;
use PHPNative\Framework\TextRenderer;
use PHPNative\Tailwind\Style\Margin;
use PHPNative\Tailwind\Style\MediaQueryEnum;
use PHPNative\Tailwind\Style\Padding;
use PHPNative\Tailwind\Style\StateEnum;
use PHPNative\Tailwind\StyleParser;
abstract class Component
{
protected $children = [];
protected $window;
protected string $styles = '';
protected bool $visible = true;
protected Viewport $viewport;
protected array $computedStyles = [];
private Viewport $contentViewport;
public function setViewport(Viewport $viewport): void
{
$this->viewport = $viewport;
}
public function setWindow($window): void
{
$this->window = $window;
}
public function update(): void
{
foreach ($this->children as $child) {
$child->update();
}
}
public function layout(null|TextRenderer $textRenderer = null): void
{
$this->computedStyles = StyleParser::parse($this->style)->getValidStyles(
MediaQueryEnum::normal,
StateEnum::normal,
);
if (isset($this->computedStyles[Margin::class]) && ($m = $this->computedStyles[Margin::class])) {
$this->viewport->x += $m->left;
$this->viewport->width -= $m->right + $m->left;
$this->viewport->y += $m->top;
$this->viewport->height -= $m->bottom + $m->top;
}
$this->contentViewport = clone $this->viewport;
if (isset($this->computedStyles[Padding::class]) && ($p = $this->computedStyles[Padding::class])) {
$this->contentViewport->x += $p->left;
$this->contentViewport->width -= $p->right + $p->left;
$this->contentViewport->y += $p->top;
$this->contentViewport->height -= $p->bottom + $p->top;
}
foreach ($this->children as $child) {
$child->setViewport($this->contentViewport);
$child->layout($textRenderer);
}
}
public function render(null|TextRenderer $textRenderer = null): void
{
if (!$this->visible) {
return;
}
if (
isset($this->computedStyles[\PHPNative\Tailwind\Style\Background::class]) &&
($bg = $this->computedStyles[\PHPNative\Tailwind\Style\Background::class])
) {
rsgl_setColor($this->window, $bg->color->red, $bg->color->green, $bg->color->blue, $bg->color->alpha);
if (
isset($this->computedStyles[\PHPNative\Tailwind\Style\Border::class]) &&
($border = $this->computedStyles[\PHPNative\Tailwind\Style\Border::class])
) {
rsgl_drawRoundRectExF(
$this->window,
$this->viewport->x,
$this->viewport->y,
$this->viewport->width,
$this->viewport->height,
$border->roundTopLeft ?? 0,
$border->roundTopLeft ?? 0,
$border->roundTopRight ?? 0,
$border->roundTopRight ?? 0,
$border->roundBottomLeft ?? 0,
$border->roundBottomLeft ?? 0,
$border->roundBottomRight ?? 0,
$border->roundBottomRight ?? 0,
);
} else {
rsgl_drawRectF(
$this->window,
$this->viewport->x,
$this->viewport->y,
$this->viewport->width,
$this->viewport->height,
);
}
}
}
public function renderContent(null|TextRenderer $textRenderer = null): void
{
if (!$this->visible) {
return;
}
// Render children
foreach ($this->children as $child) {
$child->setWindow($this->window);
$child->render($textRenderer);
$child->renderContent($textRenderer);
}
}
public function addComponent(Component $component): void
{
$this->children[] = $component;
}
}

22
src/Ui/Viewport.php Normal file
View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace PHPNative\Ui;
use PHPNative\Tailwind\Style\MediaQueryEnum;
class Viewport
{
public function __construct(
public int $x = 0,
public int $y = 0,
public $width = 0,
public $height = 0,
public $windowWidth = 0,
public $windowHeight = 0,
public $addX = 0,
public $addY = 0,
public MediaQueryEnum $windowMediaQuery = MediaQueryEnum::normal,
) {}
}

View File

@ -0,0 +1,12 @@
<?php
namespace PHPNative\Ui\Widget;
use PHPNative\Ui\Component;
class Container extends Component
{
public function __construct(
public string $style = '',
) {}
}

7
src/Ui/Window.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace PHPNative\Ui;
class Window
{
}