Skip to content

Split item with builder #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
May 14, 2018
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9ee226c
Add Split Item
Lynesth May 1, 2018
1ebbfcf
Add Split Item
Lynesth May 1, 2018
2d6f516
Create SplitItem.php
Lynesth May 1, 2018
b661374
Fix PSR2 + non-nullable int
Lynesth May 1, 2018
44b7c84
Merge branch 'master' into patch-4
Lynesth May 1, 2018
45e17ac
Merge branch 'patch-4' of https://github.com/Lynesth/cli-menu into Ly…
May 11, 2018
fc48628
Integration with CliMenuBuilder
May 11, 2018
31f134f
cs
May 11, 2018
0483240
Add submenus to split items
Lynesth May 11, 2018
92af28f
Merge branch 'patch-4' of https://github.com/Lynesth/cli-menu
AydinHassan May 11, 2018
b00bb94
Refactor split item to separate builder
AydinHassan May 11, 2018
2f08464
CS
AydinHassan May 11, 2018
32acc24
Fix typehint again
AydinHassan May 11, 2018
fe1a12d
Update SplitItem.php
Lynesth May 13, 2018
035910e
Split moveSelection in 2 functions
May 13, 2018
bc21c15
Refactoring
Lynesth May 13, 2018
009cf60
Add comment and exception
Lynesth May 13, 2018
ccea967
Merge pull request #128 from Lynesth/patch-22
AydinHassan May 13, 2018
c4c0d51
Remove unused method
AydinHassan May 13, 2018
bb9b06d
Tests + minor fixes
AydinHassan May 13, 2018
c7ef4d0
Refactor SplitItem
AydinHassan May 13, 2018
59df011
CS
AydinHassan May 13, 2018
cd817b2
CS
AydinHassan May 13, 2018
7fe55f0
Consistent method names
AydinHassan May 13, 2018
15b0c6d
Use __CLASS__ constant
AydinHassan May 13, 2018
f931cb6
Tests for add/set items
AydinHassan May 13, 2018
e058166
Throw an exception in getRows if no items were added
AydinHassan May 13, 2018
a6fadcf
CS
AydinHassan May 13, 2018
54cf6a0
Fix display bug
AydinHassan May 13, 2018
de67bcf
tests for canSelect
AydinHassan May 13, 2018
b7c1eca
Selected item tests
AydinHassan May 13, 2018
39d5df7
Remove boundary check
AydinHassan May 13, 2018
525486e
Merge pull request #132 from php-school/split-item-menu-tests
AydinHassan May 13, 2018
c5ffe6c
Fix wordwrap
AydinHassan May 13, 2018
1dc8b08
CS
AydinHassan May 13, 2018
ff94083
Merge pull request #134 from php-school/word-wrap-fix
AydinHassan May 13, 2018
f711cd3
New wordwrap method
AydinHassan May 13, 2018
1bf30f8
Merge pull request #135 from php-school/wordwrap-again
AydinHassan May 13, 2018
327c85f
Split Item - Item Extra
AydinHassan May 13, 2018
b3b5927
Merge pull request #136 from php-school/split-item-item-extra
AydinHassan May 13, 2018
c5741fe
Refactor split item item selection and CliMenu can select checks. Add
AydinHassan May 13, 2018
ecb429d
Fix doc
AydinHassan May 13, 2018
d293834
CS
AydinHassan May 13, 2018
61c6940
Merge pull request #138 from php-school/split-item-select-refactor
AydinHassan May 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions examples/split-item.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use PhpSchool\CliMenu\CliMenu;
use PhpSchool\CliMenu\CliMenuBuilder;

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

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

$menu = (new CliMenuBuilder)
->setWidth(150)
->addSplitItem()
->addSubMenu('Sub Menu on a split item')
->setTitle('Behold the awesomeness')
->addItem('This is awesome', function() { print 'Yes!'; })
->addSplitItem()
->addItem('Split Item 1', function() { print 'Item 1!'; })
->addItem('Split Item 2', function() { print 'Item 2!'; })
->addItem('Split Item 3', function() { print 'Item 3!'; })
->addSubMenu('Split Item Nested Sub Menu')
->addItem('One', function() { print 'One!'; })
->addItem('Two', function() { print 'Two!'; })
->addItem('Three', function() { print 'Three!'; })
->end()
->end()
->end()
->addItem('Item 2', $itemCallable)
->addStaticItem('Item 3 - Static')
->addItem('Item 4', $itemCallable)
->end()
->build();

$menu->open();
17 changes: 17 additions & 0 deletions src/Builder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace PhpSchool\CliMenu;

use PhpSchool\Terminal\Terminal;

/**
* @author Aydin Hassan <[email protected]>
*/
interface Builder
{
public function getTerminal() : Terminal;

public function end() : ?Builder;

public function getMenuStyle() : MenuStyle;
}
109 changes: 109 additions & 0 deletions src/BuilderUtils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace PhpSchool\CliMenu;

use PhpSchool\CliMenu\MenuItem\LineBreakItem;
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
use PhpSchool\CliMenu\MenuItem\SelectableItem;
use PhpSchool\CliMenu\MenuItem\StaticItem;
use RuntimeException;

/**
* @author Aydin Hassan <[email protected]>
*/
trait BuilderUtils
{
/**
* @var null|Builder
*/
private $parent;

/**
* @var self[]
*/
private $subMenuBuilders = [];

/**
* @var CliMenu[]
*/
private $subMenus = [];

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

public function addItem(
string $text,
callable $itemCallable,
bool $showItemExtra = false,
bool $disabled = false
) : self {
$this->menuItems[] = new SelectableItem($text, $itemCallable, $showItemExtra, $disabled);

return $this;
}

public function addStaticItem(string $text) : self
{
$this->menuItems[] = new StaticItem($text);

return $this;
}

public function addLineBreak(string $breakChar = ' ', int $lines = 1) : self
{
$this->menuItems[] = new LineBreakItem($breakChar, $lines);

return $this;
}

/**
* Add a submenu with a name. The name will be displayed as the item text
* in the parent menu.
*/
public function addSubMenu(string $name, CliMenuBuilder $subMenuBuilder = null) : self
{
$this->menuItems[] = $id = 'submenu-placeholder-' . $name;

if (null === $subMenuBuilder) {
$this->subMenuBuilders[$id] = new CliMenuBuilder($this);
return $this->subMenuBuilders[$id];
}

$this->subMenuBuilders[$id] = $subMenuBuilder;
return $this;
}

private function buildSubMenus(array $items) : array
{
return array_map(function ($item) {
if (!is_string($item) || 0 !== strpos($item, 'submenu-placeholder-')) {
return $item;
}

$menuBuilder = $this->subMenuBuilders[$item];
$this->subMenus[$item] = $menuBuilder->build();

return new MenuMenuItem(
substr($item, \strlen('submenu-placeholder-')),
$this->subMenus[$item],
$menuBuilder->isMenuDisabled()
);
}, $items);
}

/**
* Return to parent builder
*
* @throws RuntimeException
*/
public function end() : ?Builder
{
if (null === $this->parent) {
throw new RuntimeException('No parent builder to return to');
}

return $this->parent;
}
}
67 changes: 53 additions & 14 deletions src/CliMenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PhpSchool\CliMenu\Input\Text;
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
use PhpSchool\CliMenu\MenuItem\SplitItem;
use PhpSchool\CliMenu\MenuItem\StaticItem;
use PhpSchool\CliMenu\Dialogue\Confirm;
use PhpSchool\CliMenu\Dialogue\Flash;
Expand Down Expand Up @@ -256,6 +257,8 @@ private function display() : void
switch ($char->getControl()) {
case InputCharacter::UP:
case InputCharacter::DOWN:
case InputCharacter::LEFT:
case InputCharacter::RIGHT:
$this->moveSelection($char->getControl());
$this->draw();
break;
Expand All @@ -272,25 +275,57 @@ private function display() : void
protected function moveSelection(string $direction) : void
{
do {
$itemKeys = array_keys($this->items);

$direction === 'UP'
? $this->selectedItem--
: $this->selectedItem++;

if (!array_key_exists($this->selectedItem, $this->items)) {
$this->selectedItem = $direction === 'UP'
? end($itemKeys)
: reset($itemKeys);
} elseif ($this->getSelectedItem()->canSelect()) {
return;
if ($direction === 'UP' || $direction === 'DOWN') {
$itemKeys = array_keys($this->items);

This comment was marked as resolved.

This comment was marked as resolved.


$direction === 'UP'
? $this->selectedItem--
: $this->selectedItem++;

if (!array_key_exists($this->selectedItem, $this->items)) {
$this->selectedItem = $direction === 'UP'
? end($itemKeys)
: reset($itemKeys);
} elseif ($this->getSelectedItem()->canSelect()) {
return;
}
} else {
$item = $this->getSelectedItem(true);

This comment was marked as resolved.

This comment was marked as resolved.

if (!$item instanceof SplitItem) {
return;
}

$itemKeys = array_keys($item->getItems());
$selectedItemIndex = $item->getSelectedItemIndex();
$direction === 'LEFT'
? $selectedItemIndex--
: $selectedItemIndex++;
$item->setSelectedItemIndex($selectedItemIndex);

if (!array_key_exists($selectedItemIndex, $item->getItems())) {
$selectedItemIndex = $direction === 'LEFT'
? end($itemKeys)
: reset($itemKeys);
$item->setSelectedItemIndex($selectedItemIndex);
} elseif ($item->getItems()[$item->getSelectedItemIndex()]->canSelect()) {
return;
}
}
} while (!$this->getSelectedItem()->canSelect());
}

public function getSelectedItem() : MenuItemInterface
public function getSelectedItem(bool $oneLevelDeep = false) : MenuItemInterface
{
return $this->items[$this->selectedItem];
if ($oneLevelDeep) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you maybe explain this method a little? I don't understand :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can revert the changes here and create a method isSelectedItemASplitItem which you can call in the above method instead of

$item = $this->getSelectedItem(true);
if (!$item instanceof SplitItem) {
    return;
}

Which will then look like:

if (!$this->isSelectedItemASplitItem()) {
    return;
}

$item = $this->getSelectedSplitItem();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it so that getSelectedItem returns the right element.
It means that if we are selecting an item (let's say a SelectableItem) which is inside a SplitItem, it will return the SelectableItem and not the SplitItem.

But what if we want the "first level" selected element (so SplitItem in our example) ? Then we set the argument to true and the function will not "dig" layers to find the right one.

If we one day allow split items inside split items or something else, then it would still work as intended.
If we add some new element that allows multiple item inside itself then it would still do its job too.

The point is not to check if the element is a SplitItem (event though it's now used like that), but to find what is the top level element that is selected.

I agree that it is currently only used when trying to move the selection left or right, but since it's a public function it can also be used outside of the class so that (for example) a keyboard shortcut could have different effects depending on the element currently selected.

return $this->items[$this->selectedItem];
} else {
$item = $this->items[$this->selectedItem];
if ($item instanceof SplitItem) {
$item = $item->getItems()[$item->getSelectedItemIndex()];
}

return $item;
}
}

/**
Expand Down Expand Up @@ -385,6 +420,10 @@ protected function draw() : void
protected function drawMenuItem(MenuItemInterface $item, bool $selected = false) : array
{
$rows = $item->getRows($this->style, $selected);

if ($item instanceof SplitItem) {
$selected = false;
}

$invertedColoursSetCode = $selected
? $this->style->getInvertedColoursSetCode()
Expand Down
Loading