Completed
Push — master ( 306131...5909ec )
by Aydin
06:33 queued 03:57
created

src/MenuStyle.php (1 issue)

Checks property assignments for possibly missing type casts

Bug Documentation Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Exception\InvalidInstantiationException;
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'];
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);
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);
0 ignored issues
show
Documentation Bug introduced by
The property $margin was declared of type integer, but floor(($this->terminal->...() - $this->width) / 2) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
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 = mb_substr($marker, 0, 1);
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 = mb_substr($marker, 0, 1);
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