diff --git a/README.md b/README.md index 8af5be97..dc5e1ef4 100644 --- a/README.md +++ b/README.md @@ -817,10 +817,13 @@ You may also change the marker for `\PhpSchool\CliMenu\MenuItem\CheckboxItem`: setUncheckedMarker('[○] ') - ->setCheckedMarker('[●] ') + ->modifyCheckboxStyle(function (CheckboxStyle $style) { + $style->setMarkerOff('[○] ') + ->setMarkerOn('[●] '); + }) ->build(); ``` @@ -830,10 +833,13 @@ and for `\PhpSchool\CliMenu\MenuItem\RadioItem`: setUnradioMarker('[ ] ') - ->setRadioMarker('[✔] ') + ->modifyRadioStyle(function (RadioStyle $style) { + $style->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); + }) ->build(); ``` diff --git a/examples/checkable-item.php b/examples/checkbox-item.php similarity index 83% rename from examples/checkable-item.php rename to examples/checkbox-item.php index 75618775..09d65860 100644 --- a/examples/checkable-item.php +++ b/examples/checkbox-item.php @@ -2,6 +2,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\Builder\CliMenuBuilder; +use PhpSchool\CliMenu\Style\CheckboxStyle; require_once(__DIR__ . '/../vendor/autoload.php'); @@ -22,8 +23,10 @@ }) ->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) { $b->setTitle('Interpreted Languages') - ->setUncheckedMarker('[○] ') - ->setCheckedMarker('[●] ') + ->modifyCheckboxStyle(function (CheckboxStyle $style) { + $style->setMarkerOff('[○] ') + ->setMarkerOn('[●] '); + }) ->addCheckboxItem('PHP', $itemCallable) ->addCheckboxItem('Javascript', $itemCallable) ->addCheckboxItem('Ruby', $itemCallable) diff --git a/examples/radio-item.php b/examples/radio-item.php index d3b7f0c9..7f7ff5ff 100644 --- a/examples/radio-item.php +++ b/examples/radio-item.php @@ -2,6 +2,7 @@ use PhpSchool\CliMenu\CliMenu; use PhpSchool\CliMenu\Builder\CliMenuBuilder; +use PhpSchool\CliMenu\Style\RadioStyle; require_once(__DIR__ . '/../vendor/autoload.php'); @@ -22,8 +23,10 @@ }) ->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) { $b->setTitle('Interpreted Languages') - ->setUnradioMarker('[ ] ') - ->setRadioMarker('[✔] ') + ->modifyRadioStyle(function (RadioStyle $style) { + $style->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); + }) ->addRadioItem('PHP', $itemCallable) ->addRadioItem('Javascript', $itemCallable) ->addRadioItem('Ruby', $itemCallable) diff --git a/examples/split-checkable-item.php b/examples/split-checkbox-item.php similarity index 100% rename from examples/split-checkable-item.php rename to examples/split-checkbox-item.php diff --git a/src/Builder/CliMenuBuilder.php b/src/Builder/CliMenuBuilder.php index fb7cbfad..1196b63b 100644 --- a/src/Builder/CliMenuBuilder.php +++ b/src/Builder/CliMenuBuilder.php @@ -16,6 +16,8 @@ use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\Terminal\Terminal; @@ -187,12 +189,6 @@ public function addSubMenu(string $text, \Closure $callback) : self $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()); - } $this->menu->addItem($item = new MenuMenuItem( $text, @@ -210,12 +206,6 @@ public function addSubMenuFromBuilder(string $text, CliMenuBuilder $builder) : s $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()); - } - $this->menu->addItem($item = new MenuMenuItem( $text, $menu, @@ -302,7 +292,7 @@ public function addSplitItem(\Closure $callback) : self } $callback($builder); - + $this->menu->addItem($splitItem = $builder->build()); $this->processSplitItemShortcuts($splitItem); @@ -417,34 +407,6 @@ public function setSelectedMarker(string $marker) : self return $this; } - public function setUncheckedMarker(string $marker) : self - { - $this->style->setUncheckedMarker($marker); - - return $this; - } - - public function setCheckedMarker(string $marker) : self - { - $this->style->setCheckedMarker($marker); - - return $this; - } - - public function setUnradioMarker(string $marker) : self - { - $this->style->setUnradioMarker($marker); - - return $this; - } - - public function setRadioMarker(string $marker) : self - { - $this->style->setRadioMarker($marker); - - return $this; - } - public function setItemExtra(string $extra) : self { $this->style->setItemExtra($extra); @@ -556,6 +518,66 @@ public function build() : CliMenu $this->style->setDisplaysExtra($this->itemsHaveExtra($this->menu->getItems())); } + if (!$this->subMenu) { + $this->propagateStyles($this->menu); + } + return $this->menu; } + + public function getCheckboxStyle() : CheckboxStyle + { + return $this->menu->getCheckboxStyle(); + } + + public function setCheckboxStyle(CheckboxStyle $style) : self + { + $this->menu->setCheckboxStyle($style); + + return $this; + } + + public function modifyCheckboxStyle(callable $itemCallable) : self + { + $itemCallable($this->menu->getCheckboxStyle()); + + return $this; + } + + public function getRadioStyle() : RadioStyle + { + return $this->menu->getRadioStyle(); + } + + public function setRadioStyle(RadioStyle $style) : self + { + $this->menu->setRadioStyle($style); + + return $this; + } + + public function modifyRadioStyle(callable $itemCallable) : self + { + $itemCallable($this->menu->getRadioStyle()); + + return $this; + } + + /** + * Pass styles from current menu to sub-menu + * only if sub-menu style has not be customized + */ + private function propagateStyles(CliMenu $menu) + { + foreach ($menu->getItems() as $item) { + if ($item instanceof MenuMenuItem) { + $subMenu = $item->getSubMenu(); + + !$subMenu->getStyle()->hasChangedFromDefaults() + && $subMenu->setStyle(clone $menu->getStyle()); + + $this->propagateStyles($subMenu); + } + } + } } diff --git a/src/Builder/SplitItemBuilder.php b/src/Builder/SplitItemBuilder.php index c5da6531..e58248bc 100644 --- a/src/Builder/SplitItemBuilder.php +++ b/src/Builder/SplitItemBuilder.php @@ -65,7 +65,10 @@ public function addCheckboxItem( bool $showItemExtra = false, bool $disabled = false ) : self { - $this->splitItem->addItem(new CheckboxItem($text, $itemCallable, $showItemExtra, $disabled)); + $item = (new CheckboxItem($text, $itemCallable, $showItemExtra, $disabled)) + ->setStyle($this->menu->getCheckboxStyle()); + + $this->splitItem->addItem($item); return $this; } @@ -76,7 +79,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; } @@ -108,19 +114,27 @@ public function addSubMenu(string $text, \Closure $callback) : self $menu = $builder->build(); $menu->setParent($this->menu); + if (!$menu->getCheckboxStyle()->hasChangedFromDefaults()) { + $menu->setCheckboxStyle(clone $this->menu->getCheckboxStyle()); + } + + if (!$menu->getRadioStyle()->hasChangedFromDefaults()) { + $menu->setRadioStyle(clone $this->menu->getRadioStyle()); + } + $this->splitItem->addItem(new MenuMenuItem( $text, $menu, $builder->isMenuDisabled() )); - + return $this; } public function setGutter(int $gutter) : self { $this->splitItem->setGutter($gutter); - + return $this; } @@ -134,7 +148,7 @@ public function enableAutoShortcuts(string $regex = null) : self return $this; } - + public function build() : SplitItem { return $this->splitItem; diff --git a/src/CliMenu.php b/src/CliMenu.php index 5b7631ba..1a15bc08 100644 --- a/src/CliMenu.php +++ b/src/CliMenu.php @@ -14,6 +14,8 @@ use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\Dialogue\Confirm; use PhpSchool\CliMenu\Dialogue\Flash; +use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\CliMenu\Terminal\TerminalFactory; use PhpSchool\CliMenu\Util\StringUtil as s; use PhpSchool\Terminal\InputCharacter; @@ -35,6 +37,16 @@ class CliMenu */ protected $style; + /** + * @var CheckboxStyle + */ + private $checkboxStyle; + + /** + * @var RadioStyle + */ + private $radioStyle; + /** * @var ?string */ @@ -90,10 +102,12 @@ 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->checkboxStyle = new CheckboxStyle(); + $this->radioStyle = new RadioStyle(); $this->selectFirstItem(); } @@ -640,6 +654,30 @@ public function setStyle(MenuStyle $style) : void $this->style = $style; } + public function getCheckboxStyle() : CheckboxStyle + { + return $this->checkboxStyle; + } + + public function setCheckboxStyle(CheckboxStyle $style) : self + { + $this->checkboxStyle = $style; + + return $this; + } + + public function getRadioStyle() : RadioStyle + { + return $this->radioStyle; + } + + public function setRadioStyle(RadioStyle $style) : self + { + $this->radioStyle = $style; + + return $this; + } + public function getCurrentFrame() : Frame { return $this->currentFrame; diff --git a/src/MenuItem/CheckboxItem.php b/src/MenuItem/CheckboxItem.php index f0278bbf..be2b3398 100644 --- a/src/MenuItem/CheckboxItem.php +++ b/src/MenuItem/CheckboxItem.php @@ -3,30 +3,29 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\CliMenu; +use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Util\StringUtil; +use PhpSchool\CliMenu\Style\CheckboxStyle; class CheckboxItem implements MenuItemInterface, ToggableItemInterface { - use ToggableTrait; - /** * @var callable */ private $selectAction; - /** - * @var string - */ private $text = ''; - /** - * @var bool - */ private $showItemExtra = false; + private $disabled = false; + + private $checked = false; + /** - * @var bool + * @var CheckboxStyle; */ - private $disabled = false; + private $style; public function __construct( string $text, @@ -38,6 +37,43 @@ public function __construct( $this->selectAction = $selectAction; $this->showItemExtra = $showItemExtra; $this->disabled = $disabled; + + $this->style = new CheckboxStyle(); + } + + /** + * The output text for the item + */ + public function getRows(MenuStyle $style, bool $selected = false) : array + { + $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( + "\n", + StringUtil::wordwrap( + sprintf('%s%s', $marker, $this->text), + $length, + sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) + ) + ); + + 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)), $itemExtra) + : $text; + } + + return $text; + }, $rows, array_keys($rows)); } /** @@ -53,6 +89,50 @@ public function getSelectAction() : ?callable }; } + public function getStyle() : CheckboxStyle + { + return $this->style; + } + + public function setStyle(CheckboxStyle $style) : self + { + $this->style = $style; + + return $this; + } + + /** + * Toggles checked state + */ + public function toggle() : void + { + $this->checked = !$this->checked; + } + + /** + * Sets checked state to true + */ + public function setChecked() : void + { + $this->checked = true; + } + + /** + * Sets checked state to false + */ + public function setUnchecked() : void + { + $this->checked = false; + } + + /** + * Whether or not the item is checked + */ + public function getChecked() : bool + { + return $this->checked; + } + /** * Return the raw string of text */ diff --git a/src/MenuItem/RadioItem.php b/src/MenuItem/RadioItem.php index d4129699..d6017d60 100644 --- a/src/MenuItem/RadioItem.php +++ b/src/MenuItem/RadioItem.php @@ -3,30 +3,29 @@ namespace PhpSchool\CliMenu\MenuItem; use PhpSchool\CliMenu\CliMenu; +use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Util\StringUtil; +use PhpSchool\CliMenu\Style\RadioStyle; class RadioItem implements MenuItemInterface, ToggableItemInterface { - use ToggableTrait; - /** * @var callable */ private $selectAction; - /** - * @var string - */ private $text = ''; - /** - * @var bool - */ private $showItemExtra = false; + private $disabled = false; + + private $checked = false; + /** - * @var bool + * @var RadioStyle; */ - private $disabled = false; + private $style; public function __construct( string $text, @@ -38,6 +37,43 @@ public function __construct( $this->selectAction = $selectAction; $this->showItemExtra = $showItemExtra; $this->disabled = $disabled; + + $this->style = new RadioStyle(); + } + + /** + * The output text for the item + */ + public function getRows(MenuStyle $style, bool $selected = false) : array + { + $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( + "\n", + StringUtil::wordwrap( + sprintf('%s%s', $marker, $this->text), + $length, + sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) + ) + ); + + 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)), $itemExtra) + : $text; + } + + return $text; + }, $rows, array_keys($rows)); } /** @@ -73,6 +109,50 @@ function (RadioItem $item) { }; } + public function getStyle() : RadioStyle + { + return $this->style; + } + + public function setStyle(RadioStyle $style) : self + { + $this->style = $style; + + return $this; + } + + /** + * Toggles checked state + */ + public function toggle() : void + { + $this->checked = !$this->checked; + } + + /** + * Sets checked state to true + */ + public function setChecked() : void + { + $this->checked = true; + } + + /** + * Sets checked state to false + */ + public function setUnchecked() : void + { + $this->checked = false; + } + + /** + * Whether or not the item is checked + */ + public function getChecked() : bool + { + return $this->checked; + } + /** * Return the raw string of text */ diff --git a/src/MenuItem/SplitItem.php b/src/MenuItem/SplitItem.php index 6369d8fe..cca5a999 100644 --- a/src/MenuItem/SplitItem.php +++ b/src/MenuItem/SplitItem.php @@ -130,16 +130,14 @@ public function getRows(MenuStyle $style, bool $selected = false) : array array_map(function ($index, $item) use ($selected, $length, $style) { $isSelected = $selected && $index === $this->selectedItemIndex; - if ($item instanceof CheckboxItem) { - $markerType = $item->getChecked() - ? $style->getCheckedMarker() - : $style->getUncheckedMarker(); - } elseif ($item instanceof RadioItem) { - $markerType = $item->getChecked() - ? $style->getRadioMarker() - : $style->getUnradioMarker(); + if ($item instanceof CheckboxItem || $item instanceof RadioItem) { + $markerType = $item->getStyle()->getMarker($item->getChecked()); + $displaysExtra = $item->getStyle()->getDisplaysExtra(); + $itemExtraVal = $item->getStyle()->getItemExtra(); } else { - $markerType = $style->getMarker($isSelected); + $markerType = $style->getMarker($isSelected); + $displaysExtra = $style->getDisplaysExtra(); + $itemExtraVal = $style->getItemExtra(); } $marker = $item->canSelect() @@ -147,10 +145,10 @@ public function getRows(MenuStyle $style, bool $selected = false) : array : ''; $itemExtra = ''; - if ($style->getDisplaysExtra()) { + if ($displaysExtra) { $itemExtra = $item->showsItemExtra() - ? sprintf(' %s', $style->getItemExtra()) - : sprintf(' %s', str_repeat(' ', mb_strlen($style->getItemExtra()))); + ? sprintf(' %s', $itemExtraVal) + : sprintf(' %s', str_repeat(' ', mb_strlen($itemExtraVal))); } return $this->buildCell( diff --git a/src/MenuItem/ToggableTrait.php b/src/MenuItem/ToggableTrait.php deleted file mode 100644 index a56f77d0..00000000 --- a/src/MenuItem/ToggableTrait.php +++ /dev/null @@ -1,88 +0,0 @@ - $this instanceof CheckboxItem - ? $style->getCheckedMarker() - : $style->getRadioMarker(), - false => $this instanceof CheckboxItem - ? $style->getUncheckedMarker() - : $style->getUnradioMarker(), - ]; - - $marker = sprintf("%s", $markerTypes[$this->checked]); - - $length = $style->getDisplaysExtra() - ? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2) - : $style->getContentWidth(); - - $rows = explode( - "\n", - StringUtil::wordwrap( - sprintf('%s%s', $marker, $this->text), - $length, - sprintf("\n%s", str_repeat(' ', mb_strlen($marker))) - ) - ); - - return array_map(function ($row, $key) use ($style, $length) { - $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()) - : $text; - } - - return $text; - }, $rows, array_keys($rows)); - } - - /** - * Toggles checked state - */ - public function toggle() : void - { - $this->checked = !$this->checked; - } - - /** - * Sets checked state to true - */ - public function setChecked() : void - { - $this->checked = true; - } - - /** - * Sets checked state to false - */ - public function setUnchecked() : void - { - $this->checked = false; - } - - /** - * Whether or not the item is checked - */ - public function getChecked() : bool - { - return $this->checked; - } -} diff --git a/src/MenuStyle.php b/src/MenuStyle.php index f2139bca..47329f9d 100644 --- a/src/MenuStyle.php +++ b/src/MenuStyle.php @@ -69,26 +69,6 @@ class MenuStyle */ private $unselectedMarker; - /** - * @var string - */ - private $checkedMarker; - - /** - * @var string - */ - private $uncheckedMarker; - - /** - * @var string - */ - private $radioMarker; - - /** - * @var string - */ - private $unradioMarker; - /** * @var string */ @@ -178,10 +158,6 @@ class MenuStyle 'margin' => 2, 'selectedMarker' => '● ', 'unselectedMarker' => '○ ', - 'checkedMarker' => '[✔] ', - 'uncheckedMarker' => '[ ] ', - 'radioMarker' => '[●] ', - 'unradioMarker' => '[○] ', 'itemExtra' => '✔', 'displaysExtra' => false, 'titleSeparator' => '=', @@ -253,10 +229,6 @@ public function __construct(Terminal $terminal = null) $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']); @@ -278,10 +250,6 @@ public function hasChangedFromDefaults() : bool $this->margin, $this->selectedMarker, $this->unselectedMarker, - $this->checkedMarker, - $this->uncheckedMarker, - $this->radioMarker, - $this->unradioMarker, $this->itemExtra, $this->displaysExtra, $this->titleSeparator, @@ -589,54 +557,6 @@ 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; diff --git a/src/Style/CheckboxStyle.php b/src/Style/CheckboxStyle.php new file mode 100644 index 00000000..38fe2d99 --- /dev/null +++ b/src/Style/CheckboxStyle.php @@ -0,0 +1,102 @@ + '[✔] ', + 'markerOff' => '[ ] ', + 'itemExtra' => '✔', + 'displaysExtra' => false, + ]; + + protected $markerOn = ''; + + protected $markerOff = ''; + + protected $itemExtra = ''; + + protected $displaysExtra = false; + + protected $custom = 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 hasChangedFromDefaults() : bool + { + return $this->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..c798a98a --- /dev/null +++ b/src/Style/RadioStyle.php @@ -0,0 +1,102 @@ + '[●] ', + 'markerOff' => '[○] ', + 'itemExtra' => '✔', + 'displaysExtra' => false, + ]; + + protected $markerOn = ''; + + protected $markerOff = ''; + + protected $itemExtra = ''; + + protected $displaysExtra = false; + + protected $custom = 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 hasChangedFromDefaults() : bool + { + return $this->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/test/Builder/CliMenuBuilderTest.php b/test/Builder/CliMenuBuilderTest.php index 55df7f49..407daefd 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('-'); @@ -83,8 +81,6 @@ public function testModifyStyles() : void 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()); } @@ -583,17 +579,56 @@ public function testSubMenuInheritsParentsStyle() : void ->expects($this->any()) ->method('getWidth') ->will($this->returnValue(200)); - + $menu = (new CliMenuBuilder($terminal)) ->setBackgroundColour('green') ->addSubMenu('My SubMenu', function (CliMenuBuilder $b) { - $b->addItem('Some Item', function () { + $b->addSubMenu('My SubSubMenu', function (CliMenuBuilder $b) { + $b->addItem('Some Item', function () { + }); }); }) ->build(); - self::assertSame('green', $menu->getItems()[0]->getSubMenu()->getStyle()->getBg()); - self::assertSame($menu->getStyle(), $menu->getItems()[0]->getSubMenu()->getStyle()); + /** @var CliMenu $subMenu1 */ + $subMenu1 = $menu->getItems()[0]->getSubMenu(); + /** @var CliMenu $subMenu2 */ + $subMenu2 = $subMenu1->getItems()[0]->getSubMenu(); + + self::assertSame('green', $subMenu1->getStyle()->getBg()); + self::assertEquals($menu->getStyle(), $subMenu1->getStyle()); + + self::assertSame('green', $subMenu2->getStyle()->getBg()); + self::assertEquals($menu->getStyle(), $subMenu2->getStyle()); + } + + public function testSubMenuIgnoresParentsStyleIfCustomAndPassesToChildren() : void + { + $terminal = self::createMock(Terminal::class); + $terminal + ->expects($this->any()) + ->method('getWidth') + ->will($this->returnValue(200)); + + $menu = (new CliMenuBuilder($terminal)) + ->setBackgroundColour('green') + ->addSubMenu('My SubMenu', function (CliMenuBuilder $b) { + $b->setBackgroundColour('yellow') + ->addSubMenu('My SubSubMenu', function (CliMenuBuilder $b) { + $b->addItem('Some Item', function () { + }); + }); + }) + ->build(); + + /** @var CliMenu $subMenu1 */ + $subMenu1 = $menu->getItems()[0]->getSubMenu(); + /** @var CliMenu $subMenu2 */ + $subMenu2 = $subMenu1->getItems()[0]->getSubMenu(); + + self::assertSame('green', $menu->getStyle()->getBg()); + self::assertSame('yellow', $subMenu1->getStyle()->getBg()); + self::assertSame('yellow', $subMenu2->getStyle()->getBg()); } public function testSubMenuDoesNotInheritsParentsStyleWhenSubMenuStyleHasAlterations() : void diff --git a/test/MenuItem/CheckableItemTest.php b/test/MenuItem/CheckboxItemTest.php similarity index 96% rename from test/MenuItem/CheckableItemTest.php rename to test/MenuItem/CheckboxItemTest.php index 98649b0e..8a9a1d49 100644 --- a/test/MenuItem/CheckableItemTest.php +++ b/test/MenuItem/CheckboxItemTest.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 CheckboxItem('Item', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals(['[ ] Item [EXTRA]'], $item->getRows($menuStyle)); } @@ -162,6 +163,9 @@ public function testGetRowsWithMultipleLinesWithItemExtra() : void $item = new CheckboxItem('LONG ITEM LINE', function () { }, true); + $item->getStyle() + ->setItemExtra('[EXTRA]') + ->setDisplaysExtra(true); $this->assertEquals( [ "[ ] LONG [EXTRA]", 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/SplitItemTest.php b/test/MenuItem/SplitItemTest.php index bd85791b..34940b67 100644 --- a/test/MenuItem/SplitItemTest.php +++ b/test/MenuItem/SplitItemTest.php @@ -12,6 +12,8 @@ use PhpSchool\CliMenu\MenuItem\SplitItem; use PhpSchool\CliMenu\MenuItem\StaticItem; use PhpSchool\CliMenu\MenuStyle; +use PhpSchool\CliMenu\Style\CheckboxStyle; +use PhpSchool\CliMenu\Style\RadioStyle; use PhpSchool\Terminal\Terminal; use PHPUnit\Framework\TestCase; @@ -479,21 +481,17 @@ public function testCheckboxItem() : void ->method('getContentWidth') ->will($this->returnValue(30)); - $menuStyle - ->expects($this->any()) - ->method('getCheckedMarker') - ->willReturn('[✔] '); - - $menuStyle - ->expects($this->any()) - ->method('getUncheckedMarker') - ->willReturn('[ ] '); - $checkboxItem1 = new CheckboxItem('Item One', function () { }); + $checkboxItem1->getStyle() + ->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); $checkboxItem2 = new CheckboxItem('Item Two', function () { }); + $checkboxItem2->getStyle() + ->setMarkerOff('[ ] ') + ->setMarkerOn('[✔] '); $item = new SplitItem( [ @@ -520,26 +518,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('[-] '); - $checkboxItem1 = new RadioItem('Item One', function () { + $radioItem1 = new RadioItem('Item One', function () { }); + $radioItem1->setStyle($radioStyle); - $checkboxItem2 = new RadioItem('Item Two', function () { + $radioItem2 = new RadioItem('Item Two', function () { }); + $radioItem2->setStyle($radioStyle); $item = new SplitItem( [ - $checkboxItem1, - $checkboxItem2, + $radioItem1, + $radioItem2, ] ); @@ -561,14 +555,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)); - $checkboxItem1->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)); - $checkboxItem2->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..1c954cc3 100644 --- a/test/MenuStyleTest.php +++ b/test/MenuStyleTest.php @@ -100,10 +100,6 @@ public function testGetterAndSetters() : void $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('+'); @@ -121,10 +117,6 @@ public function testGetterAndSetters() : void 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());