-
-
Notifications
You must be signed in to change notification settings - Fork 41
/
Copy pathItemInput.h
306 lines (300 loc) · 9.42 KB
/
ItemInput.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#ifndef ItemInput_H
#define ItemInput_H
#include "LcdMenu.h"
#include "MenuItem.h"
#include <utils/utils.h>
/**
* @brief Item that allows user to input string information.
*
* ```
* ┌────────────────────────────┐
* │ > T E X T : V A L U E │
* └────────────────────────────┘
* ```
*
* Additionally to `text` this item has string `value`.
* Has internal `edit` state.
* Value area is scrollable, see `view`.
*/
class ItemInput : public MenuItem {
protected:
/**
* @brief String value of item.
*/
char* value;
/**
* @brief The index of first visible character.
*
* ```
* visible area
* ┌───────────────┐
* X X X X│X X X X █ X X X│X X
* └───────────────┘
* view--^
* ```
*
* Is moved when `cursor` crosses the borders of visible area.
* When length of `value` < `viewSize` this index should be 0.
*/
uint8_t view = 0;
/**
* @brief Current index of the char to be edited.
*
* ```
* visible area
* ┌───────────────┐
* X X X X│X X X X █ X X X│X X
* └───────────────┘
* cursor--^
* ```
*
* When `left` or `right` then this position will be moved over the string accordingly.
* When `type` then new character will be appended on this position.
* When `backspace` then one character before will be removed.
* Always in range [`view`, `view` + `viewSize` - 1].
*/
uint8_t cursor;
/**
* The call back that will be executed when edit will be finished.
* First parameter will be a `value` string.
*/
fptrStr callback;
public:
/**
* Construct a new ItemInput object with an initial value.
*
* @param text The text to display for the item.
* @param value The initial value for the input.
* @param callback A reference to the callback function to be invoked when
* the input is submitted.
*/
ItemInput(const char* text, char* value, fptrStr callback)
: MenuItem(text), value(value), callback(callback) {}
/**
* Construct a new ItemInput object with no initial value.
*
* @param text The text to display for the item.
* @param callback A reference to the callback function to be invoked when
* the input is submitted.
*/
ItemInput(const char* text, fptrStr callback)
: ItemInput(text, (char*)"", callback) {}
/**
* Get the current input value for this item.
*
* @return The current input value as a string.
*/
char* getValue() { return value; }
/**
* Set the input value for this item.
*
* @note You need to call `LcdMenu::refresh` after this method to see the changes.
*
* @param value The new input value.
*
* @return true if the value was changed, false otherwise.
*/
bool setValue(char* value) {
if (this->value != value) {
this->value = value;
LOG(F("ItemInput::setValue"), value);
return true;
}
return false;
}
/**
* Get the callback function for this item.
*
* @return The function pointer to the callback function.
*/
fptrStr getCallbackStr() { return callback; }
protected:
void draw(MenuRenderer* renderer) override {
const uint8_t viewSize = getViewSize(renderer);
char* vbuf = new char[viewSize + 1];
substring(value, view, viewSize, vbuf);
vbuf[viewSize] = '\0';
renderer->drawItem(text, vbuf);
delete[] vbuf;
}
bool process(LcdMenu* menu, const unsigned char command) override {
MenuRenderer* renderer = menu->getRenderer();
if (renderer->isInEditMode()) {
if (isprint(command)) {
typeChar(renderer, command);
return true;
}
switch (command) {
case ENTER:
enter(renderer);
return true;
case BACK:
back(renderer);
return true;
case UP:
return true;
case DOWN:
return true;
case LEFT:
left(renderer);
return true;
case RIGHT:
right(renderer);
return true;
case BACKSPACE:
backspace(renderer);
return true;
case CLEAR:
clear(renderer);
return true;
default:
return false;
}
} else {
switch (command) {
case ENTER:
enter(renderer);
return true;
default:
return false;
}
}
}
void enter(MenuRenderer* renderer) {
// Move cursor to the latest index
uint8_t length = strlen(value);
cursor = length;
// Move view if needed
uint8_t viewSize = getViewSize(renderer);
if (cursor > viewSize) {
view = length - (viewSize - 1);
}
// Redraw
renderer->setEditMode(true);
draw(renderer);
renderer->drawBlinker();
// Log
LOG(F("ItemInput::enterEditMode"), value);
};
void back(MenuRenderer* renderer) {
renderer->clearBlinker();
renderer->setEditMode(false);
// Move view to 0 and redraw before exit
cursor = 0;
view = 0;
draw(renderer);
if (callback != NULL) {
callback(value);
}
// Log
LOG(F("ItemInput::exitEditMode"), value);
};
void left(MenuRenderer* renderer) {
if (cursor == 0) {
return;
}
cursor--;
uint8_t cursorCol = renderer->getCursorCol();
if (cursor <= view - 1) {
view--;
draw(renderer);
} else {
cursorCol--;
}
renderer->moveCursor(cursorCol, renderer->getCursorRow());
renderer->drawBlinker();
// Log
LOG(F("ItemInput::left"), value);
};
/**
* @brief Moves the cursor to the right within the input value.
*/
void right(MenuRenderer* renderer) {
if (cursor == strlen(value)) {
return;
}
cursor++;
uint8_t viewSize = getViewSize(renderer);
uint8_t cursorCol = renderer->getCursorCol();
if (cursor > (view + viewSize - 1)) {
view++;
draw(renderer);
} else {
cursorCol++;
}
renderer->moveCursor(cursorCol, renderer->getCursorRow());
renderer->drawBlinker();
// Log
LOG(F("ItemInput::right"), value);
}
/**
* @brief Handles the backspace action for the input field.
*/
void backspace(MenuRenderer* renderer) {
if (strlen(value) == 0 || cursor == 0) {
return;
}
remove(value, cursor - 1, 1);
cursor--;
uint8_t cursorCol = renderer->getCursorCol();
if (view > 0) {
view--;
} else {
cursorCol--;
}
draw(renderer);
renderer->moveCursor(cursorCol, renderer->getCursorRow());
renderer->drawBlinker();
// Log
LOG(F("ItemInput::backspace"), value);
}
/**
* @brief Types a character into the current input value at the cursor position.
*
* This function inserts a character into the `value` string at the current cursor position.
* If the cursor is within the current length of the string, the string is split and the character
* is inserted in between. If the cursor is at the end of the string, the character is appended.
* The cursor is then incremented, and the view is adjusted if necessary.
* Finally, the renderer is updated and the blinker position is reset.
*
* @param renderer Pointer to the MenuRenderer object used for rendering.
* @param character The character to be typed into the input value.
*/
void typeChar(MenuRenderer* renderer, const unsigned char character) {
uint8_t length = strlen(value);
char* buf = new char[length + 2];
if (cursor < length) {
char start[length];
char end[length];
substring(value, 0, cursor, start);
substring(value, cursor, length - cursor, end);
concat(start, character, end, buf);
} else {
concat(value, character, buf);
}
delete[] value;
value = buf;
cursor++;
uint8_t viewSize = getViewSize(renderer);
if (cursor > (view + viewSize - 1)) {
view++;
}
draw(renderer);
renderer->drawBlinker();
// Log
LOG(F("ItemInput::typeChar"), character);
}
/**
* @brief Clear the value of the input field
*/
void clear(MenuRenderer* renderer) {
value[0] = '\0';
view = 0;
draw(renderer);
renderer->drawBlinker();
// Log
LOG(F("ItemInput::clear"), value);
}
};
#define ITEM_INPUT(...) (new ItemInput(__VA_ARGS__))
#endif // ITEM_INPUT_H