Completed
Pull Request — master (#203)
by
unknown
02:10
created

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