Skip to content

Commit 5362114

Browse files
authored
Merge pull request #232 from php-school/custom-item-styles
Custom item styles
2 parents db978a6 + 8cd1cab commit 5362114

14 files changed

+362
-25
lines changed

examples/custom-item-register.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
use PhpSchool\CliMenu\Builder\SplitItemBuilder;
4+
use PhpSchool\CliMenu\CliMenu;
5+
use PhpSchool\CliMenu\Builder\CliMenuBuilder;
6+
use PhpSchool\CliMenu\MenuItem\SelectableItem;
7+
use PhpSchool\CliMenu\Style\SelectableStyle;
8+
9+
require_once(__DIR__ . '/../vendor/autoload.php');
10+
11+
$itemCallable = function (CliMenu $menu) {
12+
echo $menu->getSelectedItem()->getText();
13+
};
14+
15+
class MyItem extends SelectableItem {
16+
17+
};
18+
19+
class MySelectableStyle extends SelectableStyle {
20+
21+
}
22+
23+
$myItem = new MyItem('MY CUSTOM ITEM 1', $itemCallable);
24+
$myItem2 = new MyItem('MY CUSTOM ITEM 2', $itemCallable);
25+
26+
$menu = (new CliMenuBuilder)
27+
->registerItemStyle(MyItem::class, new MySelectableStyle())
28+
->modifyStyle(MySelectableStyle::class, function (MySelectableStyle $style) {
29+
$style->setUnselectedMarker('--- ');
30+
$style->setSelectedMarker('*** ');
31+
})
32+
->setTitle('Showcasing Custom Items & Styles')
33+
->addMenuItem($myItem)
34+
->addMenuItem($myItem2)
35+
->addLineBreak()
36+
->addSplitItem(function (SplitItemBuilder $b) use ($itemCallable, $myItem) {
37+
$b->addItem('Split Item', $itemCallable);
38+
$b->addSubMenu('Split Item Submenu', function (CliMenuBuilder $b) use ($myItem) {
39+
$b->addMenuItem($myItem);
40+
});
41+
$b->addMenuItem($myItem);
42+
})
43+
->addLineBreak()
44+
->addSubMenu('Options', function (CliMenuBuilder $b) use ($myItem) {
45+
$b->addMenuItem($myItem);
46+
})
47+
->build();
48+
49+
$menu->open();

src/Builder/CliMenuBuilder.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
use PhpSchool\CliMenu\MenuStyle;
1919
use PhpSchool\CliMenu\Style\CheckboxStyle;
2020
use PhpSchool\CliMenu\Style\DefaultStyle;
21+
use PhpSchool\CliMenu\Style\ItemStyle;
2122
use PhpSchool\CliMenu\Style\RadioStyle;
2223
use PhpSchool\CliMenu\Style\SelectableStyle;
2324
use PhpSchool\CliMenu\Terminal\TerminalFactory;
2425
use PhpSchool\Terminal\Terminal;
26+
use function PhpSchool\CliMenu\Util\each;
2527

2628
/**
2729
* @author Michael Woodward <[email protected]>
@@ -80,6 +82,11 @@ class CliMenuBuilder
8082
*/
8183
private $autoShortcutsRegex = '/\[(.)\]/';
8284

85+
/**
86+
* @var array
87+
*/
88+
private $extraItemStyles = [];
89+
8390
/**
8491
* @var bool
8592
*/
@@ -187,6 +194,10 @@ public function addSubMenu(string $text, \Closure $callback) : self
187194
$builder->enableAutoShortcuts($this->autoShortcutsRegex);
188195
}
189196

197+
each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
198+
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
199+
});
200+
190201
$callback($builder);
191202

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

307+
each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
308+
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
309+
});
310+
296311
$callback($builder);
297312

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

604619
return $this;
605620
}
621+
622+
public function modifyStyle(string $styleClass, callable $itemCallable) : self
623+
{
624+
$itemCallable($this->menu->getItemStyle($styleClass));
625+
626+
return $this;
627+
}
628+
629+
public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : self
630+
{
631+
$this->menu->getStyleLocator()
632+
->registerItemStyle($itemClass, $itemStyle);
633+
634+
$this->extraItemStyles[] = ['class' => $itemClass, 'style' => $itemStyle];
635+
636+
return $this;
637+
}
606638
}

src/Builder/SplitItemBuilder.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
use PhpSchool\CliMenu\CliMenu;
66
use PhpSchool\CliMenu\MenuItem\CheckboxItem;
77
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
8+
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
89
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
910
use PhpSchool\CliMenu\MenuItem\RadioItem;
1011
use PhpSchool\CliMenu\MenuItem\SelectableItem;
1112
use PhpSchool\CliMenu\MenuItem\SplitItem;
1213
use PhpSchool\CliMenu\MenuItem\StaticItem;
14+
use PhpSchool\CliMenu\Style\ItemStyle;
15+
use function \PhpSchool\CliMenu\Util\each;
1316

1417
/**
1518
* @author Aydin Hassan <[email protected]>
@@ -42,6 +45,11 @@ class SplitItemBuilder
4245
*/
4346
private $autoShortcutsRegex = '/\[(.)\]/';
4447

48+
/**
49+
* @var array
50+
*/
51+
private $extraItemStyles = [];
52+
4553
public function __construct(CliMenu $menu)
4654
{
4755
$this->menu = $menu;
@@ -103,6 +111,10 @@ public function addSubMenu(string $text, \Closure $callback) : self
103111
$builder->enableAutoShortcuts($this->autoShortcutsRegex);
104112
}
105113

114+
each($this->extraItemStyles, function (int $i, array $extraItemStyle) use ($builder) {
115+
$builder->registerItemStyle($extraItemStyle['class'], $extraItemStyle['style']);
116+
});
117+
106118
$callback($builder);
107119

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

132+
public function addMenuItem(MenuItemInterface $item) : self
133+
{
134+
$this->splitItem->addItem($item);
135+
136+
return $this;
137+
}
138+
120139
public function setGutter(int $gutter) : self
121140
{
122141
$this->splitItem->setGutter($gutter);
@@ -135,6 +154,13 @@ public function enableAutoShortcuts(string $regex = null) : self
135154
return $this;
136155
}
137156

157+
public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : self
158+
{
159+
$this->extraItemStyles[] = ['class' => $itemClass, 'style' => $itemStyle];
160+
161+
return $this;
162+
}
163+
138164
public function build() : SplitItem
139165
{
140166
return $this->splitItem;

src/CliMenu.php

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use PhpSchool\Terminal\InputCharacter;
2323
use PhpSchool\Terminal\NonCanonicalReader;
2424
use PhpSchool\Terminal\Terminal;
25+
use function PhpSchool\CliMenu\Util\collect;
2526
use function PhpSchool\CliMenu\Util\each;
2627

2728
/**
@@ -666,6 +667,11 @@ public function getItemStyleForItem(MenuItemInterface $item) : ItemStyle
666667
return $this->itemStyleLocator->getStyleForMenuItem($item);
667668
}
668669

670+
public function getStyleLocator() : Locator
671+
{
672+
return $this->itemStyleLocator;
673+
}
674+
669675
public function importStyles(CliMenu $menu) : void
670676
{
671677
if (!$this->style->hasChangedFromDefaults()) {
@@ -744,22 +750,24 @@ private function guardSingleLine(string $text) : void
744750

745751
public function propagateStyles() : void
746752
{
747-
each(
748-
array_filter($this->items, function (MenuItemInterface $item) {
753+
collect($this->items)
754+
->filter(function (int $k, MenuItemInterface $item) {
755+
return $this->itemStyleLocator->hasStyleForMenuItem($item);
756+
})
757+
->filter(function (int $k, MenuItemInterface $item) {
749758
return !$item->getStyle()->hasChangedFromDefaults();
750-
}),
751-
function (int $index, $item) {
759+
})
760+
->each(function (int $k, $item) {
752761
$item->setStyle(clone $this->getItemStyleForItem($item));
753-
}
754-
);
762+
});
763+
755764

756-
each(
757-
array_filter($this->items, function (MenuItemInterface $item) {
765+
collect($this->items)
766+
->filter(function (int $k, MenuItemInterface $item) {
758767
return $item instanceof PropagatesStyles;
759-
}),
760-
function (int $index, PropagatesStyles $item) {
768+
})
769+
->each(function (int $k, $item) {
761770
$item->propagateStyles($this);
762-
}
763-
);
771+
});
764772
}
765773
}

src/MenuItem/SplitItem.php

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PhpSchool\CliMenu\Style\ItemStyle;
1010
use PhpSchool\CliMenu\Style\Selectable;
1111
use PhpSchool\CliMenu\Util\StringUtil;
12+
use function PhpSchool\CliMenu\Util\collect;
1213
use function PhpSchool\CliMenu\Util\each;
1314
use function PhpSchool\CliMenu\Util\mapWithKeys;
1415
use function PhpSchool\CliMenu\Util\max;
@@ -364,22 +365,23 @@ public function setStyle(DefaultStyle $style): void
364365
*/
365366
public function propagateStyles(CliMenu $parent): void
366367
{
367-
each(
368-
array_filter($this->getItems(), function (MenuItemInterface $item) {
368+
collect($this->items)
369+
->filter(function (int $k, MenuItemInterface $item) use ($parent) {
370+
return $parent->getStyleLocator()->hasStyleForMenuItem($item);
371+
})
372+
->filter(function (int $k, MenuItemInterface $item) {
369373
return !$item->getStyle()->hasChangedFromDefaults();
370-
}),
371-
function ($index, $item) use ($parent) {
374+
})
375+
->each(function (int $k, $item) use ($parent) {
372376
$item->setStyle(clone $parent->getItemStyleForItem($item));
373-
}
374-
);
377+
});
375378

376-
each(
377-
array_filter($this->getItems(), function (MenuItemInterface $item) {
379+
collect($this->items)
380+
->filter(function (int $k, MenuItemInterface $item) {
378381
return $item instanceof PropagatesStyles;
379-
}),
380-
function ($index, PropagatesStyles $item) use ($parent) {
382+
})
383+
->each(function (int $k, $item) use ($parent) {
381384
$item->propagateStyles($parent);
382-
}
383-
);
385+
});
384386
}
385387
}

src/Style/Exception/InvalidStyle.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public static function unregisteredItem(string $itemClass) : self
2222
{
2323
return new self("Menu item: '$itemClass' does not have a registered style class");
2424
}
25+
26+
public static function itemAlreadyRegistered(string $itemClass) : self
27+
{
28+
return new self("Menu item: '$itemClass' already has a registered style class");
29+
}
2530
}

src/Style/Locator.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ public function setStyle(ItemStyle $itemStyle, string $styleClass) : void
8787
$this->styles[$styleClass] = $itemStyle;
8888
}
8989

90+
public function hasStyleForMenuItem(MenuItemInterface $item) : bool
91+
{
92+
return isset($this->itemStyleMap[get_class($item)]);
93+
}
94+
9095
public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle
9196
{
9297
if (!isset($this->itemStyleMap[get_class($item)])) {
@@ -97,4 +102,14 @@ public function getStyleForMenuItem(MenuItemInterface $item) : ItemStyle
97102

98103
return $this->getStyle($styleClass);
99104
}
105+
106+
public function registerItemStyle(string $itemClass, ItemStyle $itemStyle) : void
107+
{
108+
if (isset($this->itemStyleMap[$itemClass])) {
109+
throw InvalidStyle::itemAlreadyRegistered($itemClass);
110+
}
111+
112+
$this->itemStyleMap[$itemClass] = get_class($itemStyle);
113+
$this->styles[get_class($itemStyle)] = $itemStyle;
114+
}
100115
}

src/Util/ArrayUtils.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ function mapWithKeys(array $array, callable $callback) : array
1616
return $arr;
1717
}
1818

19+
function filter(array $array, callable $callback) : array
20+
{
21+
return array_filter($array, function ($v, $k) use ($callback) {
22+
return $callback($k, $v);
23+
}, ARRAY_FILTER_USE_BOTH);
24+
}
25+
1926
function each(array $array, callable $callback) : void
2027
{
2128
foreach ($array as $k => $v) {
@@ -27,3 +34,8 @@ function max(array $items) : int
2734
{
2835
return count($items) > 0 ? \max($items) : 0;
2936
}
37+
38+
function collect(array $items) : Collection
39+
{
40+
return new Collection($items);
41+
}

src/Util/Collection.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpSchool\CliMenu\Util;
6+
7+
class Collection
8+
{
9+
/**
10+
* @var array
11+
*/
12+
private $items;
13+
14+
public function __construct(array $items)
15+
{
16+
$this->items = $items;
17+
}
18+
19+
public function map(callable $cb) : self
20+
{
21+
return new self(mapWithKeys($this->items, $cb));
22+
}
23+
24+
public function filter(callable $cb) : self
25+
{
26+
return new self(filter($this->items, $cb));
27+
}
28+
29+
public function values() : self
30+
{
31+
return new self(array_values($this->items));
32+
}
33+
34+
public function each(callable $cb) : self
35+
{
36+
each($this->items, $cb);
37+
38+
return $this;
39+
}
40+
41+
public function all() : array
42+
{
43+
return $this->items;
44+
}
45+
}

0 commit comments

Comments
 (0)