Skip to content

Commit 3c4db90

Browse files
authored
Merge pull request #186 from jtreminio/feature/toggleable-item
Initial checkable item support
2 parents d80c749 + 86416ab commit 3c4db90

File tree

7 files changed

+467
-0
lines changed

7 files changed

+467
-0
lines changed

examples/checkable-item.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
use PhpSchool\CliMenu\CliMenu;
4+
use PhpSchool\CliMenu\Builder\CliMenuBuilder;
5+
use PhpSchool\CliMenu\MenuItem\CheckableItem;
6+
7+
require_once(__DIR__ . '/../vendor/autoload.php');
8+
9+
$itemCallable = function (CliMenu $menu) {
10+
/** @var CheckableItem $item */
11+
$item = $menu->getSelectedItem();
12+
13+
$item->toggle();
14+
15+
$menu->redraw();
16+
};
17+
18+
$menu = (new CliMenuBuilder)
19+
->setTitle('Select a Language')
20+
->addSubMenu('Compiled', function (CliMenuBuilder $b) use ($itemCallable) {
21+
$b->setTitle('Compiled Languages')
22+
->addCheckableItem('Rust', $itemCallable)
23+
->addCheckableItem('C++', $itemCallable)
24+
->addCheckableItem('Go', $itemCallable)
25+
->addCheckableItem('Java', $itemCallable)
26+
->addCheckableItem('C', $itemCallable)
27+
;
28+
})
29+
->addSubMenu('Interpreted', function (CliMenuBuilder $b) use ($itemCallable) {
30+
$b->setTitle('Interpreted Languages')
31+
->setUncheckedMarker('[○] ')
32+
->setCheckedMarker('[●] ')
33+
->addCheckableItem('PHP', $itemCallable)
34+
->addCheckableItem('Javascript', $itemCallable)
35+
->addCheckableItem('Ruby', $itemCallable)
36+
->addCheckableItem('Python', $itemCallable)
37+
;
38+
})
39+
->build();
40+
41+
$menu->open();

src/Builder/CliMenuBuilder.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpSchool\CliMenu\Action\GoBackAction;
77
use PhpSchool\CliMenu\Exception\InvalidShortcutException;
88
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
9+
use PhpSchool\CliMenu\MenuItem\CheckableItem;
910
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
1011
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
1112
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
@@ -130,6 +131,17 @@ public function addItems(array $items) : self
130131
return $this;
131132
}
132133

134+
public function addCheckableItem(
135+
string $text,
136+
callable $itemCallable,
137+
bool $showItemExtra = false,
138+
bool $disabled = false
139+
) : self {
140+
$this->addMenuItem(new CheckableItem($text, $itemCallable, $showItemExtra, $disabled));
141+
142+
return $this;
143+
}
144+
133145
public function addStaticItem(string $text) : self
134146
{
135147
$this->addMenuItem(new StaticItem($text));
@@ -395,6 +407,20 @@ public function setSelectedMarker(string $marker) : self
395407
return $this;
396408
}
397409

410+
public function setUncheckedMarker(string $marker) : self
411+
{
412+
$this->style->setUncheckedMarker($marker);
413+
414+
return $this;
415+
}
416+
417+
public function setCheckedMarker(string $marker) : self
418+
{
419+
$this->style->setCheckedMarker($marker);
420+
421+
return $this;
422+
}
423+
398424
public function setItemExtra(string $extra) : self
399425
{
400426
$this->style->setItemExtra($extra);

src/MenuItem/CheckableItem.php

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
namespace PhpSchool\CliMenu\MenuItem;
4+
5+
use PhpSchool\CliMenu\MenuItem;
6+
use PhpSchool\CliMenu\MenuStyle;
7+
use PhpSchool\CliMenu\Util\StringUtil;
8+
9+
class CheckableItem implements MenuItem\MenuItemInterface
10+
{
11+
/**
12+
* @var callable
13+
*/
14+
private $selectAction;
15+
16+
/**
17+
* @var string
18+
*/
19+
private $text = '';
20+
21+
/**
22+
* @var bool
23+
*/
24+
private $showItemExtra = false;
25+
26+
/**
27+
* @var bool
28+
*/
29+
private $disabled = false;
30+
31+
/**
32+
* @var bool
33+
*/
34+
private $checked = false;
35+
36+
public function __construct(
37+
string $text,
38+
callable $selectAction,
39+
bool $showItemExtra = false,
40+
bool $disabled = false
41+
) {
42+
$this->text = $text;
43+
$this->selectAction = $selectAction;
44+
$this->showItemExtra = $showItemExtra;
45+
$this->disabled = $disabled;
46+
}
47+
48+
/**
49+
* Execute the items callable if required
50+
*/
51+
public function getSelectAction() : ?callable
52+
{
53+
return $this->selectAction;
54+
}
55+
56+
/**
57+
* Return the raw string of text
58+
*/
59+
public function getText() : string
60+
{
61+
return $this->text;
62+
}
63+
64+
/**
65+
* Set the raw string of text
66+
*/
67+
public function setText(string $text) : void
68+
{
69+
$this->text = $text;
70+
}
71+
72+
/**
73+
* The output text for the item
74+
*
75+
* @param MenuStyle $style
76+
* @param bool $selected Currently unused in this class
77+
* @return array
78+
*/
79+
public function getRows(MenuStyle $style, bool $selected = false) : array
80+
{
81+
$marker = sprintf("%s", $this->checked ? $style->getCheckedMarker() : $style->getUncheckedMarker());
82+
83+
$length = $style->getDisplaysExtra()
84+
? $style->getContentWidth() - (mb_strlen($style->getItemExtra()) + 2)
85+
: $style->getContentWidth();
86+
87+
$rows = explode(
88+
"\n",
89+
StringUtil::wordwrap(
90+
sprintf('%s%s', $marker, $this->text),
91+
$length,
92+
sprintf("\n%s", str_repeat(' ', mb_strlen($marker)))
93+
)
94+
);
95+
96+
return array_map(function ($row, $key) use ($style, $length) {
97+
$text = $this->disabled ? $style->getDisabledItemText($row) : $row;
98+
99+
if ($key === 0) {
100+
return $this->showItemExtra
101+
? sprintf('%s%s %s', $text, str_repeat(' ', $length - mb_strlen($row)), $style->getItemExtra())
102+
: $text;
103+
}
104+
105+
return $text;
106+
}, $rows, array_keys($rows));
107+
}
108+
109+
/**
110+
* Can the item be selected
111+
*/
112+
public function canSelect() : bool
113+
{
114+
return !$this->disabled;
115+
}
116+
117+
public function showsItemExtra() : bool
118+
{
119+
return $this->showItemExtra;
120+
}
121+
122+
/**
123+
* Enable showing item extra
124+
*/
125+
public function showItemExtra() : void
126+
{
127+
$this->showItemExtra = true;
128+
}
129+
130+
/**
131+
* Disable showing item extra
132+
*/
133+
public function hideItemExtra() : void
134+
{
135+
$this->showItemExtra = false;
136+
}
137+
138+
/**
139+
* Toggles checked state
140+
*/
141+
public function toggle()
142+
{
143+
$this->checked = !$this->checked;
144+
}
145+
146+
/**
147+
* Sets checked state to true
148+
*/
149+
public function setChecked()
150+
{
151+
$this->checked = true;
152+
}
153+
154+
/**
155+
* Sets checked state to false
156+
*/
157+
public function setUnchecked()
158+
{
159+
$this->checked = false;
160+
}
161+
162+
public function getChecked(): bool
163+
{
164+
return $this->checked;
165+
}
166+
}

src/MenuStyle.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ class MenuStyle
6969
*/
7070
private $unselectedMarker;
7171

72+
/**
73+
* @var string
74+
*/
75+
private $checkedMarker;
76+
77+
/**
78+
* @var string
79+
*/
80+
private $uncheckedMarker;
81+
7282
/**
7383
* @var string
7484
*/
@@ -158,6 +168,8 @@ class MenuStyle
158168
'margin' => 2,
159169
'selectedMarker' => '',
160170
'unselectedMarker' => '',
171+
'checkedMarker' => '[✔] ',
172+
'uncheckedMarker' => '[ ] ',
161173
'itemExtra' => '',
162174
'displaysExtra' => false,
163175
'titleSeparator' => '=',
@@ -229,6 +241,8 @@ public function __construct(Terminal $terminal = null)
229241
$this->setMargin(self::$defaultStyleValues['margin']);
230242
$this->setSelectedMarker(self::$defaultStyleValues['selectedMarker']);
231243
$this->setUnselectedMarker(self::$defaultStyleValues['unselectedMarker']);
244+
$this->setCheckedMarker(self::$defaultStyleValues['checkedMarker']);
245+
$this->setUncheckedMarker(self::$defaultStyleValues['uncheckedMarker']);
232246
$this->setItemExtra(self::$defaultStyleValues['itemExtra']);
233247
$this->setDisplaysExtra(self::$defaultStyleValues['displaysExtra']);
234248
$this->setTitleSeparator(self::$defaultStyleValues['titleSeparator']);
@@ -250,6 +264,8 @@ public function hasChangedFromDefaults() : bool
250264
$this->margin,
251265
$this->selectedMarker,
252266
$this->unselectedMarker,
267+
$this->checkedMarker,
268+
$this->uncheckedMarker,
253269
$this->itemExtra,
254270
$this->displaysExtra,
255271
$this->titleSeparator,
@@ -557,6 +573,30 @@ public function getMarker(bool $selected) : string
557573
return $selected ? $this->selectedMarker : $this->unselectedMarker;
558574
}
559575

576+
public function getCheckedMarker() : string
577+
{
578+
return $this->checkedMarker;
579+
}
580+
581+
public function setCheckedMarker(string $marker) : self
582+
{
583+
$this->checkedMarker = $marker;
584+
585+
return $this;
586+
}
587+
588+
public function getUncheckedMarker() : string
589+
{
590+
return $this->uncheckedMarker;
591+
}
592+
593+
public function setUncheckedMarker(string $marker) : self
594+
{
595+
$this->uncheckedMarker = $marker;
596+
597+
return $this;
598+
}
599+
560600
public function setItemExtra(string $itemExtra) : self
561601
{
562602
$this->itemExtra = $itemExtra;

test/Builder/CliMenuBuilderTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpSchool\CliMenu\CliMenu;
66
use PhpSchool\CliMenu\Builder\CliMenuBuilder;
77
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8+
use PhpSchool\CliMenu\MenuItem\CheckableItem;
89
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
910
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
1011
use PhpSchool\CliMenu\MenuItem\SelectableItem;
@@ -64,6 +65,8 @@ public function testModifyStyles() : void
6465
$builder->setMargin(4);
6566
$builder->setUnselectedMarker('>');
6667
$builder->setSelectedMarker('x');
68+
$builder->setUncheckedMarker('-');
69+
$builder->setCheckedMarker('+');
6770
$builder->setItemExtra('*');
6871
$builder->setTitleSeparator('-');
6972

@@ -78,6 +81,8 @@ public function testModifyStyles() : void
7881
self::assertEquals(4, $style->getMargin());
7982
self::assertEquals('>', $style->getUnselectedMarker());
8083
self::assertEquals('x', $style->getSelectedMarker());
84+
self::assertEquals('-', $style->getUncheckedMarker());
85+
self::assertEquals('+', $style->getCheckedMarker());
8186
self::assertEquals('*', $style->getItemExtra());
8287
self::assertEquals('-', $style->getTitleSeparator());
8388
}
@@ -364,6 +369,31 @@ public function testAddMultipleItems() : void
364369
$this->checkMenuItems($menu, $expected);
365370
}
366371

372+
public function testAddCheckableItem() : void
373+
{
374+
$callable = function () {
375+
};
376+
377+
$builder = new CliMenuBuilder;
378+
$builder->disableDefaultItems();
379+
$builder->addCheckableItem('Item 1', $callable);
380+
$builder->addCheckableItem('Item 2', $callable);
381+
$menu = $builder->build();
382+
383+
$expected = [
384+
[
385+
'class' => CheckableItem::class,
386+
'text' => 'Item 1',
387+
],
388+
[
389+
'class' => CheckableItem::class,
390+
'text' => 'Item 2',
391+
],
392+
];
393+
394+
$this->checkMenuItems($menu, $expected);
395+
}
396+
367397
public function testAddStaticItem() : void
368398
{
369399

0 commit comments

Comments
 (0)