Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor layouts #20

Merged
merged 25 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,10 @@ public function layout(Layout $layout): self
public function theme(Theme|BuiltInTheme $theme): self
{
if ($theme instanceof BuiltInTheme) {
$this->theme = $theme->load();
} else {
$this->theme = $theme;
$theme = $theme->load();
}

$this->theme = $theme;
return $this;
}

Expand Down
14 changes: 14 additions & 0 deletions src/Interfaces/Box.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace SimonHamp\TheOg\Interfaces;

use Intervention\Image\Interfaces\ImageInterface;

interface Box
{
public function name(string $name): static;

public function getName(): ?string;

public function render(ImageInterface $image): void;
}
14 changes: 9 additions & 5 deletions src/Interfaces/Layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ interface Layout
{
public function border(Border $border): self;

public function render(Config $config): Image;
public function callToAction(): ?string;

public function description(): ?string;

public function getCallToAction(): TextBox;
public function features(): void;

public function getDescription(): TextBox;
public function picture(): ?string;

public function render(Config $config): Image;

public function getTitle(): TextBox;
public function title(): string;

public function getUrl(): TextBox;
public function url(): ?string;
}
90 changes: 38 additions & 52 deletions src/Layout/AbstractLayout.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use SimonHamp\TheOg\Border;
use SimonHamp\TheOg\BorderPosition;
use SimonHamp\TheOg\Interfaces\Box as BoxInterface;
use SimonHamp\TheOg\Interfaces\Layout;
use SimonHamp\TheOg\Traits\RendersFeatures;

Expand All @@ -12,93 +13,73 @@ abstract class AbstractLayout implements Layout
use RendersFeatures;

protected Border $border;

protected BorderPosition $borderPosition;

protected int $borderWidth;

protected int $height;

protected int $padding;

protected int $width;

protected TextBox $callToAction;
protected TextBox $description;
protected TextBox $title;
protected TextBox $url;
/**
* @var array<string, BoxInterface>
*/
protected array $features = [];

abstract protected function features(string $feature, string $setting): mixed;
public function addFeature(BoxInterface $feature): void
{
$name = $feature->getName() ?? $this->generateFeatureName($feature);
$this->features[$name] = $feature;
}

public function border(Border $border): self
public function getFeature(string $name): ?BoxInterface
{
$this->border = $border;
return $this;
return $this->features[$name] ?? null;
}

public function callToAction(): string
public function border(Border $border): self
{
return $this->config->callToAction;
$this->border = $border;
return $this;
}

public function getCallToAction(): TextBox
public function callToAction(): ?string
{
return $this->callToAction ??= (new TextBox())
->text($this->callToAction())
->color($this->config->theme->getCallToActionColor())
->font($this->config->theme->getCallToActionFont())
->size($this->features('call_to_action', 'font_size'))
->box(...$this->features('call_to_action', 'dimensions'))
->position(...$this->features('call_to_action', 'layout'));
return $this->config->callToAction ?? null;
}

public function description(): string
public function description(): ?string
{
return $this->config->description;
return $this->config->description ?? null;
}

public function getDescription(): TextBox
public function picture(): ?string
{
return $this->description ??= (new TextBox())
->text($this->description())
->color($this->config->theme->getDescriptionColor())
->font($this->config->theme->getDescriptionFont())
->size($this->features('description', 'font_size'))
->box(...$this->features('description', 'dimensions'))
->position(...$this->features('description', 'layout'));
return $this->config->picture ?? null;
}

public function title(): string
{
return $this->config->title;
}

public function getTitle(): TextBox
public function url(): ?string
{
return $this->title ??= (new TextBox())
->text($this->title())
->color($this->config->theme->getTitleColor())
->font($this->config->theme->getTitleFont())
->size($this->features('title', 'font_size'))
->box(...$this->features('title', 'dimensions'))
->position(...$this->features('title', 'layout'));
}
if (!isset($this->config->url)) {
return null;
}

public function url(): string
{
return parse_url($this->config->url, PHP_URL_HOST) ?? $this->config->url;
}

public function getUrl(): TextBox
{
return $this->url ??= (new TextBox())
->text($this->url())
->color($this->config->theme->getUrlColor())
->font($this->config->theme->getUrlFont())
->size($this->features('url', 'font_size'))
->box(...$this->features('url', 'dimensions'))
->position(...$this->features('url', 'layout'));
}

/**
* The area within the canvas that we should be rendering content. This is just a convenience object
* The area within the canvas that we should be rendering content. This is just a convenience object to help layout
* of other features and is not normally rendered (it's not added to the $features list)
*/
public function mountArea(): Box
public function mountArea(): BoxInterface
{
return (new Box)
->box(
Expand Down Expand Up @@ -128,4 +109,9 @@ public function getBorderPosition(): BorderPosition

return $this->borderPosition;
}

protected function generateFeatureName(BoxInterface $feature): string
{
return $feature::class . '_' . (count($this->features) + 1);
}
}
144 changes: 101 additions & 43 deletions src/Layout/Box.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@

use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ImageInterface;
use SimonHamp\TheOg\Interfaces\Box as BoxInterface;

readonly class Box
readonly class Box implements BoxInterface
{
public Position $anchor;

public Rectangle $box;
public Position $pivot;

public string $name;

public Point $position;
public Box $relativeTo;

/**
* @var Closure<Box>
*/
public mixed $relativeTo;

public Position $relativeToPosition;

public Rectangle $renderedBox;

public function box(int $width, int $height): self
Expand All @@ -28,76 +40,88 @@ public function position(
int $y,
?callable $relativeTo = null,
Position $position = Position::TopLeft,
Position $pivot = Position::TopLeft
Position $anchor = Position::TopLeft
): self
{
$this->position = new Point($x, $y);

if ($relativeTo) {
$this->relativeTo = $relativeTo();
$this->relativeTo = $relativeTo;
$this->relativeToPosition = $position;
$this->pivot = $pivot;
}

$this->anchor = $anchor;

return $this;
}

public function calculatePosition(): Point
{
if (isset($this->relativeTo)) {
$position = $this->relativeTo->getPointForPosition($this->relativeToPosition);
$origin = ($this->relativeTo)();

if (! $origin instanceof Point) {
// new Point()
throw new \InvalidArgumentException(
'The relativeTo callback must return an instance of '.Point::class
);
}

return new Point(
$position->x() + $this->position->x(),
$position->y() + $this->position->y()
$origin->x() + $this->position->x() - $this->anchorOffset()->x(),
$origin->y() + $this->position->y() - $this->anchorOffset()->y()
);
}

return $this->position;
return $this->position
->moveX(-$this->anchorOffset()->x())
->moveY(-$this->anchorOffset()->y());
}

public function getPointForPosition(Position $position): Point
/**
* Get the absolute Point on the canvas for a given anchor position on the current box.
*/
public function anchor(?Position $position = null): Point
{
$box = $this->getRenderedBox();
if (! $position) {
$position = $this->anchor;
}

$origin = $this->calculatePosition();

$anchor = $this->anchorOffset($position);

return new Point($origin->x() + $anchor->x(), $origin->y() + $anchor->y());
}

protected function anchorOffset(?Position $position = null): Point
{
if (! $position) {
$position = $this->anchor;
}

// We can check pre-rendered boxes here because we know that we don't need the absolute position of the box yet
$box = $this->getPrerenderedBox() ?? $this->getRenderedBox();

$coordinates = match ($position) {
Position::BottomLeft => [
$origin->x(),
$origin->y() + $box->height()
],
Position::BottomRight => [
$origin->x() + $box->width(),
$origin->y() + $box->height()
],
Position::BottomLeft => [0, $box->height()],
Position::BottomRight => [$box->width(), $box->height()],
Position::Center => [
$origin->x() + intval(floor($box->width() / 2)),
$origin->y() + intval(floor($box->height() / 2)),
intval(floor($box->width() / 2)),
intval(floor($box->height() / 2))
],
Position::MiddleBottom => [
$origin->x() + intval(floor($box->width() / 2)),
$origin->y() + $box->height(),
],
Position::MiddleLeft => [
$origin->x(),
$origin->y() + intval(floor($box->height() / 2)),
intval(floor($box->width() / 2)),
$box->height()
],
Position::MiddleLeft => [0, intval(floor($box->height() / 2))],
Position::MiddleRight => [
$origin->x() + $box->width(),
$origin->y() + intval(floor($box->height() / 2)),
$box->width(),
intval(floor($box->height() / 2))
],
Position::MiddleTop => [
$origin->x() + intval(floor($box->width() / 2)),
$origin->y(),
],
Position::TopLeft => [
$origin->x(),
$origin->y()
],
Position::TopRight => [
$origin->x() + $box->width(),
$origin->y()
]
Position::MiddleTop => [intval(floor($box->width() / 2)), 0],
Position::TopLeft => [0, 0],
Position::TopRight => [$box->width(), 0]
};

return new Point(...$coordinates);
Expand All @@ -108,9 +132,43 @@ protected function getRenderedBox(): Rectangle
return $this->renderedBox ?? $this->box;
}

/**
* Get the box that will be rendered without calculating its position on the canvas.
*/
protected function getPrerenderedBox(): ?Rectangle
{
return null;
}

protected function setRenderedBox(Rectangle $box): self
{
$this->renderedBox = $box;
return $this;
}

public function render(ImageInterface $image): void
{
$position = $this->calculatePosition();

$this->box->setBackgroundColor('orange');
$this->box->setBorder('red');
$this->box->setPivot($position);

$image->drawRectangle(
$position->x(),
$position->y(),
$this->box,
);
}

public function name(string $name): static
{
$this->name = $name;
return $this;
}

public function getName(): ?string
{
return $this->name ?? null;
}
}
Loading