Completed
Pull Request — master (#127)
by Aydin
01:53
created

CliMenuBuilder::setPadding()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 2
1
<?php
2
3
namespace PhpSchool\CliMenu;
4
5
use PhpSchool\CliMenu\Action\ExitAction;
6
use PhpSchool\CliMenu\Action\GoBackAction;
7
use PhpSchool\CliMenu\MenuItem\AsciiArtItem;
8
use PhpSchool\CliMenu\MenuItem\LineBreakItem;
9
use PhpSchool\CliMenu\MenuItem\MenuItemInterface;
10
use PhpSchool\CliMenu\MenuItem\MenuMenuItem;
11
use PhpSchool\CliMenu\MenuItem\SelectableItem;
12
use PhpSchool\CliMenu\MenuItem\SplitItem;
13
use PhpSchool\CliMenu\MenuItem\StaticItem;
14
use PhpSchool\CliMenu\Terminal\TerminalFactory;
15
use PhpSchool\CliMenu\Util\ColourUtil;
16
use PhpSchool\Terminal\Terminal;
17
use RuntimeException;
18
19
/**
20
 * @author Michael Woodward <[email protected]>
21
 * @author Aydin Hassan <[email protected]>
22
 */
23
class CliMenuBuilder implements Builder
24
{
25
    use BuilderUtils;
26
    
27
    /**
28
     * @var bool
29
     */
30
    private $isBuilt = false;
31
32
    /**
33
     * @var SplitItemBuilder[]
34
     */
35
    private $splitItemBuilders = [];
36
37
    /**
38
     * @var SplitItem[]
39
     */
40
    private $splitItems = [];
41
42
    /**
43
     * @var string
44
     */
45
    private $goBackButtonText = 'Go Back';
46
47
    /**
48
     * @var string
49
     */
50
    private $exitButtonText = 'Exit';
51
52
    /**
53
     * @var array
54
     */
55
    private $style;
56
57
    /**
58
     * @var Terminal
59
     */
60
    private $terminal;
61
62
    /**
63
     * @var string
64
     */
65
    private $menuTitle;
66
67
    /**
68
     * @var bool
69
     */
70
    private $disableDefaultItems = false;
71
72
    /**
73
     * @var bool
74
     */
75
    private $disabled = false;
76
77
    public function __construct(Builder $parent = null)
78
    {
79
        $this->parent   = $parent;
80
        $this->terminal = $this->parent !== null
81
            ? $this->parent->getTerminal()
82
            : TerminalFactory::fromSystem();
83
        $this->style = MenuStyle::getDefaultStyleValues();
84
    }
85
86
    public function setTitle(string $title) : self
87
    {
88
        $this->menuTitle = $title;
89
90
        return $this;
91
    }
92
93
    public function addMenuItem(MenuItemInterface $item) : self
94
    {
95
        $this->menuItems[] = $item;
96
97
        return $this;
98
    }
99
100
    public function addItems(array $items) : self
101
    {
102
        foreach ($items as $item) {
103
            $this->addItem(...$item);
0 ignored issues
show
Bug introduced by
The call to addItem() misses a required argument $itemCallable.

This check looks for function calls that miss required arguments.

Loading history...
104
        }
105
106
        return $this;
107
    }
108
109
    public function addAsciiArt(string $art, string $position = AsciiArtItem::POSITION_CENTER, string $alt = '') : self
110
    {
111
        $this->addMenuItem(new AsciiArtItem($art, $position, $alt));
112
113
        return $this;
114
    }
115
116
    /**
117
     * Add a split item
118
     */
119
    public function addSplitItem() : SplitItemBuilder
120
    {
121
        $this->menuItems[] = $id = uniqid('splititem-placeholder-', true);
122
        
123
        $this->splitItemBuilders[$id] = new SplitItemBuilder($this);
124
        return $this->splitItemBuilders[$id];
125
    }
126
127
    /**
128
     * Disable a submenu
129
     *
130
     * @throws \InvalidArgumentException
131
     */
132
    public function disableMenu() : self
133
    {
134
        if (!$this->parent) {
135
            throw new \InvalidArgumentException(
136
                'You can\'t disable the root menu'
137
            );
138
        }
139
140
        $this->disabled = true;
141
142
        return $this;
143
    }
144
145
    public function isMenuDisabled() : bool
146
    {
147
        return $this->disabled;
148
    }
149
150
    public function setGoBackButtonText(string $goBackButtonTest) : self
151
    {
152
        $this->goBackButtonText = $goBackButtonTest;
153
154
        return $this;
155
    }
156
157
    public function setExitButtonText(string $exitButtonText) : self
158
    {
159
        $this->exitButtonText = $exitButtonText;
160
161
        return $this;
162
    }
163
164
    public function setBackgroundColour(string $colour, string $fallback = null) : self
165
    {
166
        $this->style['bg'] = ColourUtil::validateColour(
167
            $this->terminal,
168
            $colour,
169
            $fallback
170
        );
171
172
        return $this;
173
    }
174
175 View Code Duplication
    public function setForegroundColour(string $colour, string $fallback = null) : self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
    {
177
        $this->style['fg'] = ColourUtil::validateColour(
178
            $this->terminal,
179
            $colour,
180
            $fallback
181
        );
182
183
        return $this;
184
    }
185
186
    public function setWidth(int $width) : self
187
    {
188
        $this->style['width'] = $width;
189
190
        return $this;
191
    }
192
193 View Code Duplication
    public function setPadding(int $topBottom, int $leftRight = null) : self
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
    {
195
        if ($leftRight === null) {
196
            $leftRight = $topBottom;
197
        }
198
199
        $this->setPaddingTopBottom($topBottom);
200
        $this->setPaddingLeftRight($leftRight);
201
202
        return $this;
203
    }
204
205
    public function setPaddingTopBottom(int $topBottom) : self
206
    {
207
        $this->style['paddingTopBottom'] = $topBottom;
208
209
        return $this;
210
    }
211
212
    public function setPaddingLeftRight(int $leftRight) : self
213
    {
214
        $this->style['paddingLeftRight'] = $leftRight;
215
216
        return $this;
217
    }
218
219
    public function setMarginAuto() : self
220
    {
221
        $this->style['marginAuto'] = true;
222
223
        return $this;
224
    }
225
226
    public function setMargin(int $margin) : self
227
    {
228
        $this->style['marginAuto'] = false;
229
        $this->style['margin'] = $margin;
230
231
        return $this;
232
    }
233
234
    public function setUnselectedMarker(string $marker) : self
235
    {
236
        $this->style['unselectedMarker'] = $marker;
237
238
        return $this;
239
    }
240
241
    public function setSelectedMarker(string $marker) : self
242
    {
243
        $this->style['selectedMarker'] = $marker;
244
245
        return $this;
246
    }
247
248
    public function setItemExtra(string $extra) : self
249
    {
250
        $this->style['itemExtra'] = $extra;
251
252
        return $this;
253
    }
254
255
    public function setTitleSeparator(string $separator) : self
256
    {
257
        $this->style['titleSeparator'] = $separator;
258
259
        return $this;
260
    }
261
262
    public function setBorder(
263
        int $topWidth,
264
        $rightWidth = null,
265
        $bottomWidth = null,
266
        $leftWidth = null,
267
        string $colour = null
268
    ) : self {
269 View Code Duplication
        if (!is_int($rightWidth)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
270
            $colour = $rightWidth;
271
            $rightWidth = $bottomWidth = $leftWidth = $topWidth;
272
        } elseif (!is_int($bottomWidth)) {
273
            $colour = $bottomWidth;
274
            $bottomWidth = $topWidth;
275
            $leftWidth = $rightWidth;
276
        } elseif (!is_int($leftWidth)) {
277
            $colour = $leftWidth;
278
            $leftWidth = $rightWidth;
279
        }
280
281
        $this->style['borderTopWidth'] = $topWidth;
282
        $this->style['borderRightWidth'] = $rightWidth;
283
        $this->style['borderBottomWidth'] = $bottomWidth;
284
        $this->style['borderLeftWidth'] = $leftWidth;
285
286
        if (is_string($colour)) {
287
            $this->style['borderColour'] = $colour;
288
        } elseif ($colour !== null) {
289
            throw new \InvalidArgumentException('Invalid colour');
290
        }
291
292
        return $this;
293
    }
294
295
    public function setBorderTopWidth(int $width) : self
296
    {
297
        $this->style['borderTopWidth'] = $width;
298
        
299
        return $this;
300
    }
301
302
    public function setBorderRightWidth(int $width) : self
303
    {
304
        $this->style['borderRightWidth'] = $width;
305
306
        return $this;
307
    }
308
309
    public function setBorderBottomWidth(int $width) : self
310
    {
311
        $this->style['borderBottomWidth'] = $width;
312
313
        return $this;
314
    }
315
316
    public function setBorderLeftWidth(int $width) : self
317
    {
318
        $this->style['borderLeftWidth'] = $width;
319
320
        return $this;
321
    }
322
323
    public function setBorderColour(string $colour, $fallback = null) : self
324
    {
325
        $this->style['borderColour'] = $colour;
326
        $this->style['borderColourFallback'] = $fallback;
327
328
        return $this;
329
    }
330
331
    public function setTerminal(Terminal $terminal) : self
332
    {
333
        $this->terminal = $terminal;
334
        return $this;
335
    }
336
337
    public function getTerminal() : Terminal
338
    {
339
        return $this->terminal;
340
    }
341
342
    private function getDefaultItems() : array
343
    {
344
        $actions = [];
345
        if ($this->parent) {
346
            $actions[] = new SelectableItem($this->goBackButtonText, new GoBackAction);
347
        }
348
349
        $actions[] = new SelectableItem($this->exitButtonText, new ExitAction);
350
        return $actions;
351
    }
352
353
    public function disableDefaultItems() : self
354
    {
355
        $this->disableDefaultItems = true;
356
357
        return $this;
358
    }
359
360
    private function itemsHaveExtra(array $items) : bool
361
    {
362
        return !empty(array_filter($items, function (MenuItemInterface $item) {
363
            return $item->showsItemExtra();
364
        }));
365
    }
366
367
    /**
368
     * Recursively drop back to the parents menu style
369
     * when the current menu has a parent and has no changes
370
     */
371
    public function getMenuStyle() : MenuStyle
372
    {
373
        if (null === $this->parent) {
374
            return $this->buildStyle();
375
        }
376
377
        if ($this->style !== MenuStyle::getDefaultStyleValues()) {
378
            return $this->buildStyle();
379
        }
380
381
        return $this->parent->getMenuStyle();
382
    }
383
384
    private function buildStyle() : MenuStyle
385
    {
386
        $style = (new MenuStyle($this->terminal))
387
            ->setFg($this->style['fg'])
388
            ->setBg($this->style['bg'])
389
            ->setWidth($this->style['width'])
390
            ->setPaddingTopBottom($this->style['paddingTopBottom'])
391
            ->setPaddingLeftRight($this->style['paddingLeftRight'])
392
            ->setSelectedMarker($this->style['selectedMarker'])
393
            ->setUnselectedMarker($this->style['unselectedMarker'])
394
            ->setItemExtra($this->style['itemExtra'])
395
            ->setDisplaysExtra($this->style['displaysExtra'])
396
            ->setTitleSeparator($this->style['titleSeparator'])
397
            ->setBorderTopWidth($this->style['borderTopWidth'])
398
            ->setBorderRightWidth($this->style['borderRightWidth'])
399
            ->setBorderBottomWidth($this->style['borderBottomWidth'])
400
            ->setBorderLeftWidth($this->style['borderLeftWidth'])
401
            ->setBorderColour($this->style['borderColour'], $this->style['borderColourFallback']);
402
403
        $this->style['marginAuto'] ? $style->setMarginAuto() : $style->setMargin($this->style['margin']);
404
405
        return $style;
406
    }
407
408
    /**
409
     * @throws RuntimeException
410
     */
411
    public function getSubMenu(string $id) : CliMenu
412
    {
413
        if (false === $this->isBuilt) {
414
            throw new RuntimeException(sprintf('Menu: "%s" cannot be retrieved until menu has been built', $id));
415
        }
416
417
        return $this->subMenus['submenu-placeholder-' . $id];
418
    }
419
    
420
    private function buildSplitItems(array $items) : array
421
    {
422
        return array_map(function ($item) {
423
            if (!is_string($item) || 0 !== strpos($item, 'splititem-placeholder-')) {
424
                return $item;
425
            }
426
427
            $splitItemBuilder        = $this->splitItemBuilders[$item];
428
            $this->splitItems[$item] = $splitItemBuilder->build();
429
430
            return $this->splitItems[$item];
431
        }, $items);
432
    }
433
434
    public function build() : CliMenu
435
    {
436
        $this->isBuilt = true;
437
438
        $mergedItems = $this->disableDefaultItems
439
            ? $this->menuItems
440
            : array_merge($this->menuItems, $this->getDefaultItems());
441
442
        
443
        $menuItems = $this->buildSplitItems($mergedItems);
444
        $menuItems = $this->buildSubMenus($menuItems);
445
446
        $this->style['displaysExtra'] = $this->itemsHaveExtra($menuItems);
447
448
        $menu = new CliMenu(
449
            $this->menuTitle,
450
            $menuItems,
451
            $this->terminal,
452
            $this->getMenuStyle()
453
        );
454
455
        foreach ($this->subMenus as $subMenu) {
456
            $subMenu->setParent($menu);
457
        }
458
        
459
        foreach ($this->splitItemBuilders as $splitItemBuilder) {
460
            $splitItemBuilder->setSubMenuParents($menu);
461
        }
462
463
        return $menu;
464
    }
465
}
466