Passed
Pull Request — master (#203)
by
unknown
02:08
created

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