1 | <?php |
||||
2 | |||||
3 | namespace PhpSchool\CliMenu; |
||||
4 | |||||
5 | use PhpSchool\CliMenu\Exception\InvalidTerminalException; |
||||
6 | use PhpSchool\CliMenu\Exception\MenuNotOpenException; |
||||
7 | use PhpSchool\CliMenu\Input\InputIO; |
||||
8 | use PhpSchool\CliMenu\Input\Number; |
||||
9 | use PhpSchool\CliMenu\Input\Password; |
||||
10 | use PhpSchool\CliMenu\Input\Text; |
||||
11 | use PhpSchool\CliMenu\MenuItem\LineBreakItem; |
||||
12 | use PhpSchool\CliMenu\MenuItem\MenuItemInterface; |
||||
13 | use PhpSchool\CliMenu\MenuItem\SplitItem; |
||||
14 | use PhpSchool\CliMenu\MenuItem\StaticItem; |
||||
15 | use PhpSchool\CliMenu\Dialogue\Confirm; |
||||
16 | use PhpSchool\CliMenu\Dialogue\Flash; |
||||
17 | use PhpSchool\CliMenu\Terminal\TerminalFactory; |
||||
18 | use PhpSchool\CliMenu\Util\StringUtil as s; |
||||
19 | use PhpSchool\Terminal\InputCharacter; |
||||
20 | use PhpSchool\Terminal\NonCanonicalReader; |
||||
21 | use PhpSchool\Terminal\Terminal; |
||||
22 | |||||
23 | /** |
||||
24 | * @author Michael Woodward <[email protected]> |
||||
25 | */ |
||||
26 | class CliMenu |
||||
27 | { |
||||
28 | /** |
||||
29 | * @var Terminal |
||||
30 | */ |
||||
31 | protected $terminal; |
||||
32 | |||||
33 | /** |
||||
34 | * @var MenuStyle |
||||
35 | */ |
||||
36 | protected $style; |
||||
37 | |||||
38 | /** |
||||
39 | * @var ?string |
||||
40 | */ |
||||
41 | protected $title; |
||||
42 | |||||
43 | /** |
||||
44 | * @var MenuItemInterface[] |
||||
45 | */ |
||||
46 | protected $items = []; |
||||
47 | |||||
48 | /** |
||||
49 | * @var int|null |
||||
50 | */ |
||||
51 | protected $selectedItem; |
||||
52 | |||||
53 | /** |
||||
54 | * @var bool |
||||
55 | */ |
||||
56 | protected $open = false; |
||||
57 | |||||
58 | /** |
||||
59 | * @var CliMenu|null |
||||
60 | */ |
||||
61 | protected $parent; |
||||
62 | |||||
63 | /** |
||||
64 | * @var array |
||||
65 | */ |
||||
66 | protected $defaultControlMappings = [ |
||||
67 | '^P' => InputCharacter::UP, |
||||
68 | 'k' => InputCharacter::UP, |
||||
69 | '^K' => InputCharacter::DOWN, |
||||
70 | 'j' => InputCharacter::DOWN, |
||||
71 | "\r" => InputCharacter::ENTER, |
||||
72 | ' ' => InputCharacter::ENTER, |
||||
73 | 'l' => InputCharacter::LEFT, |
||||
74 | 'm' => InputCharacter::RIGHT, |
||||
75 | ]; |
||||
76 | |||||
77 | /** |
||||
78 | * @var array |
||||
79 | */ |
||||
80 | protected $customControlMappings = []; |
||||
81 | |||||
82 | /** |
||||
83 | * @var Frame |
||||
84 | */ |
||||
85 | private $currentFrame; |
||||
86 | |||||
87 | public function __construct( |
||||
88 | ?string $title, |
||||
89 | array $items, |
||||
90 | Terminal $terminal = null, |
||||
91 | MenuStyle $style = null |
||||
92 | ) { |
||||
93 | $this->title = $title; |
||||
94 | $this->items = $items; |
||||
95 | $this->terminal = $terminal ?: TerminalFactory::fromSystem(); |
||||
96 | $this->style = $style ?: new MenuStyle($this->terminal); |
||||
97 | |||||
98 | $this->selectFirstItem(); |
||||
99 | } |
||||
100 | |||||
101 | /** |
||||
102 | * Configure the terminal to work with CliMenu |
||||
103 | */ |
||||
104 | protected function configureTerminal() : void |
||||
105 | { |
||||
106 | $this->assertTerminalIsValidTTY(); |
||||
107 | |||||
108 | $this->terminal->disableCanonicalMode(); |
||||
109 | $this->terminal->disableEchoBack(); |
||||
110 | $this->terminal->disableCursor(); |
||||
111 | $this->terminal->clear(); |
||||
112 | } |
||||
113 | |||||
114 | /** |
||||
115 | * Revert changes made to the terminal |
||||
116 | */ |
||||
117 | protected function tearDownTerminal() : void |
||||
118 | { |
||||
119 | $this->terminal->restoreOriginalConfiguration(); |
||||
120 | $this->terminal->enableCursor(); |
||||
121 | } |
||||
122 | |||||
123 | private function assertTerminalIsValidTTY() : void |
||||
124 | { |
||||
125 | if (!$this->terminal->isInteractive()) { |
||||
126 | throw new InvalidTerminalException('Terminal is not interactive (TTY)'); |
||||
127 | } |
||||
128 | } |
||||
129 | |||||
130 | public function setTitle(string $title) : void |
||||
131 | { |
||||
132 | $this->title = $title; |
||||
133 | } |
||||
134 | |||||
135 | public function setParent(CliMenu $parent) : void |
||||
136 | { |
||||
137 | $this->parent = $parent; |
||||
138 | } |
||||
139 | |||||
140 | public function getParent() : ?CliMenu |
||||
141 | { |
||||
142 | return $this->parent; |
||||
143 | } |
||||
144 | |||||
145 | public function getTerminal() : Terminal |
||||
146 | { |
||||
147 | return $this->terminal; |
||||
148 | } |
||||
149 | |||||
150 | public function isOpen() : bool |
||||
151 | { |
||||
152 | return $this->open; |
||||
153 | } |
||||
154 | |||||
155 | /** |
||||
156 | * Add a new Item to the menu |
||||
157 | */ |
||||
158 | public function addItem(MenuItemInterface $item) : void |
||||
159 | { |
||||
160 | $this->items[] = $item; |
||||
161 | |||||
162 | $this->selectFirstItem(); |
||||
163 | } |
||||
164 | |||||
165 | /** |
||||
166 | * Add multiple Items to the menu |
||||
167 | */ |
||||
168 | public function addItems(array $items) : void |
||||
169 | { |
||||
170 | foreach ($items as $item) { |
||||
171 | $this->items[] = $item; |
||||
172 | } |
||||
173 | |||||
174 | $this->selectFirstItem(); |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * Set Items of the menu |
||||
179 | */ |
||||
180 | public function setItems(array $items) : void |
||||
181 | { |
||||
182 | $this->selectedItem = null; |
||||
183 | $this->items = $items; |
||||
184 | |||||
185 | $this->selectFirstItem(); |
||||
186 | } |
||||
187 | |||||
188 | /** |
||||
189 | * Set the selected pointer to the first selectable item |
||||
190 | */ |
||||
191 | private function selectFirstItem() : void |
||||
192 | { |
||||
193 | if (null === $this->selectedItem) { |
||||
194 | foreach ($this->items as $key => $item) { |
||||
195 | if ($item->canSelect()) { |
||||
196 | $this->selectedItem = $key; |
||||
197 | break; |
||||
198 | } |
||||
199 | } |
||||
200 | } |
||||
201 | } |
||||
202 | |||||
203 | /** |
||||
204 | * Disables the built-in VIM control mappings |
||||
205 | */ |
||||
206 | public function disableDefaultControlMappings() : void |
||||
207 | { |
||||
208 | $this->defaultControlMappings = []; |
||||
209 | } |
||||
210 | |||||
211 | /** |
||||
212 | * Set default control mappings |
||||
213 | */ |
||||
214 | public function setDefaultControlMappings(array $defaultControlMappings) : void |
||||
215 | { |
||||
216 | $this->defaultControlMappings = $defaultControlMappings; |
||||
217 | } |
||||
218 | |||||
219 | /** |
||||
220 | * Adds a custom control mapping |
||||
221 | */ |
||||
222 | public function addCustomControlMapping(string $input, callable $callable) : void |
||||
223 | { |
||||
224 | if (isset($this->defaultControlMappings[$input]) || isset($this->customControlMappings[$input])) { |
||||
225 | throw new \InvalidArgumentException('Cannot rebind this input'); |
||||
226 | } |
||||
227 | |||||
228 | $this->customControlMappings[$input] = $callable; |
||||
229 | } |
||||
230 | |||||
231 | /** |
||||
232 | * Shorthand function to add multiple custom control mapping at once |
||||
233 | */ |
||||
234 | public function addCustomControlMappings(array $map) : void |
||||
235 | { |
||||
236 | foreach ($map as $input => $callable) { |
||||
237 | $this->addCustomControlMapping($input, $callable); |
||||
238 | } |
||||
239 | } |
||||
240 | |||||
241 | /** |
||||
242 | * Removes a custom control mapping |
||||
243 | */ |
||||
244 | public function removeCustomControlMapping(string $input) : void |
||||
245 | { |
||||
246 | if (!isset($this->customControlMappings[$input])) { |
||||
247 | throw new \InvalidArgumentException('This input is not registered'); |
||||
248 | } |
||||
249 | |||||
250 | unset($this->customControlMappings[$input]); |
||||
251 | } |
||||
252 | |||||
253 | /** |
||||
254 | * Display menu and capture input |
||||
255 | */ |
||||
256 | private function display() : void |
||||
257 | { |
||||
258 | $this->draw(); |
||||
259 | |||||
260 | $reader = new NonCanonicalReader($this->terminal); |
||||
261 | $reader->addControlMappings($this->defaultControlMappings); |
||||
262 | |||||
263 | while ($this->isOpen()) { |
||||
264 | $char = $reader->readCharacter(); |
||||
265 | if (!$char->isHandledControl()) { |
||||
266 | $rawChar = $char->get(); |
||||
267 | if (isset($this->customControlMappings[$rawChar])) { |
||||
268 | $this->customControlMappings[$rawChar]($this); |
||||
269 | } |
||||
270 | continue; |
||||
271 | } |
||||
272 | |||||
273 | switch ($char->getControl()) { |
||||
274 | case InputCharacter::UP: |
||||
275 | case InputCharacter::DOWN: |
||||
276 | $this->moveSelectionVertically($char->getControl()); |
||||
277 | $this->draw(); |
||||
278 | break; |
||||
279 | case InputCharacter::LEFT: |
||||
280 | case InputCharacter::RIGHT: |
||||
281 | $this->moveSelectionHorizontally($char->getControl()); |
||||
282 | $this->draw(); |
||||
283 | break; |
||||
284 | case InputCharacter::ENTER: |
||||
285 | $this->executeCurrentItem(); |
||||
286 | break; |
||||
287 | } |
||||
288 | } |
||||
289 | } |
||||
290 | |||||
291 | /** |
||||
292 | * Move the selection in a given direction, up / down |
||||
293 | */ |
||||
294 | protected function moveSelectionVertically(string $direction) : void |
||||
295 | { |
||||
296 | $itemKeys = array_keys($this->items); |
||||
297 | |||||
298 | $increments = 0; |
||||
299 | |||||
300 | do { |
||||
301 | $increments++; |
||||
302 | |||||
303 | if ($increments > count($itemKeys)) { |
||||
304 | //full cycle detected, there must be no selected items |
||||
305 | //in the menu, so stop trying to select one. |
||||
306 | return; |
||||
307 | } |
||||
308 | |||||
309 | $direction === 'UP' |
||||
310 | ? $this->selectedItem-- |
||||
311 | : $this->selectedItem++; |
||||
312 | |||||
313 | if ($this->selectedItem !== null && !array_key_exists($this->selectedItem, $this->items)) { |
||||
314 | $this->selectedItem = $direction === 'UP' |
||||
315 | ? end($itemKeys) |
||||
316 | : reset($itemKeys); |
||||
317 | } |
||||
318 | } while (!$this->canSelect()); |
||||
319 | } |
||||
320 | |||||
321 | /** |
||||
322 | * Move the selection in a given direction, left / right |
||||
323 | */ |
||||
324 | protected function moveSelectionHorizontally(string $direction) : void |
||||
325 | { |
||||
326 | if (!$this->items[$this->selectedItem] instanceof SplitItem) { |
||||
327 | return; |
||||
328 | } |
||||
329 | |||||
330 | /** @var SplitItem $item */ |
||||
331 | $item = $this->items[$this->selectedItem]; |
||||
332 | $itemKeys = array_keys($item->getItems()); |
||||
333 | $selectedItemIndex = $item->getSelectedItemIndex(); |
||||
334 | |||||
335 | do { |
||||
336 | $direction === 'LEFT' |
||||
337 | ? $selectedItemIndex-- |
||||
338 | : $selectedItemIndex++; |
||||
339 | |||||
340 | if (!array_key_exists($selectedItemIndex, $item->getItems())) { |
||||
341 | $selectedItemIndex = $direction === 'LEFT' |
||||
342 | ? end($itemKeys) |
||||
343 | : reset($itemKeys); |
||||
344 | } |
||||
345 | } while (!$item->canSelectIndex($selectedItemIndex)); |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
346 | |||||
347 | $item->setSelectedItemIndex($selectedItemIndex); |
||||
0 ignored issues
–
show
It seems like
$selectedItemIndex can also be of type null ; however, parameter $index of PhpSchool\CliMenu\MenuIt...:setSelectedItemIndex() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
348 | } |
||||
349 | |||||
350 | /** |
||||
351 | * Can the currently selected item actually be selected? |
||||
352 | * |
||||
353 | * For example: |
||||
354 | * selectable item -> yes |
||||
355 | * static item -> no |
||||
356 | * split item with only static items -> no |
||||
357 | * split item with at least one selectable item -> yes |
||||
358 | * |
||||
359 | * @return bool |
||||
360 | */ |
||||
361 | private function canSelect() : bool |
||||
362 | { |
||||
363 | return $this->items[$this->selectedItem]->canSelect(); |
||||
364 | } |
||||
365 | |||||
366 | /** |
||||
367 | * Retrieve the item the user actually selected |
||||
368 | * |
||||
369 | */ |
||||
370 | public function getSelectedItem() : MenuItemInterface |
||||
371 | { |
||||
372 | if (null === $this->selectedItem) { |
||||
373 | throw new \RuntimeException('No selected item'); |
||||
374 | } |
||||
375 | |||||
376 | $item = $this->items[$this->selectedItem]; |
||||
377 | return $item instanceof SplitItem |
||||
378 | ? $item->getSelectedItem() |
||||
379 | : $item; |
||||
380 | } |
||||
381 | |||||
382 | public function setSelectedItem(MenuItemInterface $item) : void |
||||
383 | { |
||||
384 | $key = array_search($item, $this->items, true); |
||||
385 | |||||
386 | if (false === $key) { |
||||
387 | throw new \InvalidArgumentException('Item does not exist in menu'); |
||||
388 | } |
||||
389 | |||||
390 | $this->selectedItem = $key; |
||||
391 | } |
||||
392 | |||||
393 | public function executeAsSelected(MenuItemInterface $item) : void |
||||
394 | { |
||||
395 | $current = $this->items[$this->selectedItem]; |
||||
396 | $this->setSelectedItem($item); |
||||
397 | $this->executeCurrentItem(); |
||||
398 | $this->setSelectedItem($current); |
||||
399 | } |
||||
400 | |||||
401 | /** |
||||
402 | * Execute the current item |
||||
403 | */ |
||||
404 | protected function executeCurrentItem() : void |
||||
405 | { |
||||
406 | $item = $this->getSelectedItem(); |
||||
407 | |||||
408 | if ($item->canSelect()) { |
||||
409 | $callable = $item->getSelectAction(); |
||||
410 | if ($callable) { |
||||
411 | $callable($this); |
||||
412 | } |
||||
413 | } |
||||
414 | } |
||||
415 | |||||
416 | /** |
||||
417 | * If true we clear the whole terminal screen, useful |
||||
418 | * for example when reducing the width of the menu, to not |
||||
419 | * leave leftovers of the previous wider menu. |
||||
420 | * |
||||
421 | * Redraw the menu |
||||
422 | */ |
||||
423 | public function redraw(bool $clear = false) : void |
||||
424 | { |
||||
425 | if ($clear) { |
||||
426 | $this->terminal->clear(); |
||||
427 | } |
||||
428 | |||||
429 | $this->assertOpen(); |
||||
430 | $this->draw(); |
||||
431 | } |
||||
432 | |||||
433 | private function assertOpen() : void |
||||
434 | { |
||||
435 | if (!$this->isOpen()) { |
||||
436 | throw new MenuNotOpenException; |
||||
437 | } |
||||
438 | } |
||||
439 | |||||
440 | /** |
||||
441 | * Draw the menu to STDOUT |
||||
442 | */ |
||||
443 | protected function draw() : void |
||||
444 | { |
||||
445 | $frame = new Frame; |
||||
446 | |||||
447 | $frame->newLine(2); |
||||
448 | |||||
449 | if ($this->style->getBorderTopWidth() > 0) { |
||||
450 | $frame->addRows($this->style->getBorderTopRows()); |
||||
451 | } |
||||
452 | |||||
453 | if ($this->style->getPaddingTopBottom() > 0) { |
||||
454 | $frame->addRows($this->style->getPaddingTopBottomRows()); |
||||
455 | } |
||||
456 | |||||
457 | if ($this->title) { |
||||
458 | $frame->addRows($this->drawMenuItem(new StaticItem($this->title))); |
||||
459 | $frame->addRows($this->drawMenuItem(new LineBreakItem($this->style->getTitleSeparator()))); |
||||
460 | } |
||||
461 | |||||
462 | array_map(function ($item, $index) use ($frame) { |
||||
463 | $frame->addRows($this->drawMenuItem($item, $index === $this->selectedItem)); |
||||
464 | }, $this->items, array_keys($this->items)); |
||||
465 | |||||
466 | |||||
467 | if ($this->style->getPaddingTopBottom() > 0) { |
||||
468 | $frame->addRows($this->style->getPaddingTopBottomRows()); |
||||
469 | } |
||||
470 | |||||
471 | if ($this->style->getBorderBottomWidth() > 0) { |
||||
472 | $frame->addRows($this->style->getBorderBottomRows()); |
||||
473 | } |
||||
474 | |||||
475 | $frame->newLine(2); |
||||
476 | |||||
477 | $this->terminal->moveCursorToTop(); |
||||
478 | foreach ($frame->getRows() as $row) { |
||||
479 | if ($row == "\n") { |
||||
480 | $this->terminal->clearLine(); |
||||
481 | } |
||||
482 | $this->terminal->write($row); |
||||
483 | } |
||||
484 | $this->terminal->clearDown(); |
||||
485 | |||||
486 | $this->currentFrame = $frame; |
||||
487 | } |
||||
488 | |||||
489 | /** |
||||
490 | * Draw a menu item |
||||
491 | */ |
||||
492 | protected function drawMenuItem(MenuItemInterface $item, bool $selected = false) : array |
||||
493 | { |
||||
494 | $rows = $item->getRows($this->style, $selected); |
||||
495 | |||||
496 | if ($item instanceof SplitItem) { |
||||
497 | $selected = false; |
||||
498 | } |
||||
499 | |||||
500 | $invertedColoursSetCode = $selected |
||||
501 | ? $this->style->getInvertedColoursSetCode() |
||||
502 | : ''; |
||||
503 | $invertedColoursUnsetCode = $selected |
||||
504 | ? $this->style->getInvertedColoursUnsetCode() |
||||
505 | : ''; |
||||
506 | |||||
507 | if ($this->style->getBorderLeftWidth() || $this->style->getBorderRightWidth()) { |
||||
508 | $borderColour = $this->style->getBorderColourCode(); |
||||
509 | } else { |
||||
510 | $borderColour = ''; |
||||
511 | } |
||||
512 | |||||
513 | return array_map(function ($row) use ($invertedColoursSetCode, $invertedColoursUnsetCode, $borderColour) { |
||||
514 | return sprintf( |
||||
515 | "%s%s%s%s%s%s%s%s%s%s%s%s\n", |
||||
516 | str_repeat(' ', $this->style->getMargin()), |
||||
517 | $borderColour, |
||||
518 | str_repeat(' ', $this->style->getBorderLeftWidth()), |
||||
519 | $this->style->getColoursSetCode(), |
||||
520 | $invertedColoursSetCode, |
||||
521 | str_repeat(' ', $this->style->getPaddingLeftRight()), |
||||
522 | $row, |
||||
523 | str_repeat(' ', $this->style->getRightHandPadding(mb_strlen(s::stripAnsiEscapeSequence($row)))), |
||||
524 | $invertedColoursUnsetCode, |
||||
525 | $borderColour, |
||||
526 | str_repeat(' ', $this->style->getBorderRightWidth()), |
||||
527 | $this->style->getColoursResetCode() |
||||
528 | ); |
||||
529 | }, $rows); |
||||
530 | } |
||||
531 | |||||
532 | /** |
||||
533 | * @throws InvalidTerminalException |
||||
534 | */ |
||||
535 | public function open() : void |
||||
536 | { |
||||
537 | if ($this->isOpen()) { |
||||
538 | return; |
||||
539 | } |
||||
540 | |||||
541 | if (count($this->items) === 0) { |
||||
542 | throw new \RuntimeException('Menu must have at least 1 item before it can be opened'); |
||||
543 | } |
||||
544 | |||||
545 | $this->configureTerminal(); |
||||
546 | $this->open = true; |
||||
547 | $this->display(); |
||||
548 | } |
||||
549 | |||||
550 | /** |
||||
551 | * Close the menu |
||||
552 | * |
||||
553 | * @throws InvalidTerminalException |
||||
554 | */ |
||||
555 | public function close() : void |
||||
556 | { |
||||
557 | $menu = $this; |
||||
558 | |||||
559 | do { |
||||
560 | $menu->closeThis(); |
||||
561 | $menu = $menu->getParent(); |
||||
562 | } while (null !== $menu); |
||||
563 | |||||
564 | $this->tearDownTerminal(); |
||||
565 | } |
||||
566 | |||||
567 | public function closeThis() : void |
||||
568 | { |
||||
569 | $this->terminal->clean(); |
||||
570 | $this->terminal->moveCursorToTop(); |
||||
571 | $this->open = false; |
||||
572 | } |
||||
573 | |||||
574 | /** |
||||
575 | * @return MenuItemInterface[] |
||||
576 | */ |
||||
577 | public function getItems() : array |
||||
578 | { |
||||
579 | return $this->items; |
||||
580 | } |
||||
581 | |||||
582 | public function removeItem(MenuItemInterface $item) : void |
||||
583 | { |
||||
584 | $key = array_search($item, $this->items, true); |
||||
585 | |||||
586 | if (false === $key) { |
||||
587 | throw new \InvalidArgumentException('Item does not exist in menu'); |
||||
588 | } |
||||
589 | |||||
590 | unset($this->items[$key]); |
||||
591 | $this->items = array_values($this->items); |
||||
592 | |||||
593 | if ($this->selectedItem === $key) { |
||||
594 | $this->selectedItem = null; |
||||
595 | $this->selectFirstItem(); |
||||
596 | } |
||||
597 | } |
||||
598 | |||||
599 | public function getStyle() : MenuStyle |
||||
600 | { |
||||
601 | return $this->style; |
||||
602 | } |
||||
603 | |||||
604 | public function setStyle(MenuStyle $style) : void |
||||
605 | { |
||||
606 | $this->style = $style; |
||||
607 | } |
||||
608 | |||||
609 | public function getCurrentFrame() : Frame |
||||
610 | { |
||||
611 | return $this->currentFrame; |
||||
612 | } |
||||
613 | |||||
614 | public function flash(string $text, MenuStyle $style = null) : Flash |
||||
615 | { |
||||
616 | $this->guardSingleLine($text); |
||||
617 | |||||
618 | $style = $style ?? (new MenuStyle($this->terminal)) |
||||
619 | ->setBg('yellow') |
||||
620 | ->setFg('red'); |
||||
621 | |||||
622 | return new Flash($this, $style, $this->terminal, $text); |
||||
623 | } |
||||
624 | |||||
625 | public function confirm(string $text, MenuStyle $style = null) : Confirm |
||||
626 | { |
||||
627 | $this->guardSingleLine($text); |
||||
628 | |||||
629 | $style = $style ?? (new MenuStyle($this->terminal)) |
||||
630 | ->setBg('yellow') |
||||
631 | ->setFg('red'); |
||||
632 | |||||
633 | return new Confirm($this, $style, $this->terminal, $text); |
||||
634 | } |
||||
635 | |||||
636 | public function askNumber(MenuStyle $style = null) : Number |
||||
637 | { |
||||
638 | $this->assertOpen(); |
||||
639 | |||||
640 | $style = $style ?? (new MenuStyle($this->terminal)) |
||||
641 | ->setBg('yellow') |
||||
642 | ->setFg('red'); |
||||
643 | |||||
644 | return new Number(new InputIO($this, $this->terminal), $style); |
||||
645 | } |
||||
646 | |||||
647 | public function askText(MenuStyle $style = null) : Text |
||||
648 | { |
||||
649 | $this->assertOpen(); |
||||
650 | |||||
651 | $style = $style ?? (new MenuStyle($this->terminal)) |
||||
652 | ->setBg('yellow') |
||||
653 | ->setFg('red'); |
||||
654 | |||||
655 | return new Text(new InputIO($this, $this->terminal), $style); |
||||
656 | } |
||||
657 | |||||
658 | public function askPassword(MenuStyle $style = null) : Password |
||||
659 | { |
||||
660 | $this->assertOpen(); |
||||
661 | |||||
662 | $style = $style ?? (new MenuStyle($this->terminal)) |
||||
663 | ->setBg('yellow') |
||||
664 | ->setFg('red'); |
||||
665 | |||||
666 | return new Password(new InputIO($this, $this->terminal), $style); |
||||
667 | } |
||||
668 | |||||
669 | private function guardSingleLine($text) : void |
||||
670 | { |
||||
671 | if (strpos($text, "\n") !== false) { |
||||
672 | throw new \InvalidArgumentException; |
||||
673 | } |
||||
674 | } |
||||
675 | } |
||||
676 |