Passed
Pull Request — master (#186)
by
unknown
03:03
created

MenuStyle::getUntoggledMarker()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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