Skip to content

Custom item styles #232

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

Merged
merged 4 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions examples/custom-item-register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

use PhpSchool\CliMenu\Builder\SplitItemBuilder;
use PhpSchool\CliMenu\CliMenu;
use PhpSchool\CliMenu\Builder\CliMenuBuilder;
use PhpSchool\CliMenu\MenuItem\SelectableItem;
use PhpSchool\CliMenu\Style\SelectableStyle;

require_once(__DIR__ . '/../vendor/autoload.php');

$itemCallable = function (CliMenu $menu) {
echo $menu->getSelectedItem()->getText();
};

class MyItem extends SelectableItem {

};

class MySelectableStyle extends SelectableStyle {

}

$myItem = new MyItem('MY CUSTOM ITEM 1', $itemCallable);
$myItem2 = new MyItem('MY CUSTOM ITEM 2', $itemCallable);

$menu = (new CliMenuBuilder)
->registerItemStyle(MyItem::class, new MySelectableStyle())
->modifyStyle(MySelectableStyle::class, function (MySelectableStyle $style) {
$style->setUnselectedMarker('--- ');
$style->setSelectedMarker('*** ');
})
->setTitle('Showcasing Custom Items & Styles')
->addMenuItem($myItem)
->addMenuItem($myItem2)
->addLineBreak()
->addSplitItem(function (SplitItemBuilder $b) use ($itemCallable, $myItem) {
$b->addItem('Split Item', $itemCallable);
$b->addSubMenu('Split Item Submenu', function (CliMenuBuilder $b) use ($myItem) {
$b->addMenuItem($myItem);
});
$b->addMenuItem($myItem);
})
->addLineBreak()
->addSubMenu('Options', function (CliMenuBuilder $b) use ($myItem) {
$b->addMenuItem($myItem);
})
->build();

$menu->open();
32 changes: 32 additions & 0 deletions src/Builder/CliMenuBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
use PhpSchool\CliMenu\MenuStyle;
use PhpSchool\CliMenu\Style\CheckboxStyle;
use PhpSchool\CliMenu\Style\DefaultStyle;
use PhpSchool\CliMenu\Style\ItemStyle;
use PhpSchool\CliMenu\Style\RadioStyle;
use PhpSchool\CliMenu\Style\SelectableStyle;
use PhpSchool\CliMenu\Terminal\TerminalFactory;
use PhpSchool\Terminal\Terminal;
use function PhpSchool\CliMenu\Util\each;

/**
* @author Michael Woodward <[email protected]>
Expand Down Expand Up @@ -80,6 +82,11 @@ class CliMenuBuilder
*/
private $autoShortcutsRegex = '/\[(.)\]/';

/**
* @var array
*/
private $extraItemStyles = [];

/**
* @var bool
*/
Expand Down Expand Up @@ -187,6 +194,10 @@ public function addSubMenu(string $text, \Closure $callback) : self
$builder->enableAutoShortcuts($this->autoShortcutsRegex);
}

each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
});

$callback($builder);

$menu = $builder->build();
Expand Down Expand Up @@ -293,6 +304,10 @@ public function addSplitItem(\Closure $callback) : self
$builder->enableAutoShortcuts($this->autoShortcutsRegex);
}

each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
});

$callback($builder);

$this->menu->addItem($splitItem = $builder->build());
Expand Down Expand Up @@ -603,4 +618,21 @@ public function modifyRadioStyle(callable $itemCallable) : self

return $this;
}

public function modifyStyle(string $styleClass, callable $itemCallable) : self
{
$itemCallable($this->menu->getItemStyle($styleClass));

return $this;
}

public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : self
{
$this->menu->getStyleLocator()
->registerItemStyle($itemClass, $itemStyle);

$this->extraItemStyles[] = ['class' => $itemClass, 'style' => $itemStyle];

return $this;
}
}
26 changes: 26 additions & 0 deletions src/Builder/SplitItemBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
use PhpSchool\CliMenu\CliMenu;
use PhpSchool\CliMenu\MenuItem\CheckboxItem;
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
use PhpSchool\CliMenu\MenuItem\RadioItem;
use PhpSchool\CliMenu\MenuItem\SelectableItem;
use PhpSchool\CliMenu\MenuItem\SplitItem;
use PhpSchool\CliMenu\MenuItem\StaticItem;
use PhpSchool\CliMenu\Style\ItemStyle;
use function \PhpSchool\CliMenu\Util\each;

/**
* @author Aydin Hassan <[email protected]>
Expand Down Expand Up @@ -42,6 +45,11 @@ class SplitItemBuilder
*/
private $autoShortcutsRegex = '/\[(.)\]/';

/**
* @var array
*/
private $extraItemStyles = [];

public function __construct(CliMenu $menu)
{
$this->menu = $menu;
Expand Down Expand Up @@ -103,6 +111,10 @@ public function addSubMenu(string $text, \Closure $callback) : self
$builder->enableAutoShortcuts($this->autoShortcutsRegex);
}

each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
});

$callback($builder);

$menu = $builder->build();
Expand All @@ -117,6 +129,13 @@ public function addSubMenu(string $text, \Closure $callback) : self
return $this;
}

public function addMenuItem(MenuItemInterface $item) : self
{
$this->splitItem->addItem($item);

return $this;
}

public function setGutter(int $gutter) : self
{
$this->splitItem->setGutter($gutter);
Expand All @@ -135,6 +154,13 @@ public function enableAutoShortcuts(string $regex = null) : self
return $this;
}

public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : self
{
$this->extraItemStyles[] = ['class' => $itemClass, 'style' => $itemStyle];

return $this;
}

public function build() : SplitItem
{
return $this->splitItem;
Expand Down
32 changes: 20 additions & 12 deletions src/CliMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use PhpSchool\Terminal\InputCharacter;
use PhpSchool\Terminal\NonCanonicalReader;
use PhpSchool\Terminal\Terminal;
use function PhpSchool\CliMenu\Util\collect;
use function PhpSchool\CliMenu\Util\each;

/**
Expand Down Expand Up @@ -666,6 +667,11 @@ public function getItemStyleForItem(MenuItemInterface $item) : ItemStyle
return $this->itemStyleLocator->getStyleForMenuItem($item);
}

public function getStyleLocator() : Locator
{
return $this->itemStyleLocator;
}

public function importStyles(CliMenu $menu) : void
{
if (!$this->style->hasChangedFromDefaults()) {
Expand Down Expand Up @@ -744,22 +750,24 @@ private function guardSingleLine(string $text) : void

public function propagateStyles() : void
{
each(
array_filter($this->items, function (MenuItemInterface $item) {
collect($this->items)
->filter(function (int $k, MenuItemInterface $item) {
return $this->itemStyleLocator->hasStyleForMenuItem($item);
})
->filter(function (int $k, MenuItemInterface $item) {
return !$item->getStyle()->hasChangedFromDefaults();
}),
function (int $index, $item) {
})
->each(function (int $k, $item) {
$item->setStyle(clone $this->getItemStyleForItem($item));
}
);
});


each(
array_filter($this->items, function (MenuItemInterface $item) {
collect($this->items)
->filter(function (int $k, MenuItemInterface $item) {
return $item instanceof PropagatesStyles;
}),
function (int $index, PropagatesStyles $item) {
})
->each(function (int $k, $item) {
$item->propagateStyles($this);
}
);
});
}
}
26 changes: 14 additions & 12 deletions src/MenuItem/SplitItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpSchool\CliMenu\Style\ItemStyle;
use PhpSchool\CliMenu\Style\Selectable;
use PhpSchool\CliMenu\Util\StringUtil;
use function PhpSchool\CliMenu\Util\collect;
use function PhpSchool\CliMenu\Util\each;
use function PhpSchool\CliMenu\Util\mapWithKeys;
use function PhpSchool\CliMenu\Util\max;
Expand Down Expand Up @@ -364,22 +365,23 @@ public function setStyle(DefaultStyle $style): void
*/
public function propagateStyles(CliMenu $parent): void
{
each(
array_filter($this->getItems(), function (MenuItemInterface $item) {
collect($this->items)
->filter(function (int $k, MenuItemInterface $item) use ($parent) {
return $parent->getStyleLocator()->hasStyleForMenuItem($item);
})
->filter(function (int $k, MenuItemInterface $item) {
return !$item->getStyle()->hasChangedFromDefaults();
}),
function ($index, $item) use ($parent) {
})
->each(function (int $k, $item) use ($parent) {
$item->setStyle(clone $parent->getItemStyleForItem($item));
}
);
});

each(
array_filter($this->getItems(), function (MenuItemInterface $item) {
collect($this->items)
->filter(function (int $k, MenuItemInterface $item) {
return $item instanceof PropagatesStyles;
}),
function ($index, PropagatesStyles $item) use ($parent) {
})
->each(function (int $k, $item) use ($parent) {
$item->propagateStyles($parent);
}
);
});
}
}
5 changes: 5 additions & 0 deletions src/Style/Exception/InvalidStyle.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ public static function unregisteredItem(string $itemClass) : self
{
return new self("Menu item: '$itemClass' does not have a registered style class");
}

public static function itemAlreadyRegistered(string $itemClass) : self
{
return new self("Menu item: '$itemClass' already has a registered style class");
}
}
15 changes: 15 additions & 0 deletions src/Style/Locator.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ public function setStyle(ItemStyle $itemStyle, string $styleClass) : void
$this->styles[$styleClass] = $itemStyle;
}

public function hasStyleForMenuItem(MenuItemInterface $item) : bool
{
return isset($this->itemStyleMap[get_class($item)]);
}

public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle
{
if (!isset($this->itemStyleMap[get_class($item)])) {
Expand All @@ -97,4 +102,14 @@ public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle

return $this->getStyle($styleClass);
}

public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : void
{
if (isset($this->itemStyleMap[$itemClass])) {
throw InvalidStyle::itemAlreadyRegistered($itemClass);
}

$this->itemStyleMap[$itemClass] = get_class($itemStyle);
$this->styles[get_class($itemStyle)] = $itemStyle;
}
}
12 changes: 12 additions & 0 deletions src/Util/ArrayUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ function mapWithKeys(array $array, callable $callback) : array
return $arr;
}

function filter(array $array, callable $callback) : array
{
return array_filter($array, function ($v, $k) use ($callback) {
return $callback($k, $v);
}, ARRAY_FILTER_USE_BOTH);
}

function each(array $array, callable $callback) : void
{
foreach ($array as $k => $v) {
Expand All @@ -27,3 +34,8 @@ function max(array $items) : int
{
return count($items) > 0 ? \max($items) : 0;
}

function collect(array $items) : Collection
{
return new Collection($items);
}
45 changes: 45 additions & 0 deletions src/Util/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace PhpSchool\CliMenu\Util;

class Collection
{
/**
* @var array
*/
private $items;

public function __construct(array $items)
{
$this->items = $items;
}

public function map(callable $cb) : self
{
return new self(mapWithKeys($this->items, $cb));
}

public function filter(callable $cb) : self
{
return new self(filter($this->items, $cb));
}

public function values() : self
{
return new self(array_values($this->items));
}

public function each(callable $cb) : self
{
each($this->items, $cb);

return $this;
}

public function all() : array
{
return $this->items;
}
}
Loading