Completed
Push — master ( e920ad...bc045d )
by Aydin
01:58
created

src/MenuStyle.php (3 issues)

1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Exception\InvalidInstantiationException;
0 ignored issues
show
The type PhpSchool\CliMenu\Except...dInstantiationException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use PhpSchool\CliMenu\Terminal\TerminalFactory;
7
use PhpSchool\CliMenu\Util\ColourUtil;
8
use PhpSchool\Terminal\Terminal;
9
use Assert\Assertion;
10
11
//TODO: B/W fallback
12
13
/**
14
 * @author Michael Woodward <[email protected]>
15
 */
16
class MenuStyle
17
{
18
    /**
19
     * @var Terminal
20
     */
21
    protected $terminal;
22
23
    /**
24
     * @var string
25
     */
26
    protected $fg;
27
28
    /**
29
     * @var string
30
     */
31
    protected $bg;
32
33
    /**
34
     * @var int
35
     */
36
    protected $width;
37
38
    /**
39
     * @var int
40
     */
41
    protected $margin;
42
43
    /**
44
     * @var int
45
     */
46
    protected $paddingTopBottom;
47
48
    /**
49
     * @var int
50
     */
51
    protected $paddingLeftRight;
52
53
    /**
54
     * @var array
55
     */
56
    private $paddingTopBottomRows = [];
57
58
    /**
59
     * @var int
60
     */
61
    protected $contentWidth;
62
63
    /**
64
     * @var string
65
     */
66
    private $selectedMarker;
67
68
    /**
69
     * @var string
70
     */
71
    private $unselectedMarker;
72
73
    /**
74
     * @var string
75
     */
76
    private $itemExtra;
77
78
    /**
79
     * @var bool
80
     */
81
    private $displaysExtra;
82
83
    /**
84
     * @var string
85
     */
86
    private $titleSeparator;
87
88
    /**
89
     * @var string
90
     */
91
    private $coloursSetCode;
92
93
    /**
94
     * @var string
95
     */
96
    private $invertedColoursSetCode = "\033[7m";
97
98
    /**
99
     * @var string
100
     */
101
    private $invertedColoursUnsetCode = "\033[27m";
102
103
    /**
104
     * @var string
105
     */
106
    private $coloursResetCode = "\033[0m";
107
108
    /**
109
     * @var int
110
     */
111
    private $borderTopWidth;
112
113
    /**
114
     * @var int
115
     */
116
    private $borderRightWidth;
117
118
    /**
119
     * @var int
120
     */
121
    private $borderBottomWidth;
122
123
    /**
124
     * @var int
125
     */
126
    private $borderLeftWidth;
127
128
    /**
129
     * @var string
130
     */
131
    private $borderColour = 'white';
132
133
    /**
134
     * @var array
135
     */
136
    private $borderTopRows = [];
137
138
    /**
139
     * @var array
140
     */
141
    private $borderBottomRows = [];
142
143
    /**
144
     * @var bool
145
     */
146
    private $marginAuto = false;
147
148
    /**
149
     * Default Values
150
     *
151
     * @var array
152
     */
153
    private static $defaultStyleValues = [
154
        'fg' => 'white',
155
        'bg' => 'blue',
156
        'width' => 100,
157
        'paddingTopBottom' => 1,
158
        'paddingLeftRight' => 2,
159
        'margin' => 2,
160
        'selectedMarker' => '● ',
161
        'unselectedMarker' => '○ ',
162
        'itemExtra' => '✔',
163
        'displaysExtra' => false,
164
        'titleSeparator' => '=',
165
        'borderTopWidth' => 0,
166
        'borderRightWidth' => 0,
167
        'borderBottomWidth' => 0,
168
        'borderLeftWidth' => 0,
169
        'borderColour' => 'white',
170
        'marginAuto' => false,
171
    ];
172
173
    /**
174
     * @var array
175
     */
176
    private static $availableForegroundColors = array(
177
        'black'   => 30,
178
        'red'     => 31,
179
        'green'   => 32,
180
        'yellow'  => 33,
181
        'blue'    => 34,
182
        'magenta' => 35,
183
        'cyan'    => 36,
184
        'white'   => 37,
185
        'default' => 39,
186
    );
187
188
    /**
189
     * @var array
190
     */
191
    private static $availableBackgroundColors = array(
192
        'black'   => 40,
193
        'red'     => 41,
194
        'green'   => 42,
195
        'yellow'  => 43,
196
        'blue'    => 44,
197
        'magenta' => 45,
198
        'cyan'    => 46,
199
        'white'   => 47,
200
        'default' => 49,
201
    );
202
203
    /**
204
     * @var array
205
     */
206
    private static $availableOptions = array(
207
        'bold'       => array('set' => 1, 'unset' => 22),
208
        'dim'        => array('set' => 2, 'unset' => 22),
209
        'underscore' => array('set' => 4, 'unset' => 24),
210
        'blink'      => array('set' => 5, 'unset' => 25),
211
        'reverse'    => array('set' => 7, 'unset' => 27),
212
        'conceal'    => array('set' => 8, 'unset' => 28)
213
    );
214
215
    /**
216
     * Initialise style
217
     */
218
    public function __construct(Terminal $terminal = null)
219
    {
220
        $this->terminal = $terminal ?: TerminalFactory::fromSystem();
221
222
        $this->fg = static::$defaultStyleValues['fg'];
0 ignored issues
show
Since $defaultStyleValues is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $defaultStyleValues to at least protected.
Loading history...
223
        $this->bg = static::$defaultStyleValues['bg'];
224
225
        $this->generateColoursSetCode();
226
227
        $this->setWidth(static::$defaultStyleValues['width']);
228
        $this->setPaddingTopBottom(static::$defaultStyleValues['paddingTopBottom']);
229
        $this->setPaddingLeftRight(static::$defaultStyleValues['paddingLeftRight']);
230
        $this->setMargin(static::$defaultStyleValues['margin']);
231
        $this->setSelectedMarker(static::$defaultStyleValues['selectedMarker']);
232
        $this->setUnselectedMarker(static::$defaultStyleValues['unselectedMarker']);
233
        $this->setItemExtra(static::$defaultStyleValues['itemExtra']);
234
        $this->setDisplaysExtra(static::$defaultStyleValues['displaysExtra']);
235
        $this->setTitleSeparator(static::$defaultStyleValues['titleSeparator']);
236
        $this->setBorderTopWidth(static::$defaultStyleValues['borderTopWidth']);
237
        $this->setBorderRightWidth(static::$defaultStyleValues['borderRightWidth']);
238
        $this->setBorderBottomWidth(static::$defaultStyleValues['borderBottomWidth']);
239
        $this->setBorderLeftWidth(static::$defaultStyleValues['borderLeftWidth']);
240
        $this->setBorderColour(static::$defaultStyleValues['borderColour']);
241
    }
242
243
    public function hasChangedFromDefaults() : bool
244
    {
245
        $currentValues = [
246
            $this->fg,
247
            $this->bg,
248
            $this->width,
249
            $this->paddingTopBottom,
250
            $this->paddingLeftRight,
251
            $this->margin,
252
            $this->selectedMarker,
253
            $this->unselectedMarker,
254
            $this->itemExtra,
255
            $this->displaysExtra,
256
            $this->titleSeparator,
257
            $this->borderTopWidth,
258
            $this->borderRightWidth,
259
            $this->borderBottomWidth,
260
            $this->borderLeftWidth,
261
            $this->borderColour,
262
            $this->marginAuto,
263
        ];
264
                
265
        return $currentValues !== array_values(static::$defaultStyleValues);
0 ignored issues
show
Since $defaultStyleValues is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $defaultStyleValues to at least protected.
Loading history...
266
    }
267
268
    public function getDisabledItemText(string $text) : string
269
    {
270
        return sprintf(
271
            "\033[%sm%s\033[%sm",
272
            self::$availableOptions['dim']['set'],
273
            $text,
274
            self::$availableOptions['dim']['unset']
275
        );
276
    }
277
278
    /**
279
     * Generates the ansi escape sequence to set the colours
280
     */
281
    private function generateColoursSetCode() : void
282
    {
283
        if (!ctype_digit($this->fg)) {
284
            $fgCode = self::$availableForegroundColors[$this->fg];
285
        } else {
286
            $fgCode = sprintf("38;5;%s", $this->fg);
287
        }
288
289
        if (!ctype_digit($this->bg)) {
290
            $bgCode = self::$availableBackgroundColors[$this->bg];
291
        } else {
292
            $bgCode = sprintf("48;5;%s", $this->bg);
293
        }
294
295
        $this->coloursSetCode = sprintf("\033[%s;%sm", $fgCode, $bgCode);
296
    }
297
298
    /**
299
     * Get the colour code for Bg and Fg
300
     */
301
    public function getColoursSetCode() : string
302
    {
303
        return $this->coloursSetCode;
304
    }
305
306
    /**
307
     * Get the inverted escape sequence (used for selected elements)
308
     */
309
    public function getInvertedColoursSetCode() : string
310
    {
311
        return $this->invertedColoursSetCode;
312
    }
313
314
    /**
315
     * Get the inverted escape sequence (used for selected elements)
316
     */
317
    public function getInvertedColoursUnsetCode() : string
318
    {
319
        return $this->invertedColoursUnsetCode;
320
    }
321
322
    /**
323
     * Get the escape sequence used to reset colours to default
324
     */
325
    public function getColoursResetCode() : string
326
    {
327
        return $this->coloursResetCode;
328
    }
329
330
    /**
331
     * Calculate the contents width
332
     */
333
    protected function calculateContentWidth() : void
334
    {
335
        $this->contentWidth = $this->width
336
            - ($this->paddingLeftRight * 2)
337
            - ($this->borderRightWidth + $this->borderLeftWidth);
338
339
        if ($this->contentWidth < 0) {
340
            $this->contentWidth = 0;
341
        }
342
    }
343
344
    public function getFg()
345
    {
346
        return $this->fg;
347
    }
348
349
    public function setFg(string $fg, string $fallback = null) : self
350
    {
351
        $this->fg = ColourUtil::validateColour(
352
            $this->terminal,
353
            $fg,
354
            $fallback
355
        );
356
        $this->generateColoursSetCode();
357
358
        return $this;
359
    }
360
361
    public function getBg()
362
    {
363
        return $this->bg;
364
    }
365
366
    public function setBg(string $bg, string $fallback = null) : self
367
    {
368
        $this->bg = ColourUtil::validateColour(
369
            $this->terminal,
370
            $bg,
371
            $fallback
372
        );
373
374
        $this->generateColoursSetCode();
375
        $this->generatePaddingTopBottomRows();
376
377
        return $this;
378
    }
379
380
    public function getWidth() : int
381
    {
382
        return $this->width;
383
    }
384
385
    public function setWidth(int $width) : self
386
    {
387
        Assertion::greaterOrEqualThan($width, 0);
388
389
        if ($width >= $this->terminal->getWidth()) {
390
            $width = $this->terminal->getWidth();
391
        }
392
393
        $this->width = $width;
394
        if ($this->marginAuto) {
395
            $this->setMarginAuto();
396
        }
397
398
        $this->calculateContentWidth();
399
        $this->generateBorderRows();
400
        $this->generatePaddingTopBottomRows();
401
402
        return $this;
403
    }
404
405
    public function getPaddingTopBottom() : int
406
    {
407
        return $this->paddingTopBottom;
408
    }
409
410
    public function getPaddingLeftRight() : int
411
    {
412
        return $this->paddingLeftRight;
413
    }
414
415
    private function generatePaddingTopBottomRows() : void
416
    {
417
        if ($this->borderLeftWidth || $this->borderRightWidth) {
418
            $borderColour = $this->getBorderColourCode();
419
        } else {
420
            $borderColour = '';
421
        }
422
423
        $paddingRow = sprintf(
424
            "%s%s%s%s%s%s%s%s%s%s\n",
425
            str_repeat(' ', $this->margin),
426
            $borderColour,
427
            str_repeat(' ', $this->borderLeftWidth),
428
            $this->getColoursSetCode(),
429
            str_repeat(' ', $this->paddingLeftRight),
430
            str_repeat(' ', $this->contentWidth),
431
            str_repeat(' ', $this->paddingLeftRight),
432
            $borderColour,
433
            str_repeat(' ', $this->borderRightWidth),
434
            $this->coloursResetCode
435
        );
436
437
        $this->paddingTopBottomRows = array_fill(0, $this->paddingTopBottom, $paddingRow);
438
    }
439
440
    public function getPaddingTopBottomRows() : array
441
    {
442
        return $this->paddingTopBottomRows;
443
    }
444
445
    public function setPadding(int $topBottom, int $leftRight = null) : self
446
    {
447
        if ($leftRight === null) {
448
            $leftRight = $topBottom;
449
        }
450
451
        $this->setPaddingTopBottom($topBottom);
452
        $this->setPaddingLeftRight($leftRight);
453
454
        $this->calculateContentWidth();
455
        $this->generatePaddingTopBottomRows();
456
457
        return $this;
458
    }
459
460
    public function setPaddingTopBottom(int $topBottom) : self
461
    {
462
        Assertion::greaterOrEqualThan($topBottom, 0);
463
        $this->paddingTopBottom = $topBottom;
464
465
        $this->generatePaddingTopBottomRows();
466
467
        return $this;
468
    }
469
470
    public function setPaddingLeftRight(int $leftRight) : self
471
    {
472
        Assertion::greaterOrEqualThan($leftRight, 0);
473
        $this->paddingLeftRight = $leftRight;
474
475
        $this->calculateContentWidth();
476
        $this->generatePaddingTopBottomRows();
477
478
        return $this;
479
    }
480
481
    public function getMargin() : int
482
    {
483
        return $this->margin;
484
    }
485
486
    public function setMarginAuto() : self
487
    {
488
        $this->marginAuto = true;
489
        $this->margin = floor(($this->terminal->getWidth() - $this->width) / 2);
490
491
        $this->generateBorderRows();
492
        $this->generatePaddingTopBottomRows();
493
494
        return $this;
495
    }
496
497
    public function setMargin(int $margin) : self
498
    {
499
        Assertion::greaterOrEqualThan($margin, 0);
500
501
        $this->marginAuto = false;
502
        $this->margin = $margin;
503
504
        $this->generateBorderRows();
505
        $this->generatePaddingTopBottomRows();
506
507
        return $this;
508
    }
509
510
    public function getContentWidth() : int
511
    {
512
        return $this->contentWidth;
513
    }
514
515
    /**
516
     * Get padding for right had side of content
517
     */
518
    public function getRightHandPadding(int $contentLength) : int
519
    {
520
        $rightPadding = $this->getContentWidth() - $contentLength + $this->getPaddingLeftRight();
521
522
        if ($rightPadding < 0) {
523
            $rightPadding = 0;
524
        }
525
526
        return $rightPadding;
527
    }
528
529
    public function getSelectedMarker() : string
530
    {
531
        return $this->selectedMarker;
532
    }
533
534
    public function setSelectedMarker(string $marker) : self
535
    {
536
        $this->selectedMarker = $marker;
537
538
        return $this;
539
    }
540
541
    public function getUnselectedMarker() : string
542
    {
543
        return $this->unselectedMarker;
544
    }
545
546
    public function setUnselectedMarker(string $marker) : self
547
    {
548
        $this->unselectedMarker = $marker;
549
550
        return $this;
551
    }
552
553
    /**
554
     * Get the correct marker for the item
555
     */
556
    public function getMarker(bool $selected) : string
557
    {
558
        return $selected ? $this->selectedMarker : $this->unselectedMarker;
559
    }
560
561
    public function setItemExtra(string $itemExtra) : self
562
    {
563
        $this->itemExtra = $itemExtra;
564
565
        return $this;
566
    }
567
568
    public function getItemExtra() : string
569
    {
570
        return $this->itemExtra;
571
    }
572
573
    public function getDisplaysExtra() : bool
574
    {
575
        return $this->displaysExtra;
576
    }
577
578
    public function setDisplaysExtra(bool $displaysExtra) : self
579
    {
580
        $this->displaysExtra = $displaysExtra;
581
582
        return $this;
583
    }
584
585
    public function getTitleSeparator() : string
586
    {
587
        return $this->titleSeparator;
588
    }
589
590
    public function setTitleSeparator(string $actionSeparator) : self
591
    {
592
        $this->titleSeparator = $actionSeparator;
593
594
        return $this;
595
    }
596
597
    private function generateBorderRows() : void
598
    {
599
        $borderRow = sprintf(
600
            "%s%s%s%s\n",
601
            str_repeat(' ', $this->margin),
602
            $this->getBorderColourCode(),
603
            str_repeat(' ', $this->width),
604
            $this->coloursResetCode
605
        );
606
607
        $this->borderTopRows = array_fill(0, $this->borderTopWidth, $borderRow);
608
        $this->borderBottomRows = array_fill(0, $this->borderBottomWidth, $borderRow);
609
    }
610
611
    public function getBorderTopRows() : array
612
    {
613
        return $this->borderTopRows;
614
    }
615
616
    public function getBorderBottomRows() : array
617
    {
618
        return $this->borderBottomRows;
619
    }
620
621
    /**
622
     * Shorthand function to set all borders values at once
623
     */
624
    public function setBorder(
625
        int $topWidth,
626
        $rightWidth = null,
627
        $bottomWidth = null,
628
        $leftWidth = null,
629
        string $colour = null
630
    ) : self {
631
        if (!is_int($rightWidth)) {
632
            $colour = $rightWidth;
633
            $rightWidth = $bottomWidth = $leftWidth = $topWidth;
634
        } elseif (!is_int($bottomWidth)) {
635
            $colour = $bottomWidth;
636
            $bottomWidth = $topWidth;
637
            $leftWidth = $rightWidth;
638
        } elseif (!is_int($leftWidth)) {
639
            $colour = $leftWidth;
640
            $leftWidth = $rightWidth;
641
        }
642
643
        $this->borderTopWidth = $topWidth;
644
        $this->borderRightWidth = $rightWidth;
645
        $this->borderBottomWidth = $bottomWidth;
646
        $this->borderLeftWidth = $leftWidth;
647
648
        if (is_string($colour)) {
649
            $this->setBorderColour($colour);
650
        } elseif ($colour !== null) {
651
            throw new \InvalidArgumentException('Invalid colour');
652
        }
653
654
        $this->calculateContentWidth();
655
        $this->generateBorderRows();
656
        $this->generatePaddingTopBottomRows();
657
658
        return $this;
659
    }
660
661
    public function setBorderTopWidth(int $width) : self
662
    {
663
        $this->borderTopWidth = $width;
664
665
        $this->generateBorderRows();
666
667
        return $this;
668
    }
669
670
    public function setBorderRightWidth(int $width) : self
671
    {
672
        $this->borderRightWidth = $width;
673
        $this->calculateContentWidth();
674
675
        $this->generatePaddingTopBottomRows();
676
677
        return $this;
678
    }
679
680
    public function setBorderBottomWidth(int $width) : self
681
    {
682
        $this->borderBottomWidth = $width;
683
684
        $this->generateBorderRows();
685
686
        return $this;
687
    }
688
689
    public function setBorderLeftWidth(int $width) : self
690
    {
691
        $this->borderLeftWidth = $width;
692
        $this->calculateContentWidth();
693
694
        $this->generatePaddingTopBottomRows();
695
696
        return $this;
697
    }
698
699
    public function setBorderColour(string $colour, $fallback = null) : self
700
    {
701
        $this->borderColour = ColourUtil::validateColour(
702
            $this->terminal,
703
            $colour,
704
            $fallback
705
        );
706
707
        $this->generateBorderRows();
708
        $this->generatePaddingTopBottomRows();
709
710
        return $this;
711
    }
712
713
    public function getBorderTopWidth() : int
714
    {
715
        return $this->borderTopWidth;
716
    }
717
718
    public function getBorderRightWidth() : int
719
    {
720
        return $this->borderRightWidth;
721
    }
722
723
    public function getBorderBottomWidth() : int
724
    {
725
        return $this->borderBottomWidth;
726
    }
727
728
    public function getBorderLeftWidth() : int
729
    {
730
        return $this->borderLeftWidth;
731
    }
732
733
    public function getBorderColour() : string
734
    {
735
        return $this->borderColour;
736
    }
737
738
    public function getBorderColourCode() : string
739
    {
740
        if (!ctype_digit($this->borderColour)) {
741
            $borderColourCode = self::$availableBackgroundColors[$this->borderColour];
742
        } else {
743
            $borderColourCode = sprintf("48;5;%s", $this->borderColour);
744
        }
745
746
        return sprintf("\033[%sm", $borderColourCode);
747
    }
748
}
749