diff --git a/examples/checkable-item.php b/examples/checkable-item.php index 663cdd33..b9603155 100644 --- a/examples/checkable-item.php +++ b/examples/checkable-item.php @@ -2,6 +2,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\Builder\CliMenuBuilder; +use PhpSchool\CliMenu\Style\CheckableStyle; require_once(__DIR__ . '/../vendor/autoload.php'); @@ -22,8 +23,10 @@ }) ->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) { $b->setTitle('Interpreted Languages') - ->setUncheckedMarker('[○] ') - ->setCheckedMarker('[●] ') + ->setCheckableStyle(function (CheckableStyle $style) { + $style->setMarkerOff('[○] ') + ->setMarkerOn('[●] '); + }) ->addCheckableItem('PHP', $itemCallable) ->addCheckableItem('Javascript', $itemCallable) ->addCheckableItem('Ruby', $itemCallable) diff --git a/examples/radio-item.php b/examples/radio-item.php index d3b7f0c9..b0add65c 100644 --- a/examples/radio-item.php +++ b/examples/radio-item.php @@ -2,9 +2,14 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\Builder\CliMenuBuilder; +use PhpSchool\CliMenu\Style\RadioStyle; require_once(__DIR__ . '/../vendor/autoload.php'); +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); + $itemCallable = function (CliMenu $menu) { echo $menu->getSelectedItem()->getText(); }; @@ -22,8 +27,10 @@ }) ->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) { $b->setTitle('Interpreted Languages') - ->setUnradioMarker('[ ] ') - ->setRadioMarker('[✔] ') + ->setRadioStyle(function (RadioStyle $style) { + $style->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); + }) ->addRadioItem('PHP', $itemCallable) ->addRadioItem('Javascript', $itemCallable) ->addRadioItem('Ruby', $itemCallable) diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index fd856698..7f511021 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -2,20 +2,27 @@ namespace PhpSchool\CliMenu\Builder; +use Closure; use PhpSchool\CliMenu\Action\ExitAction; use PhpSchool\CliMenu\Action\GoBackAction; use PhpSchool\CliMenu\Exception\InvalidShortcutException; use PhpSchool\CliMenu\MenuItem\AsciiArtItem; use PhpSchool\CliMenu\MenuItem\CheckableItem; +use PhpSchool\CliMenu\MenuItem\ItemStyleInterface; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuItemInterface; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; use PhpSchool\CliMenu\MenuItem\RadioItem; +use PhpSchool\CliMenu\MenuItem\SelectableInterface; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\CheckableStyle; +use PhpSchool\CliMenu\Style\RadioStyle; +use PhpSchool\CliMenu\Style\SelectableStyle; +use PhpSchool\CliMenu\Style\SplitStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\Terminal\Terminal; @@ -118,7 +125,10 @@ public function addItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->addMenuItem(new SelectableItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new SelectableItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getSelectableStyle()); + + $this->addMenuItem($item); return $this; } @@ -138,7 +148,10 @@ public function addCheckableItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->addMenuItem(new CheckableItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new CheckableItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getCheckableStyle()); + + $this->addMenuItem($item); return $this; } @@ -149,7 +162,10 @@ public function addRadioItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->addMenuItem(new RadioItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new RadioItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getRadioStyle()); + + $this->addMenuItem($item); return $this; } @@ -175,7 +191,7 @@ public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITI return $this; } - public function addSubMenu(string $text, \Closure $callback) : self + public function addSubMenu(string $text, Closure $callback) : self { $builder = self::newSubMenu($this->terminal); @@ -183,23 +199,14 @@ public function addSubMenu(string $text, \Closure $callback) : self $builder->enableAutoShortcuts($this->autoShortcutsRegex); } - $callback = $callback->bindTo($builder); $callback($builder); - $menu = $builder->build(); - $menu->setParent($this->menu); - - //we apply the parent theme if nothing was changed - //if no styles were changed in this sub-menu - if (!$menu->getStyle()->hasChangedFromDefaults()) { - $menu->setStyle($this->menu->getStyle()); - } + $menu = $this->createMenuClosure($builder); + + $item = (new MenuMenuItem($text, $menu, $builder->isMenuDisabled())) + ->setStyle($this->menu->getSelectableStyle()); - $this->menu->addItem($item = new MenuMenuItem( - $text, - $menu, - $builder->isMenuDisabled() - )); + $this->menu->addItem($item); $this->processItemShortcut($item); @@ -208,26 +215,79 @@ public function addSubMenu(string $text, \Closure $callback) : self public function addSubMenuFromBuilder(string $text, CliMenuBuilder $builder) : self { - $menu = $builder->build(); - $menu->setParent($this->menu); + $menu = $this->createMenuClosure($builder); - //we apply the parent theme if nothing was changed - //if no styles were changed in this sub-menu - if (!$menu->getStyle()->hasChangedFromDefaults()) { - $menu->setStyle($this->menu->getStyle()); - } + $item = (new MenuMenuItem($text, $menu, $builder->isMenuDisabled())) + ->setStyle($this->menu->getSelectableStyle()); - $this->menu->addItem($item = new MenuMenuItem( - $text, - $menu, - $builder->isMenuDisabled() - )); + $this->menu->addItem($item); $this->processItemShortcut($item); return $this; } + /** + * Create the submenu as a closure which is then unpacked in MenuMenuItem::showSubMenu + * This allows us to wait until all user-provided styles are parsed and apply them to nested items + * + * @param CliMenuBuilder|SplitItemBuilder $builder + * @return Closure + */ + protected function createMenuClosure($builder) : Closure + { + return function () use ($builder) { + $menu = $builder->build(); + + $menu->setParent($this->menu); + + // we apply the parent theme if nothing was changed + // if no styles were changed in this sub-menu + if (!$menu->getStyle()->hasChangedFromDefaults()) { + $menu->setStyle($this->menu->getStyle()); + } + + $menu->checkableStyle(function (CheckableStyle $style) { + $style->fromArray($this->menu->getCheckableStyle()->toArray()); + }); + + $menu->radioStyle(function (RadioStyle $style) { + $style->fromArray($this->menu->getRadioStyle()->toArray()); + }); + + $menu->selectableStyle(function (SelectableStyle $style) { + $style->fromArray($this->menu->getSelectableStyle()->toArray()); + }); + + // This will be filled with user-provided items + foreach ($menu->getItems() as $item) { + // Only set style for compatible items + if (!$item instanceof ItemStyleInterface) { + continue; + } + + // If item has a custom style, skip overriding + if ($item->getStyle()->getIsCustom()) { + continue; + } + + if ($item instanceof CheckableStyle) { + $item->setStyle(clone $menu->getCheckableStyle()); + } + + if ($item instanceof RadioStyle) { + $item->setStyle(clone $menu->getRadioStyle()); + } + + if ($item instanceof SelectableInterface) { + $item->setStyle(clone $menu->getSelectableStyle()); + } + } + + return $menu; + }; + } + public function enableAutoShortcuts(string $regex = null) : self { $this->autoShortcuts = true; @@ -294,7 +354,7 @@ private function processIndividualShortcut(MenuItemInterface $item, callable $ca } } - public function addSplitItem(\Closure $callback) : self + public function addSplitItem(Closure $callback) : self { $builder = new SplitItemBuilder($this->menu); @@ -302,9 +362,26 @@ public function addSplitItem(\Closure $callback) : self $builder->enableAutoShortcuts($this->autoShortcutsRegex); } + $builder->checkableStyle(function (CheckableStyle $style) { + $style->fromArray($this->menu->getCheckableStyle()->toArray()); + }); + + $builder->radioStyle(function (RadioStyle $style) { + $style->fromArray($this->menu->getRadioStyle()->toArray()); + }); + + $builder->selectableStyle(function (SelectableStyle $style) { + $style->fromArray($this->menu->getSelectableStyle()->toArray()); + }); + $callback($builder); - - $this->menu->addItem($splitItem = $builder->build()); + + $splitItem = $builder->build(); + $splitItem->setStyleCallback(function (SplitStyle $style) { + $style->fromArray($this->menu->getSelectableStyle()->toArray()); + }); + + $this->menu->addItem($splitItem); $this->processSplitItemShortcuts($splitItem); @@ -406,51 +483,47 @@ public function setMargin(int $margin) : self public function setUnselectedMarker(string $marker) : self { - $this->style->setUnselectedMarker($marker); - - return $this; - } - - public function setSelectedMarker(string $marker) : self - { - $this->style->setSelectedMarker($marker); - - return $this; - } - - public function setUncheckedMarker(string $marker) : self - { - $this->style->setUncheckedMarker($marker); - - return $this; - } + array_map(function (SelectableInterface $item) use ($marker) { + $item->getStyle() + ->setMarkerOff($marker); + }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) { + return $item instanceof SelectableInterface; + })); - public function setCheckedMarker(string $marker) : self - { - $this->style->setCheckedMarker($marker); + $this->menu->getSelectableStyle() + ->setMarkerOff($marker); return $this; } - public function setUnradioMarker(string $marker) : self + public function setSelectedMarker(string $marker) : self { - $this->style->setUnradioMarker($marker); - - return $this; - } + array_map(function (SelectableInterface $item) use ($marker) { + $item->getStyle() + ->setMarkerOn($marker); + }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) { + return $item instanceof SelectableInterface; + })); - public function setRadioMarker(string $marker) : self - { - $this->style->setRadioMarker($marker); + $this->menu->getSelectableStyle() + ->setMarkerOn($marker); return $this; } public function setItemExtra(string $extra) : self { - $this->style->setItemExtra($extra); + array_map(function (SelectableInterface $item) use ($extra) { + $item->getStyle() + ->setItemExtra($extra); + }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) { + return $item instanceof SelectableInterface; + })); + + $this->menu->getSelectableStyle() + ->setItemExtra($extra); - //if we customise item extra, it means we most likely want to display it + // if we customise item extra, it means we most likely want to display it $this->displayExtra(); return $this; @@ -519,10 +592,13 @@ private function getDefaultItems() : array { $actions = []; if ($this->subMenu) { - $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction); + $actions[] = (new SelectableItem($this->goBackButtonText, new GoBackAction)) + ->setStyle($this->menu->getSelectableStyle()); } - $actions[] = new SelectableItem($this->exitButtonText, new ExitAction); + $actions[] = (new SelectableItem($this->exitButtonText, new ExitAction)) + ->setStyle($this->menu->getSelectableStyle()); + return $actions; } @@ -535,7 +611,15 @@ public function disableDefaultItems() : self public function displayExtra() : self { - $this->style->setDisplaysExtra(true); + array_map(function (SelectableInterface $item) { + $item->getStyle() + ->setDisplaysExtra(true); + }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) { + return $item instanceof SelectableInterface; + })); + + $this->menu->getSelectableStyle() + ->setDisplaysExtra(true); return $this; } @@ -553,10 +637,68 @@ public function build() : CliMenu $this->menu->addItems($this->getDefaultItems()); } - if (!$this->style->getDisplaysExtra()) { - $this->style->setDisplaysExtra($this->itemsHaveExtra($this->menu->getItems())); + if (!$this->menu->getSelectableStyle()->getDisplaysExtra()) { + $displaysExtra = $this->itemsHaveExtra($this->menu->getItems()); + + array_map(function (SelectableInterface $item) use ($displaysExtra) { + $item->getStyle() + ->setDisplaysExtra($displaysExtra); + }, array_filter($this->menu->getItems(), function (MenuItemInterface $item) { + return $item instanceof SelectableInterface; + })); } return $this->menu; } + + /** + * Use as + * + ->checkableStyle(function (CheckableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function checkableStyle(callable $itemCallable) : self + { + $this->menu->checkableStyle($itemCallable); + + return $this; + } + + /** + * Use as + * + ->radioStyle(function (RadioStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function radioStyle(callable $itemCallable) : self + { + $this->menu->radioStyle($itemCallable); + + return $this; + } + + /** + * Use as + * + ->selectableStyle(function (SelectableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function selectableStyle(callable $itemCallable) : self + { + $this->menu->selectableStyle($itemCallable); + + return $this; + } } diff --git a/src/Builder/SplitItemBuilder.php b/src/Builder/SplitItemBuilder.php index 1ece321d..0166863b 100644 --- a/src/Builder/SplitItemBuilder.php +++ b/src/Builder/SplitItemBuilder.php @@ -2,14 +2,19 @@ namespace PhpSchool\CliMenu\Builder; +use Closure; use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\MenuItem\CheckableItem; use PhpSchool\CliMenu\MenuItem\LineBreakItem; use PhpSchool\CliMenu\MenuItem\MenuMenuItem; use PhpSchool\CliMenu\MenuItem\RadioItem; +use PhpSchool\CliMenu\MenuItem\SelectableInterface; use PhpSchool\CliMenu\MenuItem\SelectableItem; use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; +use PhpSchool\CliMenu\Style\CheckableStyle; +use PhpSchool\CliMenu\Style\RadioStyle; +use PhpSchool\CliMenu\Style\SelectableStyle; /** * @author Aydin Hassan @@ -42,10 +47,29 @@ class SplitItemBuilder */ private $autoShortcutsRegex = '/\[(.)\]/'; + /** + * @var CheckableStyle + */ + private $checkableStyle; + + /** + * @var RadioStyle + */ + private $radioStyle; + + /** + * @var SelectableStyle + */ + private $selectableStyle; + public function __construct(CliMenu $menu) { $this->menu = $menu; $this->splitItem = new SplitItem(); + + $this->checkableStyle = new CheckableStyle(); + $this->radioStyle = new RadioStyle(); + $this->selectableStyle = new SelectableStyle(); } public function addItem( @@ -54,7 +78,10 @@ public function addItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->splitItem->addItem(new SelectableItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new SelectableItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getSelectableStyle()); + + $this->splitItem->addItem($item); return $this; } @@ -65,7 +92,10 @@ public function addCheckableItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->splitItem->addItem(new CheckableItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new CheckableItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getCheckableStyle()); + + $this->splitItem->addItem($item); return $this; } @@ -76,7 +106,10 @@ public function addRadioItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->splitItem->addItem(new RadioItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new RadioItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getRadioStyle()); + + $this->splitItem->addItem($item); return $this; } @@ -105,8 +138,7 @@ public function addSubMenu(string $text, \Closure $callback) : self $callback($builder); - $menu = $builder->build(); - $menu->setParent($this->menu); + $menu = $this->createMenuClosure($builder); $this->splitItem->addItem(new MenuMenuItem( $text, @@ -139,4 +171,101 @@ public function build() : SplitItem { return $this->splitItem; } + + /** + * Use as + * + ->checkableStyle(function (CheckableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function checkableStyle(callable $itemCallable) : self + { + $this->menu->checkableStyle($itemCallable); + + return $this; + } + + /** + * Use as + * + ->radioStyle(function (RadioStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function radioStyle(callable $itemCallable) : self + { + $this->menu->radioStyle($itemCallable); + + return $this; + } + + /** + * Use as + * + ->selectableStyle(function (SelectableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function selectableStyle(callable $itemCallable) : self + { + $this->menu->selectableStyle($itemCallable); + + return $this; + } + + /** + * Create the submenu as a closure which is then unpacked in MenuMenuItem::showSubMenu + * This allows us to wait until all user-provided styles are parsed and apply them to nested items + * + * @param CliMenuBuilder $builder + * @return Closure + */ + protected function createMenuClosure(CliMenuBuilder $builder) : Closure + { + return function () use ($builder) { + $menu = $builder->build(); + + $menu->setParent($this->menu); + + // If user changed this style, persist to the menu so children CheckableItems may use it + if ($this->menu->getCheckableStyle()->getIsCustom()) { + $menu->checkableStyle(function (CheckableStyle $style) { + $style->fromArray($this->menu->getCheckableStyle()->toArray()); + }); + } + + // If user changed this style, persist to the menu so children RadioItems may use it + if ($this->menu->getRadioStyle()->getIsCustom()) { + $menu->radioStyle(function (RadioStyle $style) { + $style->fromArray($this->menu->getRadioStyle()->toArray()); + }); + } + + // If user changed this style, persist to the menu so children SelectableItems may use it + if ($this->menu->getSelectableStyle()->getIsCustom()) { + $menu->selectableStyle(function (SelectableStyle $style) { + $style->fromArray($this->menu->getSelectableStyle()->toArray()); + }); + } + + // This will be filled with user-provided items + foreach ($menu->getItems() as $item) { + if ($item instanceof SelectableInterface && !$item->getStyle()->getIsCustom()) { + $item->setStyle(clone $menu->getSelectableStyle()); + } + } + + return $menu; + }; + } } diff --git a/src/CliMenu.php b/src/CliMenu.php index 56174747..0a989872 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -14,6 +14,9 @@ use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\Dialogue\Confirm; use PhpSchool\CliMenu\Dialogue\Flash; +use PhpSchool\CliMenu\Style\CheckableStyle; +use PhpSchool\CliMenu\Style\RadioStyle; +use PhpSchool\CliMenu\Style\SelectableStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\CliMenu\Util\StringUtil as s; use PhpSchool\Terminal\InputCharacter; @@ -35,6 +38,21 @@ class CliMenu */ protected $style; + /** + * @var CheckableStyle + */ + private $checkableStyle; + + /** + * @var RadioStyle + */ + private $radioStyle; + + /** + * @var SelectableStyle + */ + private $selectableStyle; + /** * @var ?string */ @@ -90,10 +108,13 @@ public function __construct( Terminal $terminal = null, MenuStyle $style = null ) { - $this->title = $title; - $this->items = $items; - $this->terminal = $terminal ?: TerminalFactory::fromSystem(); - $this->style = $style ?: new MenuStyle($this->terminal); + $this->title = $title; + $this->items = $items; + $this->terminal = $terminal ?: TerminalFactory::fromSystem(); + $this->style = $style ?: new MenuStyle($this->terminal); + $this->checkableStyle = new CheckableStyle(); + $this->radioStyle = new RadioStyle(); + $this->selectableStyle = new SelectableStyle(); $this->selectFirstItem(); } @@ -638,6 +659,72 @@ public function setStyle(MenuStyle $style) : void $this->style = $style; } + public function getCheckableStyle() : CheckableStyle + { + return $this->checkableStyle; + } + + /** + * Use as + * + ->checkableStyle(function (CheckableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function checkableStyle(callable $itemCallable) : self + { + $itemCallable($this->checkableStyle); + + return $this; + } + + public function getRadioStyle() : RadioStyle + { + return $this->radioStyle; + } + + /** + * Use as + * + ->radioStyle(function (RadioStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function radioStyle(callable $itemCallable) : self + { + $itemCallable($this->radioStyle); + + return $this; + } + + public function getSelectableStyle() : SelectableStyle + { + return $this->selectableStyle; + } + + /** + * Use as + * + ->selectableStyle(function (SelectableStyle $style) { + $style->setMarkerOff('- '); + }) + * + * @param callable $itemCallable + * @return $this + */ + public function selectableStyle(callable $itemCallable) : self + { + $itemCallable($this->selectableStyle); + + return $this; + } + public function getCurrentFrame() : Frame { return $this->currentFrame; diff --git a/src/MenuItem/CheckableInterface.php b/src/MenuItem/CheckableInterface.php new file mode 100644 index 00000000..c8657415 --- /dev/null +++ b/src/MenuItem/CheckableInterface.php @@ -0,0 +1,7 @@ +selectAction = $selectAction; $this->showItemExtra = $showItemExtra; $this->disabled = $disabled; - } - - /** - * Execute the items callable if required - */ - public function getSelectAction() : ?callable - { - return function (CliMenu $cliMenu) { - $this->toggle(); - $cliMenu->redraw(); - return ($this->selectAction)($cliMenu); - }; + $this->style = new Style\CheckableStyle(); } - /** - * Return the raw string of text - */ - public function getText() : string + public function getStyle() : Style\ItemStyleInterface { - return $this->text; + return $this->style; } /** - * Set the raw string of text + * @param Style\CheckableStyle|Style\ItemStyleInterface $style + * @return $this */ - public function setText(string $text) : void + public function setStyle(Style\ItemStyleInterface $style) : self { - $this->text = $text; - } + $this->style = $style; - /** - * Can the item be selected - */ - public function canSelect() : bool - { - return !$this->disabled; - } - - public function showsItemExtra() : bool - { - return $this->showItemExtra; + return $this; } /** - * Enable showing item extra + * Execute the items callable if required */ - public function showItemExtra() : void + public function getSelectAction() : ?callable { - $this->showItemExtra = true; - } + return function (CliMenu $cliMenu) { + $this->toggle(); + $cliMenu->redraw(); - /** - * Disable showing item extra - */ - public function hideItemExtra() : void - { - $this->showItemExtra = false; + return ($this->selectAction)($cliMenu); + }; } } diff --git a/src/MenuItem/ItemStyleInterface.php b/src/MenuItem/ItemStyleInterface.php new file mode 100644 index 00000000..db98f82c --- /dev/null +++ b/src/MenuItem/ItemStyleInterface.php @@ -0,0 +1,12 @@ + */ -class MenuMenuItem implements MenuItemInterface +class MenuMenuItem implements MenuItemInterface, SelectableInterface { use SelectableTrait; /** - * @var CliMenu + * @var CliMenu|\Closure */ private $subMenu; - public function __construct(string $text, CliMenu $subMenu, bool $disabled = false) + public function __construct(string $text, $subMenu, bool $disabled = false) { $this->text = $text; $this->subMenu = $subMenu; $this->disabled = $disabled; + + $this->style = new Style\SelectableStyle(); } /** @@ -55,6 +57,10 @@ public function setText(string $text) : void */ public function getSubMenu() : CliMenu { + if ($this->subMenu instanceof \Closure) { + $this->subMenu = ($this->subMenu)(); + } + return $this->subMenu; } @@ -64,6 +70,11 @@ public function getSubMenu() : CliMenu public function showSubMenu(CliMenu $parentMenu) : void { $parentMenu->closeThis(); + + if ($this->subMenu instanceof \Closure) { + $this->subMenu = ($this->subMenu)(); + } + $this->subMenu->open(); } } diff --git a/src/MenuItem/RadioInterface.php b/src/MenuItem/RadioInterface.php new file mode 100644 index 00000000..f9e6dc46 --- /dev/null +++ b/src/MenuItem/RadioInterface.php @@ -0,0 +1,7 @@ +selectAction = $selectAction; $this->showItemExtra = $showItemExtra; $this->disabled = $disabled; + + $this->style = new Style\RadioStyle(); + } + + public function getStyle() : Style\ItemStyleInterface + { + return $this->style; + } + + /** + * @param Style\RadioStyle|Style\ItemStyleInterface $style + * @return $this + */ + public function setStyle(Style\ItemStyleInterface $style) : self + { + $this->style = $style; + + return $this; } /** @@ -72,49 +71,4 @@ function (RadioItem $checkableItem) { return ($this->selectAction)($cliMenu); }; } - - /** - * Return the raw string of text - */ - public function getText() : string - { - return $this->text; - } - - /** - * Set the raw string of text - */ - public function setText(string $text) : void - { - $this->text = $text; - } - - /** - * Can the item be selected - */ - public function canSelect() : bool - { - return !$this->disabled; - } - - public function showsItemExtra() : bool - { - return $this->showItemExtra; - } - - /** - * Enable showing item extra - */ - public function showItemExtra() : void - { - $this->showItemExtra = true; - } - - /** - * Disable showing item extra - */ - public function hideItemExtra() : void - { - $this->showItemExtra = false; - } } diff --git a/src/MenuItem/SelectableInterface.php b/src/MenuItem/SelectableInterface.php new file mode 100644 index 00000000..7b5ced8f --- /dev/null +++ b/src/MenuItem/SelectableInterface.php @@ -0,0 +1,7 @@ + */ -class SelectableItem implements MenuItemInterface +class SelectableItem implements MenuItemInterface, SelectableInterface { use SelectableTrait; @@ -24,6 +26,8 @@ public function __construct( $this->selectAction = $selectAction; $this->showItemExtra = $showItemExtra; $this->disabled = $disabled; + + $this->style = new Style\SelectableStyle(); } /** diff --git a/src/MenuItem/SelectableTrait.php b/src/MenuItem/SelectableTrait.php index c162493a..80a71aae 100644 --- a/src/MenuItem/SelectableTrait.php +++ b/src/MenuItem/SelectableTrait.php @@ -3,6 +3,7 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style; use PhpSchool\CliMenu\Util\StringUtil; /** @@ -10,30 +11,42 @@ */ trait SelectableTrait { - /** - * @var string - */ private $text = ''; + private $showItemExtra = false; + + private $disabled = false; + /** - * @var bool + * @var Style\ItemStyleInterface; */ - private $showItemExtra = false; + private $style; + + public function getStyle() : Style\ItemStyleInterface + { + return $this->style; + } /** - * @var bool + * @param Style\ItemStyleInterface|Style\SelectableStyle $style + * @return $this */ - private $disabled = false; + public function setStyle(Style\ItemStyleInterface $style) : self + { + $this->style = $style; + + return $this; + } /** * The output text for the item */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $marker = sprintf("%s", $style->getMarker($selected)); + $marker = sprintf("%s", $this->style->getMarker($selected)); - $length = $style->getDisplaysExtra() - ? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2) + $length = $this->style->getDisplaysExtra() + ? $style->getContentWidth() - (mb_strlen($this->style->getItemExtra()) + 2) : $style->getContentWidth(); $rows = explode( @@ -50,7 +63,7 @@ public function getRows(MenuStyle $style, bool $selected = false) : array if ($key === 0) { return $this->showItemExtra - ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $style->getItemExtra()) + ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $this->style->getItemExtra()) : $text; } diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index 2808b333..c5074a3e 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -4,6 +4,7 @@ use Assert\Assertion; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\SplitStyle; use PhpSchool\CliMenu\Util\StringUtil; /** @@ -31,6 +32,16 @@ class SplitItem implements MenuItemInterface */ private $gutter = 2; + /** + * @var SplitStyle + */ + private $style; + + /** + * @var \Callable + */ + private $styleCallback = null; + /** * @var array */ @@ -44,6 +55,13 @@ public function __construct(array $items = []) { $this->addItems($items); $this->setDefaultSelectedItem(); + + $this->style = new SplitStyle(); + } + + public function setStyleCallback(Callable $callable) + { + $this->styleCallback = $callable; } public function setGutter(int $gutter) : void @@ -107,6 +125,13 @@ private function setDefaultSelectedItem() : void */ public function getRows(MenuStyle $style, bool $selected = false) : array { + // Set local style with parent style + if ($this->styleCallback) { + ($this->styleCallback)($this->style); + + $this->styleCallback = null; + } + $numberOfItems = count($this->items); if ($numberOfItems === 0) { @@ -117,8 +142,8 @@ public function getRows(MenuStyle $style, bool $selected = false) : array $this->setDefaultSelectedItem(); } - $length = $style->getDisplaysExtra() - ? floor($style->getContentWidth() / $numberOfItems) - (mb_strlen($style->getItemExtra()) + 2) + $length = $this->style->getDisplaysExtra() + ? floor($style->getContentWidth() / $numberOfItems) - (mb_strlen($this->style->getItemExtra()) + 2) : floor($style->getContentWidth() / $numberOfItems); $length -= $this->gutter; @@ -128,29 +153,25 @@ public function getRows(MenuStyle $style, bool $selected = false) : array return $this->buildRows( array_map(function ($index, $item) use ($selected, $length, $style) { + /** @var ItemStyleInterface|MenuItemInterface $item */ $isSelected = $selected && $index === $this->selectedItemIndex; - if ($item instanceof CheckableItem) { - $markerType = $item->getChecked() - ? $style->getCheckedMarker() - : $style->getUncheckedMarker(); - } elseif ($item instanceof RadioItem) { - $markerType = $item->getChecked() - ? $style->getRadioMarker() - : $style->getUnradioMarker(); - } else { - $markerType = $style->getMarker($isSelected); - } + $itemStyle = $item->getStyle(); + + $getMarkerType = $item instanceof ToggableItemInterface + ? $item->getChecked() + : $isSelected; $marker = $item->canSelect() - ? sprintf('%s', $markerType) + ? sprintf('%s', $itemStyle->getMarker($getMarkerType)) : ''; $itemExtra = ''; - if ($style->getDisplaysExtra()) { + if ($itemStyle->getDisplaysExtra()) { + $itemExtraString = $itemStyle->getItemExtra(); $itemExtra = $item->showsItemExtra() - ? sprintf(' %s', $style->getItemExtra()) - : sprintf(' %s', str_repeat(' ', mb_strlen($style->getItemExtra()))); + ? sprintf(' %s', $itemExtraString) + : sprintf(' %s', str_repeat(' ', mb_strlen($itemExtraString))); } return $this->buildCell( @@ -176,7 +197,7 @@ public function getRows(MenuStyle $style, bool $selected = false) : array private function buildRows(array $cells, MenuStyle $style, int $missingLength, int $length) : array { - $extraPadLength = $style->getDisplaysExtra() ? 2 + mb_strlen($style->getItemExtra()) : 0; + $extraPadLength = $this->style->getDisplaysExtra() ? 2 + mb_strlen($this->style->getItemExtra()) : 0; return array_map( function ($i) use ($cells, $length, $missingLength, $extraPadLength) { diff --git a/src/MenuItem/StaticItem.php b/src/MenuItem/StaticItem.php index fc532a0f..3601baa8 100644 --- a/src/MenuItem/StaticItem.php +++ b/src/MenuItem/StaticItem.php @@ -1,8 +1,8 @@ text = $text; + + $this->style = new Style\StaticStyle(); + } + + public function getStyle() : Style\ItemStyleInterface + { + return $this->style; + } + + /** + * @param Style\ItemStyleInterface|Style\SelectableStyle $style + * @return $this + */ + public function setStyle(Style\ItemStyleInterface $style) : self + { + $this->style = $style; + + return $this; } /** diff --git a/src/MenuItem/ToggableTrait.php b/src/MenuItem/ToggableTrait.php index 6982d6b5..504988e7 100644 --- a/src/MenuItem/ToggableTrait.php +++ b/src/MenuItem/ToggableTrait.php @@ -3,33 +3,40 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\ItemStyleInterface; use PhpSchool\CliMenu\Util\StringUtil; trait ToggableTrait { /** - * @var bool + * @var callable */ + private $selectAction; + + private $text = ''; + + private $showItemExtra = false; + + private $disabled = false; + private $checked = false; + /** + * @var ItemStyleInterface; + */ + private $style; + /** * The output text for the item */ public function getRows(MenuStyle $style, bool $selected = false) : array { - $markerTypes = [ - true => $this instanceof CheckableItem - ? $style->getCheckedMarker() - : $style->getRadioMarker(), - false => $this instanceof CheckableItem - ? $style->getUncheckedMarker() - : $style->getUnradioMarker(), - ]; - - $marker = sprintf("%s", $markerTypes[$this->checked]); - - $length = $style->getDisplaysExtra() - ? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2) + $marker = sprintf("%s", $this->style->getMarker($this->checked)); + + $itemExtra = $this->style->getItemExtra(); + + $length = $this->style->getDisplaysExtra() + ? $style->getContentWidth() - (mb_strlen($itemExtra) + 2) : $style->getContentWidth(); $rows = explode( @@ -41,12 +48,12 @@ public function getRows(MenuStyle $style, bool $selected = false) : array ) ); - return array_map(function ($row, $key) use ($style, $length) { + return array_map(function ($row, $key) use ($style, $length, $itemExtra) { $text = $this->disabled ? $style->getDisabledItemText($row) : $row; if ($key === 0) { return $this->showItemExtra - ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $style->getItemExtra()) + ? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $itemExtra) : $text; } @@ -85,4 +92,49 @@ public function getChecked() : bool { return $this->checked; } + + /** + * Return the raw string of text + */ + public function getText() : string + { + return $this->text; + } + + /** + * Set the raw string of text + */ + public function setText(string $text) : void + { + $this->text = $text; + } + + /** + * Can the item be selected + */ + public function canSelect() : bool + { + return !$this->disabled; + } + + public function showsItemExtra() : bool + { + return $this->showItemExtra; + } + + /** + * Enable showing item extra + */ + public function showItemExtra() : void + { + $this->showItemExtra = true; + } + + /** + * Disable showing item extra + */ + public function hideItemExtra() : void + { + $this->showItemExtra = false; + } } diff --git a/src/MenuStyle.php b/src/MenuStyle.php index f2139bca..98064782 100644 --- a/src/MenuStyle.php +++ b/src/MenuStyle.php @@ -59,46 +59,6 @@ class MenuStyle */ protected $contentWidth; - /** - * @var string - */ - private $selectedMarker; - - /** - * @var string - */ - private $unselectedMarker; - - /** - * @var string - */ - private $checkedMarker; - - /** - * @var string - */ - private $uncheckedMarker; - - /** - * @var string - */ - private $radioMarker; - - /** - * @var string - */ - private $unradioMarker; - - /** - * @var string - */ - private $itemExtra; - - /** - * @var bool - */ - private $displaysExtra; - /** * @var string */ @@ -176,14 +136,6 @@ class MenuStyle 'paddingTopBottom' => 1, 'paddingLeftRight' => 2, 'margin' => 2, - 'selectedMarker' => '● ', - 'unselectedMarker' => '○ ', - 'checkedMarker' => '[✔] ', - 'uncheckedMarker' => '[ ] ', - 'radioMarker' => '[●] ', - 'unradioMarker' => '[○] ', - 'itemExtra' => '✔', - 'displaysExtra' => false, 'titleSeparator' => '=', 'borderTopWidth' => 0, 'borderRightWidth' => 0, @@ -251,14 +203,6 @@ public function __construct(Terminal $terminal = null) $this->setPaddingTopBottom(self::$defaultStyleValues['paddingTopBottom']); $this->setPaddingLeftRight(self::$defaultStyleValues['paddingLeftRight']); $this->setMargin(self::$defaultStyleValues['margin']); - $this->setSelectedMarker(self::$defaultStyleValues['selectedMarker']); - $this->setUnselectedMarker(self::$defaultStyleValues['unselectedMarker']); - $this->setCheckedMarker(self::$defaultStyleValues['checkedMarker']); - $this->setUncheckedMarker(self::$defaultStyleValues['uncheckedMarker']); - $this->setRadioMarker(self::$defaultStyleValues['radioMarker']); - $this->setUnradioMarker(self::$defaultStyleValues['unradioMarker']); - $this->setItemExtra(self::$defaultStyleValues['itemExtra']); - $this->setDisplaysExtra(self::$defaultStyleValues['displaysExtra']); $this->setTitleSeparator(self::$defaultStyleValues['titleSeparator']); $this->setBorderTopWidth(self::$defaultStyleValues['borderTopWidth']); $this->setBorderRightWidth(self::$defaultStyleValues['borderRightWidth']); @@ -276,14 +220,6 @@ public function hasChangedFromDefaults() : bool $this->paddingTopBottom, $this->paddingLeftRight, $this->margin, - $this->selectedMarker, - $this->unselectedMarker, - $this->checkedMarker, - $this->uncheckedMarker, - $this->radioMarker, - $this->unradioMarker, - $this->itemExtra, - $this->displaysExtra, $this->titleSeparator, $this->borderTopWidth, $this->borderRightWidth, @@ -557,110 +493,6 @@ public function getRightHandPadding(int $contentLength) : int return $rightPadding; } - public function getSelectedMarker() : string - { - return $this->selectedMarker; - } - - public function setSelectedMarker(string $marker) : self - { - $this->selectedMarker = $marker; - - return $this; - } - - public function getUnselectedMarker() : string - { - return $this->unselectedMarker; - } - - public function setUnselectedMarker(string $marker) : self - { - $this->unselectedMarker = $marker; - - return $this; - } - - /** - * Get the correct marker for the item - */ - public function getMarker(bool $selected) : string - { - return $selected ? $this->selectedMarker : $this->unselectedMarker; - } - - public function getCheckedMarker() : string - { - return $this->checkedMarker; - } - - public function setCheckedMarker(string $marker) : self - { - $this->checkedMarker = $marker; - - return $this; - } - - public function getUncheckedMarker() : string - { - return $this->uncheckedMarker; - } - - public function setUncheckedMarker(string $marker) : self - { - $this->uncheckedMarker = $marker; - - return $this; - } - - public function getRadioMarker() : string - { - return $this->radioMarker; - } - - public function setRadioMarker(string $marker) : self - { - $this->radioMarker = $marker; - - return $this; - } - - public function getUnradioMarker() : string - { - return $this->unradioMarker; - } - - public function setUnradioMarker(string $marker) : self - { - $this->unradioMarker = $marker; - - return $this; - } - - public function setItemExtra(string $itemExtra) : self - { - $this->itemExtra = $itemExtra; - - return $this; - } - - public function getItemExtra() : string - { - return $this->itemExtra; - } - - public function getDisplaysExtra() : bool - { - return $this->displaysExtra; - } - - public function setDisplaysExtra(bool $displaysExtra) : self - { - $this->displaysExtra = $displaysExtra; - - return $this; - } - public function getTitleSeparator() : string { return $this->titleSeparator; diff --git a/src/Style/CheckableStyle.php b/src/Style/CheckableStyle.php new file mode 100644 index 00000000..9a62925c --- /dev/null +++ b/src/Style/CheckableStyle.php @@ -0,0 +1,45 @@ + '[✔] ', + 'markerOff' => '[ ] ', + 'itemExtra' => '✔', + 'displaysExtra' => false, + ]; + + public function __construct() + { + $this->setMarkerOn(self::DEFAULT_STYLES['markerOn']); + $this->setMarkerOff(self::DEFAULT_STYLES['markerOff']); + $this->setItemExtra(self::DEFAULT_STYLES['itemExtra']); + $this->setDisplaysExtra(self::DEFAULT_STYLES['displaysExtra']); + + $this->custom = false; + } + + public function toArray() : array + { + return [ + 'markerOn' => $this->markerOn, + 'markerOff' => $this->markerOff, + 'itemExtra' => $this->itemExtra, + 'displaysExtra' => $this->displaysExtra, + ]; + } + + public function fromArray(array $style) : self + { + $this->markerOn = $style['markerOn'] ?? $this->markerOn; + $this->markerOff = $style['markerOff'] ?? $this->markerOff; + $this->itemExtra = $style['itemExtra'] ?? $this->itemExtra; + $this->displaysExtra = $style['displaysExtra'] ?? $this->displaysExtra; + + return $this; + } +} diff --git a/src/Style/ItemStyleInterface.php b/src/Style/ItemStyleInterface.php new file mode 100644 index 00000000..d3b6fa79 --- /dev/null +++ b/src/Style/ItemStyleInterface.php @@ -0,0 +1,38 @@ +custom; + } + + public function getMarker(bool $selected) : string + { + return $selected ? $this->markerOn : $this->markerOff; + } + + public function getMarkerOn() : string + { + return $this->markerOn; + } + + public function setMarkerOn(string $marker) : self + { + $this->custom = true; + + $this->markerOn = $marker; + + return $this; + } + + public function getMarkerOff() : string + { + return $this->markerOff; + } + + public function setMarkerOff(string $marker) : self + { + $this->custom = true; + + $this->markerOff = $marker; + + return $this; + } + + public function getItemExtra() : string + { + return $this->itemExtra; + } + + public function setItemExtra(string $itemExtra) : self + { + $this->custom = true; + + $this->itemExtra = $itemExtra; + + // if we customise item extra, it means we most likely want to display it + $this->setDisplaysExtra(true); + + return $this; + } + + public function getDisplaysExtra() : bool + { + return $this->displaysExtra; + } + + public function setDisplaysExtra(bool $displaysExtra) : self + { + $this->custom = true; + + $this->displaysExtra = $displaysExtra; + + return $this; + } +} diff --git a/src/Style/RadioStyle.php b/src/Style/RadioStyle.php new file mode 100644 index 00000000..1b877345 --- /dev/null +++ b/src/Style/RadioStyle.php @@ -0,0 +1,45 @@ + '[●] ', + 'markerOff' => '[○] ', + 'itemExtra' => '✔', + 'displaysExtra' => false, + ]; + + public function __construct() + { + $this->setMarkerOn(self::DEFAULT_STYLES['markerOn']); + $this->setMarkerOff(self::DEFAULT_STYLES['markerOff']); + $this->setItemExtra(self::DEFAULT_STYLES['itemExtra']); + $this->setDisplaysExtra(self::DEFAULT_STYLES['displaysExtra']); + + $this->custom = false; + } + + public function toArray(): array + { + return [ + 'markerOn' => $this->markerOn, + 'markerOff' => $this->markerOff, + 'itemExtra' => $this->itemExtra, + 'displaysExtra' => $this->displaysExtra, + ]; + } + + public function fromArray(array $style) : self + { + $this->markerOn = $style['markerOn'] ?? $this->markerOn; + $this->markerOff = $style['markerOff'] ?? $this->markerOff; + $this->itemExtra = $style['itemExtra'] ?? $this->itemExtra; + $this->displaysExtra = $style['displaysExtra'] ?? $this->displaysExtra; + + return $this; + } +} diff --git a/src/Style/SelectableStyle.php b/src/Style/SelectableStyle.php new file mode 100644 index 00000000..388c3823 --- /dev/null +++ b/src/Style/SelectableStyle.php @@ -0,0 +1,45 @@ + '● ', + 'markerOff' => '○ ', + 'itemExtra' => '✔', + 'displaysExtra' => false, + ]; + + public function __construct() + { + $this->setMarkerOn(self::DEFAULT_STYLES['markerOn']); + $this->setMarkerOff(self::DEFAULT_STYLES['markerOff']); + $this->setItemExtra(self::DEFAULT_STYLES['itemExtra']); + $this->setDisplaysExtra(self::DEFAULT_STYLES['displaysExtra']); + + $this->custom = false; + } + + public function toArray(): array + { + return [ + 'markerOn' => $this->markerOn, + 'markerOff' => $this->markerOff, + 'itemExtra' => $this->itemExtra, + 'displaysExtra' => $this->displaysExtra, + ]; + } + + public function fromArray(array $style) : self + { + $this->markerOn = $style['markerOn'] ?? $this->markerOn; + $this->markerOff = $style['markerOff'] ?? $this->markerOff; + $this->itemExtra = $style['itemExtra'] ?? $this->itemExtra; + $this->displaysExtra = $style['displaysExtra'] ?? $this->displaysExtra; + + return $this; + } +} diff --git a/src/Style/SplitStyle.php b/src/Style/SplitStyle.php new file mode 100644 index 00000000..56573df1 --- /dev/null +++ b/src/Style/SplitStyle.php @@ -0,0 +1,67 @@ + '', + 'displaysExtra' => false, + ]; + + public function __construct() + { + $this->setItemExtra(self::DEFAULT_STYLES['itemExtra']); + $this->setDisplaysExtra(self::DEFAULT_STYLES['displaysExtra']); + } + + public function getItemExtra() : string + { + return $this->itemExtra; + } + + public function setItemExtra(string $itemExtra) : self + { + $this->itemExtra = $itemExtra; + + return $this; + } + + public function getDisplaysExtra() : bool + { + return $this->displaysExtra; + } + + public function setDisplaysExtra(bool $displaysExtra) : self + { + $this->displaysExtra = $displaysExtra; + + return $this; + } + + public function toArray() : array + { + return [ + 'itemExtra' => $this->itemExtra, + 'displaysExtra' => $this->displaysExtra, + ]; + } + + public function fromArray(array $style) : self + { + $this->itemExtra = $style['itemExtra'] ?? $this->itemExtra; + $this->displaysExtra = $style['displaysExtra'] ?? $this->displaysExtra; + + return $this; + } +} diff --git a/src/Style/StaticStyle.php b/src/Style/StaticStyle.php new file mode 100644 index 00000000..8a1df7cd --- /dev/null +++ b/src/Style/StaticStyle.php @@ -0,0 +1,60 @@ + '', + 'markerOff' => '', + 'itemExtra' => '', + 'displaysExtra' => false, + ]; + + public function __construct() + { + $this->fromArray([]); + } + + public function setMarkerOn(string $marker) : self + { + return $this; + } + + public function setMarkerOff(string $marker) : self + { + return $this; + } + + public function setItemExtra(string $itemExtra) : self + { + return $this; + } + + public function setDisplaysExtra(bool $displaysExtra) : self + { + return $this; + } + + public function toArray(): array + { + return [ + 'markerOn' => $this->markerOn, + 'markerOff' => $this->markerOff, + 'itemExtra' => $this->itemExtra, + 'displaysExtra' => $this->displaysExtra, + ]; + } + + public function fromArray(array $style) : self + { + $this->markerOn = self::DEFAULT_STYLES['markerOn']; + $this->markerOff = self::DEFAULT_STYLES['markerOff']; + $this->itemExtra = self::DEFAULT_STYLES['itemExtra']; + $this->displaysExtra = self::DEFAULT_STYLES['displaysExtra']; + + return $this; + } +} diff --git a/test/Builder/CliMenuBuilderTest.php b/test/Builder/CliMenuBuilderTest.php index 60a6b77f..d7404b60 100644 --- a/test/Builder/CliMenuBuilderTest.php +++ b/test/Builder/CliMenuBuilderTest.php @@ -67,8 +67,6 @@ public function testModifyStyles() : void $builder->setMargin(4); $builder->setUnselectedMarker('>'); $builder->setSelectedMarker('x'); - $builder->setUncheckedMarker('-'); - $builder->setCheckedMarker('+'); $builder->setItemExtra('*'); $builder->setTitleSeparator('-'); @@ -81,12 +79,11 @@ public function testModifyStyles() : void self::assertEquals(4, $style->getPaddingTopBottom()); self::assertEquals(1, $style->getPaddingLeftRight()); self::assertEquals(4, $style->getMargin()); - self::assertEquals('>', $style->getUnselectedMarker()); - self::assertEquals('x', $style->getSelectedMarker()); - self::assertEquals('-', $style->getUncheckedMarker()); - self::assertEquals('+', $style->getCheckedMarker()); - self::assertEquals('*', $style->getItemExtra()); self::assertEquals('-', $style->getTitleSeparator()); + + self::assertEquals('>', $menu->getSelectableStyle()->getMarkerOff()); + self::assertEquals('x', $menu->getSelectableStyle()->getMarkerOn()); + self::assertEquals('*', $menu->getSelectableStyle()->getItemExtra()); } public function testSetBorderShorthandFunction() : void @@ -808,9 +805,9 @@ public function testAddSubMenuWithClosureBinding() : void { $builder = new CliMenuBuilder; $builder->disableDefaultItems(); - $builder->addSubMenu('My SubMenu', function () { - $this->disableDefaultItems(); - $this->addItem('My Item', function () { + $builder->addSubMenu('My SubMenu', function (CliMenuBuilder $b) { + $b->disableDefaultItems(); + $b->addItem('My Item', function () { }); }); @@ -858,7 +855,7 @@ public function testDisplayExtraForcesExtraToBeDisplayedWhenNoItemsDisplayExtra( $menu = $builder->build(); - self::assertTrue($menu->getStyle()->getDisplaysExtra()); + self::assertTrue($menu->getSelectableStyle()->getDisplaysExtra()); } public function testModifyingItemExtraForcesExtraToBeDisplayedWhenNoItemsDisplayExtra() : void @@ -872,7 +869,7 @@ public function testModifyingItemExtraForcesExtraToBeDisplayedWhenNoItemsDisplay $menu = $builder->build(); - self::assertTrue($menu->getStyle()->getDisplaysExtra()); + self::assertTrue($menu->getSelectableStyle()->getDisplaysExtra()); } diff --git a/test/MenuItem/CheckableItemTest.php b/test/MenuItem/CheckableItemTest.php index 9d28a0f1..5ab00ee9 100644 --- a/test/MenuItem/CheckableItemTest.php +++ b/test/MenuItem/CheckableItemTest.php @@ -141,11 +141,12 @@ public function testGetRowsWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); $item = new CheckableItem('Item', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals(['[ ] Item [EXTRA]'], $item->getRows($menuStyle)); } @@ -157,11 +158,12 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); $item = new CheckableItem('LONG ITEM LINE', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals( [ "[ ] LONG [EXTRA]", diff --git a/test/MenuItem/MenuMenuItemTest.php b/test/MenuItem/MenuMenuItemTest.php index 16c03bd8..2199ab0d 100644 --- a/test/MenuItem/MenuMenuItemTest.php +++ b/test/MenuItem/MenuMenuItemTest.php @@ -55,11 +55,12 @@ public function testGetRows() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(10); - $menuStyle->setUnselectedMarker('* '); $subMenu = $this->createMock(CliMenu::class); $item = new MenuMenuItem('Item', $subMenu); + $item->getStyle() + ->setMarkerOff('* '); $this->assertEquals(['* Item'], $item->getRows($menuStyle)); } @@ -71,11 +72,12 @@ public function testGetRowsWithUnSelectedMarker() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(10); - $menuStyle->setUnselectedMarker('* '); $subMenu = $this->createMock(CliMenu::class); $item = new MenuMenuItem('Item', $subMenu); + $item->getStyle() + ->setMarkerOff('* '); $this->assertEquals(['* Item'], $item->getRows($menuStyle)); $this->assertEquals(['* Item'], $item->getRows($menuStyle, false)); } @@ -89,17 +91,13 @@ public function testGetRowsWithSelectedMarker() : void ->method('getContentWidth') ->will($this->returnValue(10)); - $menuStyle - ->expects($this->once()) - ->method('getMarker') - ->with(true) - ->will($this->returnValue('= ')); - $subMenu = $this->getMockBuilder(CliMenu::class) ->disableOriginalConstructor() ->getMock(); $item = new MenuMenuItem('Item', $subMenu); + $item->getStyle() + ->setMarkerOn('= '); $this->assertEquals(['= Item'], $item->getRows($menuStyle, true)); } @@ -112,11 +110,12 @@ public function testGetRowsWithMultipleLines() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(10); - $menuStyle->setUnselectedMarker('* '); $subMenu = $this->createMock(CliMenu::class); $item = new MenuMenuItem('LONG ITEM LINE', $subMenu); + $item->getStyle() + ->setMarkerOff('* '); $this->assertEquals( [ "* LONG", diff --git a/test/MenuItem/RadioItemTest.php b/test/MenuItem/RadioItemTest.php index 00f667df..92475352 100644 --- a/test/MenuItem/RadioItemTest.php +++ b/test/MenuItem/RadioItemTest.php @@ -175,11 +175,12 @@ public function testGetRowsWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); $item = new RadioItem('Item', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals(['[○] Item [EXTRA]'], $item->getRows($menuStyle)); } @@ -191,11 +192,12 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); $item = new RadioItem('LONG ITEM LINE', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals( [ "[○] LONG [EXTRA]", diff --git a/test/MenuItem/SelectableItemTest.php b/test/MenuItem/SelectableItemTest.php index c78e9fec..5b9c09af 100644 --- a/test/MenuItem/SelectableItemTest.php +++ b/test/MenuItem/SelectableItemTest.php @@ -87,14 +87,10 @@ public function testGetRowsWithUnSelectedMarker() : void ->method('getContentWidth') ->will($this->returnValue(10)); - $menuStyle - ->expects($this->exactly(2)) - ->method('getMarker') - ->with(false) - ->will($this->returnValue('* ')); - $item = new SelectableItem('Item', function () { }); + $item->getStyle() + ->setMarkerOff('* '); $this->assertEquals(['* Item'], $item->getRows($menuStyle)); $this->assertEquals(['* Item'], $item->getRows($menuStyle, false)); } @@ -108,14 +104,10 @@ public function testGetRowsWithSelectedMarker() : void ->method('getContentWidth') ->will($this->returnValue(10)); - $menuStyle - ->expects($this->once()) - ->method('getMarker') - ->with(true) - ->will($this->returnValue('= ')); - $item = new SelectableItem('Item', function () { }); + $item->getStyle() + ->setMarkerOn('= '); $this->assertEquals(['= Item'], $item->getRows($menuStyle, true)); } @@ -127,12 +119,13 @@ public function testGetRowsWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); - $menuStyle->setUnselectedMarker('* '); $item = new SelectableItem('Item', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true) + ->setMarkerOff('* '); $this->assertEquals(['* Item [EXTRA]'], $item->getRows($menuStyle)); } @@ -144,12 +137,13 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(20); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); - $menuStyle->setUnselectedMarker('* '); $item = new SelectableItem('LONG ITEM LINE', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true) + ->setMarkerOff('* '); $this->assertEquals( [ "* LONG ITEM [EXTRA]", diff --git a/test/MenuItem/SplitItemTest.php b/test/MenuItem/SplitItemTest.php index d97b1b1f..4debcf15 100644 --- a/test/MenuItem/SplitItemTest.php +++ b/test/MenuItem/SplitItemTest.php @@ -12,6 +12,10 @@ use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\CheckableStyle; +use PhpSchool\CliMenu\Style\RadioStyle; +use PhpSchool\CliMenu\Style\SelectableStyle; +use PhpSchool\CliMenu\Style\SplitStyle; use PhpSchool\Terminal\Terminal; use PHPUnit\Framework\TestCase; @@ -193,17 +197,16 @@ public function testGetRowsWithOneItemSelected() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getMarker') - ->willReturnMap([[true, '= '], [false, '* ']]); + $selectableStyle = new SelectableStyle(); + $selectableStyle->setMarkerOn('= ') + ->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem('Item One', function () { - }), - new SelectableItem('Item Two', function () { - }) + (new SelectableItem('Item One', function () { + }))->setStyle($selectableStyle), + (new SelectableItem('Item Two', function () { + }))->setStyle($selectableStyle), ] ); @@ -241,18 +244,15 @@ public function testGetRowsWithMultipleLinesWithUnSelectedMarker() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getMarker') - ->with(false) - ->will($this->returnValue('* ')); + $selectableStyle = new SelectableStyle(); + $selectableStyle->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem("Item\nOne", function () { - }), - new SelectableItem("Item\nTwo", function () { - }) + (new SelectableItem("Item\nOne", function () { + }))->setStyle($selectableStyle), + (new SelectableItem("Item\nTwo", function () { + }))->setStyle($selectableStyle), ] ); @@ -274,17 +274,16 @@ public function testGetRowsWithMultipleLinesWithOneItemSelected() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getMarker') - ->willReturnMap([[true, '= '], [false, '* ']]); + $selectableStyle = new SelectableStyle(); + $selectableStyle->setMarkerOn('= ') + ->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem("Item\nOne", function () { - }), - new SelectableItem("Item\nTwo", function () { - }) + (new SelectableItem("Item\nOne", function () { + }))->setStyle($selectableStyle), + (new SelectableItem("Item\nTwo", function () { + }))->setStyle($selectableStyle), ] ); @@ -299,6 +298,9 @@ public function testGetRowsWithMultipleLinesWithOneItemSelected() : void ); } + /** + * @todo Refactor this to use SelectableStyle() exclusively + */ public function testGetRowsWithItemExtra() : void { $terminal = $this->createMock(Terminal::class); @@ -307,18 +309,24 @@ public function testGetRowsWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(50); - $menuStyle->setItemExtra('[EXTRA]'); - $menuStyle->setDisplaysExtra(true); - $menuStyle->setUnselectedMarker('* '); + + $selectableStyle = new SelectableStyle(); + $selectableStyle->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true) + ->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem('Item 1', function () { - }, true), - new SelectableItem('Item 2', function () { - }, true) + (new SelectableItem('Item 1', function () { + }, true))->setStyle($selectableStyle), + (new SelectableItem('Item 2', function () { + }, true))->setStyle($selectableStyle), ] ); + $item->setStyleCallback(function (SplitStyle $style) { + $style->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); + }); self::assertEquals(['* Item 1 [EXTRA] * Item 2 [EXTRA] '], $item->getRows($menuStyle)); } @@ -331,18 +339,24 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(50); - $menuStyle->setItemExtra(' [EXTRA]'); - $menuStyle->setDisplaysExtra(true); - $menuStyle->setUnselectedMarker('* '); + + $selectableStyle = new SelectableStyle(); + $selectableStyle->setItemExtra(' [EXTRA]') + ->setDisplaysExtra(true) + ->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem("Item 1\nItem 1", function () { - }, true), - new SelectableItem("Item 2\nItem 2", function () { - }, true) + (new SelectableItem("Item 1\nItem 1", function () { + }, true))->setStyle($selectableStyle), + (new SelectableItem("Item 2\nItem 2", function () { + }, true))->setStyle($selectableStyle), ] ); + $item->setStyleCallback(function (SplitStyle $style) { + $style->setItemExtra(' [EXTRA]') + ->setDisplaysExtra(true); + }); self::assertEquals( [ @@ -361,18 +375,24 @@ public function testGetRowsWithMultipleLinesWithItemExtraOnOne() : void $menuStyle = new MenuStyle($terminal); $menuStyle->setPaddingLeftRight(0); $menuStyle->setWidth(50); - $menuStyle->setItemExtra(' [EXTRA] '); - $menuStyle->setDisplaysExtra(true); - $menuStyle->setUnselectedMarker('* '); + + $selectableStyle = new SelectableStyle(); + $selectableStyle->setItemExtra(' [EXTRA] ') + ->setDisplaysExtra(true) + ->setMarkerOff('* '); $item = new SplitItem( [ - new SelectableItem("Item 1\nItem 1", function () { - }), - new SelectableItem("Item 2\nItem 2", function () { - }, true) + (new SelectableItem("Item 1\nItem 1", function () { + }))->setStyle($selectableStyle), + (new SelectableItem("Item 2\nItem 2", function () { + }, true))->setStyle($selectableStyle), ] ); + $item->setStyleCallback(function (SplitStyle $style) { + $style->setItemExtra(' [EXTRA] ') + ->setDisplaysExtra(true); + }); self::assertEquals( [ @@ -479,21 +499,17 @@ public function testCheckableItem() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getCheckedMarker') - ->willReturn('[✔] '); - - $menuStyle - ->expects($this->any()) - ->method('getUncheckedMarker') - ->willReturn('[ ] '); - $checkableItem1 = new CheckableItem('Item One', function () { }); + $checkableItem1->getStyle() + ->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); $checkableItem2 = new CheckableItem('Item Two', function () { }); + $checkableItem2->getStyle() + ->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); $item = new SplitItem( [ @@ -520,26 +536,22 @@ public function testRadioItem() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getRadioMarker') - ->willReturn('[●] '); - - $menuStyle - ->expects($this->any()) - ->method('getUnradioMarker') - ->willReturn('[○] '); + $radioStyle = new RadioStyle(); + $radioStyle->setMarkerOn('[+] ') + ->setMarkerOff('[-] '); - $checkableItem1 = new RadioItem('Item One', function () { + $radioItem1 = new RadioItem('Item One', function () { }); + $radioItem1->setStyle($radioStyle); - $checkableItem2 = new RadioItem('Item Two', function () { + $radioItem2 = new RadioItem('Item Two', function () { }); + $radioItem2->setStyle($radioStyle); $item = new SplitItem( [ - $checkableItem1, - $checkableItem2, + $radioItem1, + $radioItem2, ] ); @@ -561,14 +573,14 @@ public function testRadioItem() : void $item->setSelectedItemIndex(0); - self::assertEquals(['[○] Item One [○] Item Two '], $item->getRows($menuStyle, true)); + self::assertEquals(['[-] Item One [-] Item Two '], $item->getRows($menuStyle, true)); - $checkableItem1->getSelectAction()($cliMenu); + $radioItem1->getSelectAction()($cliMenu); - self::assertEquals(['[●] Item One [○] Item Two '], $item->getRows($menuStyle, true)); + self::assertEquals(['[+] Item One [-] Item Two '], $item->getRows($menuStyle, true)); - $checkableItem2->getSelectAction()($cliMenu); + $radioItem2->getSelectAction()($cliMenu); - self::assertEquals(['[○] Item One [●] Item Two '], $item->getRows($menuStyle, true)); + self::assertEquals(['[-] Item One [+] Item Two '], $item->getRows($menuStyle, true)); } } diff --git a/test/MenuStyleTest.php b/test/MenuStyleTest.php index 6294f351..7a664140 100644 --- a/test/MenuStyleTest.php +++ b/test/MenuStyleTest.php @@ -81,10 +81,6 @@ public function testGetterAndSetters() : void self::assertSame('blue', $style->getBg()); self::assertSame('white', $style->getFg()); - self::assertSame('○ ', $style->getUnselectedMarker()); - self::assertSame('● ', $style->getSelectedMarker()); - self::assertSame('✔', $style->getItemExtra()); - self::assertFalse($style->getDisplaysExtra()); self::assertSame('=', $style->getTitleSeparator()); self::assertSame(100, $style->getWidth()); self::assertSame(2, $style->getMargin()); @@ -98,14 +94,6 @@ public function testGetterAndSetters() : void $style->setBg('red'); $style->setFg('yellow'); - $style->setUnselectedMarker('-'); - $style->setSelectedMarker('>'); - $style->setUncheckedMarker('/'); - $style->setCheckedMarker('+'); - $style->setUnradioMarker('O'); - $style->setRadioMarker('X'); - $style->setItemExtra('EXTRA!'); - $style->setDisplaysExtra(true); $style->setTitleSeparator('+'); $style->setWidth(200); $style->setMargin(10); @@ -119,14 +107,6 @@ public function testGetterAndSetters() : void self::assertSame('red', $style->getBg()); self::assertSame('yellow', $style->getFg()); - self::assertSame('-', $style->getUnselectedMarker()); - self::assertSame('>', $style->getSelectedMarker()); - self::assertEquals('/', $style->getUncheckedMarker()); - self::assertEquals('+', $style->getCheckedMarker()); - self::assertEquals('O', $style->getUnradioMarker()); - self::assertEquals('X', $style->getRadioMarker()); - self::assertSame('EXTRA!', $style->getItemExtra()); - self::assertTrue($style->getDisplaysExtra()); self::assertSame('+', $style->getTitleSeparator()); self::assertSame(200, $style->getWidth()); self::assertSame(10, $style->getMargin()); @@ -241,17 +221,6 @@ public function testSetBgThrowsExceptionWhenColourCodeIsNotInRange() : void $style->setBg(257, 'white'); } - public function testGetMarkerReturnsTheCorrectMarkers() : void - { - $style = $this->getMenuStyle(); - - $style->setSelectedMarker('>'); - $style->setUnselectedMarker('x'); - - static::assertSame('>', $style->getMarker(true)); - static::assertSame('x', $style->getMarker(false)); - } - public function testWidthCalculation() : void { $style = $this->getMenuStyle();