-
Notifications
You must be signed in to change notification settings - Fork 106
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
Changes from 12 commits
9ee226c
1ebbfcf
2d6f516
b661374
44b7c84
45e17ac
fc48628
31f134f
0483240
92af28f
b00bb94
2f08464
32acc24
fe1a12d
035910e
bc21c15
009cf60
ccea967
c4c0d51
bb9b06d
c7ef4d0
59df011
cd817b2
7fe55f0
15b0c6d
f931cb6
e058166
a6fadcf
54cf6a0
de67bcf
b7c1eca
39d5df7
525486e
c5ffe6c
1dc8b08
ff94083
f711cd3
1bf30f8
327c85f
b3b5927
c5741fe
ecb429d
d293834
61c6940
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); |
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; | ||
} |
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
||
$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.
Sorry, something went wrong.
This comment was marked as resolved.
Sorry, something went wrong. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you maybe explain this method a little? I don't understand :( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you can revert the changes here and create a method
Which will then look like: if (!$this->isSelectedItemASplitItem()) {
return;
}
$item = $this->getSelectedSplitItem(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made it so that But what if we want the "first level" selected element (so SplitItem in our example) ? Then we set the argument to If we one day allow split items inside split items or something else, then it would still work as intended. 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; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -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() | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.