Completed
Push — master ( 499201...4a0bb5 )
by Aydin
26s queued 12s
created

MenuStyle::getUnselectedMarker()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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