Skip to content

Commit

Permalink
Merge pull request #20 from simonhamp/refactor-layouts
Browse files Browse the repository at this point in the history
Refactor layouts
  • Loading branch information
simonhamp authored Jan 13, 2024
2 parents cb5241c + 1566ca5 commit 8812b17
Show file tree
Hide file tree
Showing 26 changed files with 564 additions and 247 deletions.
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

0 comments on commit 8812b17

Please sign in to comment.