Completed
Pull Request — master (#221)
by Aydin
01:59
created

MenuStyle::setBorder()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 1 Features 0
Metric Value
cc 5
eloc 20
c 9
b 1
f 0
nc 8
nop 5
dl 0
loc 33
rs 9.2888
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() : string
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() : string
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
    /**
424
     * @return array
425
     */
426
    public function getPaddingTopBottomRows() : array
427
    {
428
        return $this->paddingTopBottomRows;
429
    }
430
431
    public function setPadding(int $topBottom, int $leftRight = null) : self
432
    {
433
        if ($leftRight === null) {
434
            $leftRight = $topBottom;
435
        }
436
437
        $this->setPaddingTopBottom($topBottom);
438
        $this->setPaddingLeftRight($leftRight);
439
440
        $this->calculateContentWidth();
441
        $this->generatePaddingTopBottomRows();
442
443
        return $this;
444
    }
445
446
    public function setPaddingTopBottom(int $topBottom) : self
447
    {
448
        Assertion::greaterOrEqualThan($topBottom, 0);
449
        $this->paddingTopBottom = $topBottom;
450
451
        $this->generatePaddingTopBottomRows();
452
453
        return $this;
454
    }
455
456
    public function setPaddingLeftRight(int $leftRight) : self
457
    {
458
        Assertion::greaterOrEqualThan($leftRight, 0);
459
        $this->paddingLeftRight = $leftRight;
460
461
        $this->calculateContentWidth();
462
        $this->generatePaddingTopBottomRows();
463
464
        return $this;
465
    }
466
467
    public function getMargin() : int
468
    {
469
        return $this->margin;
470
    }
471
472
    public function setMarginAuto() : self
473
    {
474
        $this->marginAuto = true;
475
        $this->margin = (int) floor(($this->terminal->getWidth() - $this->width) / 2);
476
477
        $this->generateBorderRows();
478
        $this->generatePaddingTopBottomRows();
479
480
        return $this;
481
    }
482
483
    public function setMargin(int $margin) : self
484
    {
485
        Assertion::greaterOrEqualThan($margin, 0);
486
487
        $this->marginAuto = false;
488
        $this->margin = $margin;
489
490
        $this->generateBorderRows();
491
        $this->generatePaddingTopBottomRows();
492
493
        return $this;
494
    }
495
496
    public function getContentWidth() : int
497
    {
498
        return $this->contentWidth;
499
    }
500
501
    /**
502
     * Get padding for right had side of content
503
     */
504
    public function getRightHandPadding(int $contentLength) : int
505
    {
506
        $rightPadding = $this->getContentWidth() - $contentLength + $this->getPaddingLeftRight();
507
508
        if ($rightPadding < 0) {
509
            $rightPadding = 0;
510
        }
511
512
        return $rightPadding;
513
    }
514
515
    public function setItemExtra(string $itemExtra) : self
516
    {
517
        $this->itemExtra = $itemExtra;
518
519
        return $this;
520
    }
521
522
    public function getItemExtra() : string
523
    {
524
        return $this->itemExtra;
525
    }
526
527
    public function getDisplaysExtra() : bool
528
    {
529
        return $this->displaysExtra;
530
    }
531
532
    public function setDisplaysExtra(bool $displaysExtra) : self
533
    {
534
        $this->displaysExtra = $displaysExtra;
535
536
        return $this;
537
    }
538
539
    public function getTitleSeparator() : string
540
    {
541
        return $this->titleSeparator;
542
    }
543
544
    public function setTitleSeparator(string $actionSeparator) : self
545
    {
546
        $this->titleSeparator = $actionSeparator;
547
548
        return $this;
549
    }
550
551
    private function generateBorderRows() : void
552
    {
553
        $borderRow = sprintf(
554
            "%s%s%s%s\n",
555
            str_repeat(' ', $this->margin),
556
            $this->getBorderColourCode(),
557
            str_repeat(' ', $this->width),
558
            $this->coloursResetCode
559
        );
560
561
        $this->borderTopRows = array_fill(0, $this->borderTopWidth, $borderRow);
562
        $this->borderBottomRows = array_fill(0, $this->borderBottomWidth, $borderRow);
563
    }
564
565
    /**
566
     * @return array
567
     */
568
    public function getBorderTopRows() : array
569
    {
570
        return $this->borderTopRows;
571
    }
572
573
    /**
574
     * @return array
575
     */
576
    public function getBorderBottomRows() : array
577
    {
578
        return $this->borderBottomRows;
579
    }
580
581
    /**
582
     * @param int|string|null $rightWidth
583
     * @param int|string|null $bottomWidth
584
     * @param int|string|null $leftWidth
585
     *
586
     * Shorthand function to set all borders values at once
587
     */
588
    public function setBorder(
589
        int $topWidth,
590
        $rightWidth = null,
591
        $bottomWidth = null,
592
        $leftWidth = null,
593
        string $colour = null
594
    ) : self {
595
        if (!is_int($rightWidth)) {
596
            $colour = $rightWidth;
597
            $rightWidth = $bottomWidth = $leftWidth = $topWidth;
598
        } elseif (!is_int($bottomWidth)) {
599
            $colour = $bottomWidth;
600
            $bottomWidth = $topWidth;
601
            $leftWidth = $rightWidth;
602
        } elseif (!is_int($leftWidth)) {
603
            $colour = $leftWidth;
604
            $leftWidth = $rightWidth;
605
        }
606
607
        $this->borderTopWidth = $topWidth;
608
        $this->borderRightWidth = $rightWidth;
609
        $this->borderBottomWidth = $bottomWidth;
610
        $this->borderLeftWidth = $leftWidth;
611
612
        if (is_string($colour)) {
613
            $this->setBorderColour($colour);
614
        }
615
616
        $this->calculateContentWidth();
617
        $this->generateBorderRows();
618
        $this->generatePaddingTopBottomRows();
619
620
        return $this;
621
    }
622
623
    public function setBorderTopWidth(int $width) : self
624
    {
625
        $this->borderTopWidth = $width;
626
627
        $this->generateBorderRows();
628
629
        return $this;
630
    }
631
632
    public function setBorderRightWidth(int $width) : self
633
    {
634
        $this->borderRightWidth = $width;
635
        $this->calculateContentWidth();
636
637
        $this->generatePaddingTopBottomRows();
638
639
        return $this;
640
    }
641
642
    public function setBorderBottomWidth(int $width) : self
643
    {
644
        $this->borderBottomWidth = $width;
645
646
        $this->generateBorderRows();
647
648
        return $this;
649
    }
650
651
    public function setBorderLeftWidth(int $width) : self
652
    {
653
        $this->borderLeftWidth = $width;
654
        $this->calculateContentWidth();
655
656
        $this->generatePaddingTopBottomRows();
657
658
        return $this;
659
    }
660
661
    public function setBorderColour(string $colour, string $fallback = null) : self
662
    {
663
        $this->borderColour = ColourUtil::validateColour(
664
            $this->terminal,
665
            $colour,
666
            $fallback
667
        );
668
669
        $this->generateBorderRows();
670
        $this->generatePaddingTopBottomRows();
671
672
        return $this;
673
    }
674
675
    public function getBorderTopWidth() : int
676
    {
677
        return $this->borderTopWidth;
678
    }
679
680
    public function getBorderRightWidth() : int
681
    {
682
        return $this->borderRightWidth;
683
    }
684
685
    public function getBorderBottomWidth() : int
686
    {
687
        return $this->borderBottomWidth;
688
    }
689
690
    public function getBorderLeftWidth() : int
691
    {
692
        return $this->borderLeftWidth;
693
    }
694
695
    public function getBorderColour() : string
696
    {
697
        return $this->borderColour;
698
    }
699
700
    public function getBorderColourCode() : string
701
    {
702
        if (!ctype_digit($this->borderColour)) {
703
            $borderColourCode = self::$availableBackgroundColors[$this->borderColour];
704
        } else {
705
            $borderColourCode = sprintf("48;5;%s", $this->borderColour);
706
        }
707
708
        return sprintf("\033[%sm", $borderColourCode);
709
    }
710
}
711