Completed
Pull Request — master (#81)
by Aydin
01:47
created

InputIO::drawTitle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 2
1
<?php
2
3
namespace PhpSchool\CliMenu\Input;
4
5
use PhpSchool\CliMenu\CliMenu;
6
use PhpSchool\CliMenu\Util\StringUtil;
7
use PhpSchool\Terminal\InputCharacter;
8
use PhpSchool\Terminal\NonCanonicalReader;
9
use PhpSchool\Terminal\Terminal;
10
11
/**
12
 * @author Aydin Hassan <[email protected]>
13
 */
14
class InputIO
15
{
16
    /**
17
     * @var CliMenu
18
     */
19
    private $parentMenu;
20
21
    /**
22
     * @var Terminal
23
     */
24
    private $terminal;
25
26
    /**
27
     * @var callable[][]
28
     */
29
    private $callbacks = [];
30
31
    public function __construct(CliMenu $parentMenu, Terminal $terminal)
32
    {
33
        $this->terminal     = $terminal;
34
        $this->parentMenu   = $parentMenu;
35
    }
36
37
    public function collect(Input $input) : InputResult
38
    {
39
        $this->drawInput($input, $input->getPlaceholderText());
40
41
        $inputValue = $input->getPlaceholderText();
42
        $havePlaceHolderValue = !empty($inputValue);
43
44
        $reader = new NonCanonicalReader($this->terminal);
45
46
        while ($char = $reader->readCharacter()) {
47
            if ($char->isNotControl()) {
48
                if ($havePlaceHolderValue) {
49
                    $inputValue = $char->get();
50
                    $havePlaceHolderValue = false;
51
                } else {
52
                    $inputValue .= $char->get();
53
                }
54
55
                $this->parentMenu->redraw();
56
                $this->drawInput($input, $inputValue);
57
                continue;
58
            }
59
60
            switch ($char->getControl()) {
61
                case InputCharacter::ENTER:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
62
                    if ($input->validate($inputValue)) {
63
                        $this->parentMenu->redraw();
64
                        return new InputResult($inputValue);
65
                    } else {
66
                        $this->drawInputWithError($input, $inputValue);
67
                        continue 2;
68
                    }
69
70
                case InputCharacter::BACKSPACE:
71
                    $inputValue = substr($inputValue, 0, -1);
72
                    $this->parentMenu->redraw();
73
                    $this->drawInput($input, $inputValue);
74
                    continue 2;
75
            }
76
77
            if (!empty($this->callbacks[$char->getControl()])) {
78
                foreach ($this->callbacks[$char->getControl()] as $callback) {
79
                    $inputValue = $callback($inputValue);
80
                    $this->drawInput($input, $inputValue);
81
                }
82
            }
83
        }
84
    }
85
86
    public function registerControlCallback(string $control, callable $callback) : void
87
    {
88
        if (!isset($this->callbacks[$control])) {
89
            $this->callbacks[$control] = [];
90
        }
91
92
        $this->callbacks[$control][] = $callback;
93
    }
94
95
    private function getInputWidth(array $lines)
96
    {
97
        return max(
98
            array_map(
99
                function (string $line) {
100
                    return mb_strlen($line);
101
                },
102
                $lines
103
            )
104
        );
105
    }
106
107
    private function calculateYPosition() : int
108
    {
109
        $lines = 5; //1. empty 2. prompt text 3. empty 4. input 5. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
110
111
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
112
    }
113
114
    private function calculateYPositionWithError() : int
115
    {
116
        $lines = 7; //1. empty 2. prompt text 3. empty 4. input 5. empty 6. error 7. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
117
118
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
119
    }
120
121
    private function calculateXPosition(Input $input, string $userInput) : int
122
    {
123
        $width = $this->getInputWidth(
124
            [
125
                $input->getPromptText(),
126
                $input->getValidationFailedText(),
127
                $userInput
128
            ]
129
        );
130
131
        $parentStyle     = $this->parentMenu->getStyle();
132
        $halfWidth       = ($width + ($input->getStyle()->getPadding() * 2)) / 2;
133
        $parentHalfWidth = ceil($parentStyle->getWidth() / 2);
134
135
        return $parentHalfWidth - $halfWidth;
136
    }
137
138
    private function drawLine(Input $input, string $userInput, string $text) : void
139
    {
140
        $this->terminal->moveCursorToColumn($this->calculateXPosition($input, $userInput));
141
142
        $line = sprintf(
143
            "%s%s%s%s%s\n",
144
            $input->getStyle()->getUnselectedSetCode(),
145
            str_repeat(' ', $input->getStyle()->getPadding()),
146
            $text,
147
            str_repeat(' ', $input->getStyle()->getPadding()),
148
            $input->getStyle()->getUnselectedUnsetCode()
149
        );
150
151
        $this->terminal->write($line);
152
    }
153
154
    private function drawCenteredLine(Input $input, string $userInput, string $text) : void
155
    {
156
        $width = $this->getInputWidth(
157
            [
158
                $input->getPromptText(),
159
                $input->getValidationFailedText(),
160
                $userInput
161
            ]
162
        );
163
164
        $textLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($text));
165
        $leftFill   = ($width / 2) - ($textLength / 2);
166
        $rightFill  = ceil($width - $leftFill - $textLength);
167
168
        $this->drawLine(
169
            $input,
170
            $userInput,
171
            sprintf(
172
                '%s%s%s',
173
                str_repeat(' ', $leftFill),
174
                $text,
175
                str_repeat(' ', $rightFill)
176
            )
177
        );
178
    }
179
180
    private function drawEmptyLine(Input $input, string $userInput) : void
181
    {
182
        $width = $this->getInputWidth(
183
            [
184
                $input->getPromptText(),
185
                $input->getValidationFailedText(),
186
                $userInput
187
            ]
188
        );
189
190
        $this->drawLine(
191
            $input,
192
            $userInput,
193
            str_repeat(' ', $width)
194
        );
195
    }
196
197
    private function drawInput(Input $input, string $userInput) : void
198
    {
199
        $this->terminal->moveCursorToRow($this->calculateYPosition());
200
201
        $this->drawEmptyLine($input, $userInput);
202
        $this->drawTitle($input, $userInput);
203
        $this->drawEmptyLine($input, $userInput);
204
        $this->drawInputField($input, $input->filter($userInput));
205
        $this->drawEmptyLine($input, $userInput);
206
    }
207
208
    private function drawInputWithError(Input $input, string $userInput) : void
209
    {
210
        $this->terminal->moveCursorToRow($this->calculateYPositionWithError());
211
212
        $this->drawEmptyLine($input, $userInput);
213
        $this->drawTitle($input, $userInput);
214
        $this->drawEmptyLine($input, $userInput);
215
        $this->drawInputField($input, $input->filter($userInput));
216
        $this->drawEmptyLine($input, $userInput);
217
        $this->drawCenteredLine(
218
            $input,
219
            $userInput,
220
            $input->getValidationFailedText()
221
        );
222
        $this->drawEmptyLine($input, $userInput);
223
    }
224
225
    private function drawTitle(Input $input, string $userInput) : void
226
    {
227
228
        $this->drawCenteredLine(
229
            $input,
230
            $userInput,
231
            $input->getPromptText()
232
        );
233
    }
234
235
    private function drawInputField(Input $input, string $userInput) : void
236
    {
237
        $this->drawCenteredLine(
238
            $input,
239
            $userInput,
240
            sprintf(
241
                '%s%s%s%s%s',
242
                $input->getStyle()->getUnselectedUnsetCode(),
243
                $input->getStyle()->getSelectedSetCode(),
244
                $userInput,
245
                $input->getStyle()->getSelectedUnsetCode(),
246
                $input->getStyle()->getUnselectedSetCode()
247
            )
248
        );
249
    }
250
}
251