1 | // This file is part of SmallBASIC |
2 | // |
3 | // Copyright(C) 2001-2014 Chris Warren-Smith. |
4 | // |
5 | // This program is distributed under the terms of the GPL v2.0 or later |
6 | // Download the GNU Public License (GPL) from www.gnu.org |
7 | // |
8 | |
9 | #include <string.h> |
10 | #include <ctype.h> |
11 | #include <stdarg.h> |
12 | #include <stdio.h> |
13 | #include <stdlib.h> |
14 | #include <wchar.h> |
15 | #include <math.h> |
16 | |
17 | #include "ui/ansiwidget.h" |
18 | #include "ui/inputs.h" |
19 | #include "ui/utils.h" |
20 | |
21 | /* class AnsiWidget |
22 | |
23 | Displays ANSI escape codes. |
24 | |
25 | Escape sequences start with the characters ESC (ASCII 27d / 1Bh / 033o ) |
26 | and [ (left bracket). This sequence is called CSI for |
27 | "Control Sequence Introducer". |
28 | |
29 | For more information about ANSI code see: |
30 | http://en.wikipedia.org/wiki/ANSI_escape_code |
31 | http://www.uv.tietgen.dk/staff/mlha/PC/Soft/Prog/BAS/VB/Function.html |
32 | http://bjh21.me.uk/all-escapes/all-escapes.txt |
33 | |
34 | Supported control codes: |
35 | \t tab (20 px) |
36 | \a beep |
37 | \r return |
38 | \n next line |
39 | \xC clear screen (new page) |
40 | \e[K clear to end of line |
41 | \e[0m reset all attributes to their defaults |
42 | \e[1m set bold on |
43 | \e[4m set underline on |
44 | \e[7m reverse video |
45 | \e[21m set bold off |
46 | \e[24m set underline off |
47 | \e[27m set reverse off |
48 | */ |
49 | |
50 | #define BUTTON_PADDING 10 |
51 | #define OVER_SCROLL 100 |
52 | #define H_SCROLL_SIZE 10 |
53 | #define SWIPE_MAX_TIMER 3000 |
54 | #define SWIPE_DELAY_STEP 200 |
55 | #define SWIPE_MAX_DURATION 300 |
56 | #define SWIPE_MIN_DISTANCE 60 |
57 | #define FONT_FACTOR 30 |
58 | |
59 | extern "C" void dev_beep(void); |
60 | |
61 | AnsiWidget::AnsiWidget(int width, int height) : |
62 | _back(nullptr), |
63 | _front(nullptr), |
64 | _focus(nullptr), |
65 | _activeButton(nullptr), |
66 | _hoverInput(nullptr), |
67 | _width(width), |
68 | _height(height), |
69 | _xTouch(-1), |
70 | _yTouch(-1), |
71 | _xMove(-1), |
72 | _yMove(-1), |
73 | _touchTime(0), |
74 | _swipeExit(false), |
75 | _autoflush(true) { |
76 | for (int i = 0; i < MAX_SCREENS; i++) { |
77 | _screens[i] = nullptr; |
78 | } |
79 | _fontSize = MIN(width, height) / FONT_FACTOR; |
80 | trace("width: %d height: %d fontSize:%d" , _width, height, _fontSize); |
81 | } |
82 | |
83 | void AnsiWidget::clearScreen() { |
84 | _hoverInput = nullptr; |
85 | _activeButton = nullptr; |
86 | _back->clear(); |
87 | } |
88 | |
89 | bool AnsiWidget::construct() { |
90 | bool result = false; |
91 | _back = new GraphicScreen(_width, _height, _fontSize); |
92 | if (_back && _back->construct()) { |
93 | _screens[0] = _front = _back; |
94 | clearScreen(); |
95 | result = true; |
96 | } |
97 | return result; |
98 | } |
99 | |
100 | // widget clean up |
101 | AnsiWidget::~AnsiWidget() { |
102 | logEntered(); |
103 | for (int i = 0; i < MAX_SCREENS; i++) { |
104 | delete _screens[i]; |
105 | } |
106 | } |
107 | |
108 | Screen *AnsiWidget::createScreen(int screenId) { |
109 | Screen *result = _screens[screenId]; |
110 | if (result == nullptr) { |
111 | if (screenId == TEXT_SCREEN || screenId == MENU_SCREEN) { |
112 | result = new TextScreen(_width, _height, _fontSize); |
113 | } else { |
114 | result = new GraphicScreen(_width, _height, _fontSize); |
115 | } |
116 | if (result && result->construct()) { |
117 | _screens[screenId] = result; |
118 | result->drawInto(); |
119 | result->clear(); |
120 | } else { |
121 | trace("Failed to create screen %d" , screenId); |
122 | } |
123 | } |
124 | return result; |
125 | } |
126 | |
127 | void AnsiWidget::addImage(ImageDisplay &image) { |
128 | _back->addImage(image); |
129 | flush(false, false, MAX_PENDING_GRAPHICS); |
130 | } |
131 | |
132 | void AnsiWidget::drawArc(int xc, int yc, double r, double start, double end, double aspect) { |
133 | _back->drawArc(xc, yc, r, start, end, aspect); |
134 | flush(false, false, MAX_PENDING_GRAPHICS); |
135 | } |
136 | |
137 | void AnsiWidget::drawEllipse(int xc, int yc, int rx, int ry, int fill) { |
138 | _back->drawEllipse(xc, yc, rx, ry, fill); |
139 | flush(false, false, MAX_PENDING_GRAPHICS); |
140 | } |
141 | |
142 | void AnsiWidget::drawImage(ImageDisplay &image) { |
143 | _back->drawImage(image); |
144 | flush(false, false, MAX_PENDING_GRAPHICS); |
145 | } |
146 | |
147 | // draw a line onto the offscreen buffer |
148 | void AnsiWidget::drawLine(int x1, int y1, int x2, int y2) { |
149 | _back->drawLine(x1, y1, x2, y2); |
150 | flush(false, false, MAX_PENDING_GRAPHICS); |
151 | } |
152 | |
153 | // draw a rectangle onto the offscreen buffer |
154 | void AnsiWidget::drawRect(int x1, int y1, int x2, int y2) { |
155 | _back->drawRect(x1, y1, x2, y2); |
156 | flush(false, false, MAX_PENDING_GRAPHICS); |
157 | } |
158 | |
159 | // draw a filled rectangle onto the offscreen buffer |
160 | void AnsiWidget::drawRectFilled(int x1, int y1, int x2, int y2) { |
161 | _back->drawRectFilled(x1, y1, x2, y2); |
162 | flush(false, false, MAX_PENDING_GRAPHICS); |
163 | } |
164 | |
165 | // display any pending images changed |
166 | void AnsiWidget::flush(bool force, bool vscroll, int maxPending) { |
167 | if (_front != nullptr && _autoflush) { |
168 | bool update = false; |
169 | if (force) { |
170 | update = _front->_dirty; |
171 | } else if (_front->_dirty) { |
172 | update = (maGetMilliSecondCount() - _front->_dirty >= maxPending); |
173 | } |
174 | if (update) { |
175 | _front->drawBase(vscroll); |
176 | } |
177 | } |
178 | } |
179 | |
180 | int AnsiWidget::getScreenId(bool back) { |
181 | int result = 0; |
182 | for (int i = 0; i < MAX_SCREENS; i++) { |
183 | if (_screens[i] == (back ? _back : _front)) { |
184 | result = i; |
185 | break; |
186 | } |
187 | } |
188 | return result; |
189 | } |
190 | |
191 | // prints the contents of the given string onto the backbuffer |
192 | void AnsiWidget::print(const char *str) { |
193 | int len = (str == nullptr ? 0 : strlen(str)); |
194 | if (len) { |
195 | _back->drawInto(); |
196 | |
197 | int lineHeight = textHeight(); |
198 | const char *p = (char *)str; |
199 | |
200 | while (*p) { |
201 | switch (*p) { |
202 | case '\a': // beep |
203 | dev_beep(); |
204 | break; |
205 | case '\t': |
206 | _back->calcTab(); |
207 | break; |
208 | case '\003': // end of text |
209 | flush(true); |
210 | break; |
211 | case '\xC': // form feed |
212 | clearScreen(); |
213 | break; |
214 | case '\033': // ESC ctrl chars |
215 | handleEscape(p, lineHeight); |
216 | break; |
217 | case '\034': |
218 | // file separator |
219 | break; |
220 | case '\n': // new line |
221 | _back->newLine(lineHeight); |
222 | break; |
223 | case '\r': // return |
224 | _back->_curX = INITXY; // erasing the line will clear any previous text |
225 | break; |
226 | default: |
227 | p += _back->print(p, lineHeight) - 1; // allow for p++ |
228 | break; |
229 | }; |
230 | |
231 | if (*p == '\0') { |
232 | break; |
233 | } |
234 | p++; |
235 | } |
236 | |
237 | // cleanup |
238 | flush(false); |
239 | } |
240 | } |
241 | |
242 | // redraws and flushes the front screen |
243 | void AnsiWidget::redraw() { |
244 | _front->drawInto(); |
245 | flushNow(); |
246 | } |
247 | |
248 | // reinit for new program run |
249 | void AnsiWidget::reset() { |
250 | _back = _front = _screens[USER_SCREEN1]; |
251 | _back->reset(_fontSize); |
252 | _back->clear(); |
253 | |
254 | // reset user screens |
255 | delete _screens[USER_SCREEN2]; |
256 | delete _screens[TEXT_SCREEN]; |
257 | _screens[USER_SCREEN2] = nullptr; |
258 | _screens[TEXT_SCREEN] = nullptr; |
259 | |
260 | maFontSetCurrent(_back->_font); |
261 | redraw(); |
262 | } |
263 | |
264 | // update the widget to new dimensions |
265 | void AnsiWidget::resize(int newWidth, int newHeight) { |
266 | int lineHeight = textHeight(); |
267 | for (int i = 0; i < MAX_SCREENS; i++) { |
268 | Screen *screen = _screens[i]; |
269 | if (screen) { |
270 | screen->resize(newWidth, newHeight, _width, _height, lineHeight); |
271 | if (screen == _front) { |
272 | screen->drawBase(false); |
273 | } |
274 | } |
275 | } |
276 | _width = newWidth; |
277 | _height = newHeight; |
278 | } |
279 | |
280 | void AnsiWidget::removeHover() { |
281 | if (_hoverInput) { |
282 | int dx = _front->_x; |
283 | int dy = _front->_y - _front->_scrollY; |
284 | _hoverInput->drawHover(dx, dy, false); |
285 | _hoverInput = nullptr; |
286 | } |
287 | } |
288 | |
289 | void AnsiWidget::removeInputs() { |
290 | List_each(FormInput *, it, _back->_inputs) { |
291 | FormInput *widget = *it; |
292 | if (widget == _activeButton) { |
293 | _activeButton = nullptr; |
294 | } else if (widget == _hoverInput) { |
295 | _hoverInput = nullptr; |
296 | } |
297 | } |
298 | _back->removeInputs(); |
299 | } |
300 | |
301 | bool AnsiWidget::scroll(bool up, bool page) { |
302 | int h = page ? _front->_height - _front->_charHeight : _front->_charHeight; |
303 | int vscroll = _front->_scrollY + (up ? - h : h); |
304 | int maxVScroll = (_front->_curY - _front->_height) + (2 * _fontSize); |
305 | bool result; |
306 | |
307 | if (page) { |
308 | if (vscroll < 0 && _front->_scrollY > 0) { |
309 | vscroll = 0; |
310 | } else if (vscroll >= maxVScroll) { |
311 | vscroll = maxVScroll - _front->_charHeight; |
312 | } |
313 | } |
314 | |
315 | if (vscroll >= 0 && vscroll < maxVScroll) { |
316 | _front->drawInto(); |
317 | _front->_scrollY = vscroll; |
318 | flush(true, true); |
319 | result = true; |
320 | } else { |
321 | result = false; |
322 | } |
323 | return result; |
324 | } |
325 | |
326 | // sets the current drawing color |
327 | void AnsiWidget::setColor(long fg) { |
328 | _back->setColor(fg); |
329 | } |
330 | |
331 | // sets the font |
332 | void AnsiWidget::setFont(int size, bool bold, bool italic) { |
333 | _back->setGraphicsRendition('m', bold ? 1 : 21, 0); |
334 | _back->setGraphicsRendition('m', italic ? 3 : 23, 0); |
335 | _back->updateFont(size); |
336 | } |
337 | |
338 | // sets the text font size |
339 | void AnsiWidget::setFontSize(int fontSize) { |
340 | this->_fontSize = fontSize; |
341 | for (int i = 0; i < MAX_SCREENS; i++) { |
342 | if (_screens[i] != nullptr) { |
343 | _screens[i]->reset(fontSize); |
344 | } |
345 | } |
346 | redraw(); |
347 | } |
348 | |
349 | // sets the pixel to the given color at the given xy location |
350 | void AnsiWidget::setPixel(int x, int y, int c) { |
351 | _back->setPixel(x, y, c); |
352 | flush(false, false, MAX_PENDING_GRAPHICS); |
353 | } |
354 | |
355 | void AnsiWidget::setStatus(const char *label) { |
356 | _back->_label = label; |
357 | _back->setDirty(); |
358 | } |
359 | |
360 | // sets the current text drawing color |
361 | void AnsiWidget::setTextColor(long fg, long bg) { |
362 | _back->setTextColor(fg, bg); |
363 | } |
364 | |
365 | void AnsiWidget::setXY(int x, int y) { |
366 | if (x != _back->_curX || y != _back->_curY) { |
367 | int lineHeight = textHeight(); |
368 | int newLines = (y - _back->_curY) / lineHeight; |
369 | while (newLines-- > 0) { |
370 | _back->newLine(lineHeight); |
371 | } |
372 | _back->_curX = (x == 0) ? INITXY : x; |
373 | _back->_curY = y; |
374 | flush(false, false, MAX_PENDING_GRAPHICS); |
375 | } |
376 | } |
377 | |
378 | void AnsiWidget::handleMenu(bool up) { |
379 | _activeButton = _front->getNextMenu(_activeButton, up); |
380 | } |
381 | |
382 | void AnsiWidget::(int x, int y, int w, int h) { |
383 | if (_back == _screens[MENU_SCREEN]) { |
384 | _back = _screens[USER_SCREEN1]; |
385 | } |
386 | TextScreen * = (TextScreen *)createScreen(MENU_SCREEN); |
387 | menuScreen->_x = x; |
388 | menuScreen->_y = y; |
389 | menuScreen->_width = w; |
390 | menuScreen->_height = h; |
391 | menuScreen->setOver(_front); |
392 | _front = _back = menuScreen; |
393 | _front->_dirty = true; |
394 | } |
395 | |
396 | void AnsiWidget::insetTextScreen(int x, int y, int w, int h) { |
397 | if (_back == _screens[TEXT_SCREEN]) { |
398 | _back = _screens[USER_SCREEN1]; |
399 | } |
400 | TextScreen *textScreen = (TextScreen *)createScreen(TEXT_SCREEN); |
401 | textScreen->inset(x, y, w, h, _front); |
402 | _front = _back = textScreen; |
403 | _front->_dirty = true; |
404 | flush(true); |
405 | } |
406 | |
407 | // handler for pointer touch events |
408 | bool AnsiWidget::pointerTouchEvent(MAEvent &event) { |
409 | bool result = false; |
410 | // hit test buttons on the front screen |
411 | if (setActiveButton(event, _front)) { |
412 | _focus = _front; |
413 | } else { |
414 | // hit test buttons on remaining screens |
415 | for (int i = 0; i < MAX_SCREENS; i++) { |
416 | if (_screens[i] != nullptr && _screens[i] != _front) { |
417 | if (setActiveButton(event, _screens[i])) { |
418 | _focus = _screens[i]; |
419 | break; |
420 | } |
421 | } |
422 | } |
423 | } |
424 | // paint the pressed button |
425 | if (_activeButton != nullptr) { |
426 | _activeButton->clicked(event.point.x, event.point.y, true); |
427 | drawActiveButton(); |
428 | } |
429 | // setup vars for page scrolling |
430 | if (_front->overlaps(event.point.x, event.point.y)) { |
431 | _touchTime = maGetMilliSecondCount(); |
432 | _xTouch = _xMove = event.point.x; |
433 | _yTouch = _yMove = event.point.y; |
434 | result = true; |
435 | } |
436 | return result; |
437 | } |
438 | |
439 | // handler for pointer move events |
440 | bool AnsiWidget::pointerMoveEvent(MAEvent &event) { |
441 | bool result = false; |
442 | if (_front == _screens[MENU_SCREEN]) { |
443 | _activeButton = _front->getMenu(_activeButton, event.point.x, event.point.y); |
444 | } else if (_activeButton != nullptr) { |
445 | bool redraw = false; |
446 | bool pressed = _activeButton->selected(event.point, _focus->_x, |
447 | _focus->_y - _focus->_scrollY, redraw); |
448 | if (redraw || (pressed != _activeButton->_pressed)) { |
449 | _activeButton->_pressed = pressed; |
450 | drawActiveButton(); |
451 | result = true; |
452 | } |
453 | } else if (!_swipeExit && _xMove != -1 && _yMove != -1 && |
454 | _front->overlaps(event.point.x, event.point.y)) { |
455 | int hscroll = _front->_scrollX + (_xMove - event.point.x); |
456 | int vscroll = _front->_scrollY + (_yMove - event.point.y); |
457 | int maxHScroll = MAX(0, _front->getMaxHScroll()); |
458 | int maxVScroll = (_front->_curY - _front->_height) + (2 * _fontSize); |
459 | if (hscroll < 0) { |
460 | hscroll = 0; |
461 | } else if (hscroll > maxHScroll) { |
462 | hscroll = maxHScroll; |
463 | } |
464 | if (vscroll < 0) { |
465 | vscroll = 0; |
466 | } |
467 | if ((abs(hscroll - _front->_scrollX) > H_SCROLL_SIZE |
468 | && maxHScroll > 0 |
469 | && hscroll <= maxHScroll) || |
470 | (vscroll != _front->_scrollY && maxVScroll > 0 && |
471 | vscroll < maxVScroll + OVER_SCROLL)) { |
472 | _front->drawInto(); |
473 | _front->_scrollX = hscroll; |
474 | _front->_scrollY = vscroll; |
475 | _xMove = event.point.x; |
476 | _yMove = event.point.y; |
477 | flush(true, true); |
478 | result = true; |
479 | } |
480 | } else { |
481 | result = drawHoverLink(event); |
482 | } |
483 | return result; |
484 | } |
485 | |
486 | // handler for pointer release events |
487 | void AnsiWidget::pointerReleaseEvent(MAEvent &event) { |
488 | if (_activeButton != nullptr && _front == _screens[MENU_SCREEN]) { |
489 | _activeButton->clicked(event.point.x, event.point.y, false); |
490 | } else if (_activeButton != nullptr && _activeButton->_pressed) { |
491 | _activeButton->_pressed = false; |
492 | drawActiveButton(); |
493 | _activeButton->clicked(event.point.x, event.point.y, false); |
494 | } else if (_swipeExit) { |
495 | _swipeExit = false; |
496 | } else { |
497 | int maxScroll = (_front->_curY - _front->_height) + (2 * _fontSize); |
498 | if (_yMove != -1 && maxScroll > 0) { |
499 | _front->drawInto(); |
500 | |
501 | // swipe test - min distance and not max duration |
502 | int deltaX = _xTouch - event.point.x; |
503 | int deltaY = _yTouch - event.point.y; |
504 | int distance = (int) fabs(sqrt(deltaX * deltaX + deltaY * deltaY)); |
505 | int now = maGetMilliSecondCount(); |
506 | if (distance >= SWIPE_MIN_DISTANCE && (now - _touchTime) < SWIPE_MAX_DURATION) { |
507 | bool moveDown = (deltaY >= SWIPE_MIN_DISTANCE); |
508 | doSwipe(now, moveDown, distance, maxScroll); |
509 | } else if (_front->_scrollY > maxScroll) { |
510 | _front->_scrollY = maxScroll; |
511 | } |
512 | // ensure the scrollbar is removed |
513 | _front->_dirty = true; |
514 | flush(true); |
515 | _touchTime = 0; |
516 | } |
517 | } |
518 | |
519 | if (_hoverInput) { |
520 | int dx = _front->_x; |
521 | int dy = _front->_y - _front->_scrollY; |
522 | _hoverInput->drawHover(dx, dy, false); |
523 | _hoverInput = nullptr; |
524 | } |
525 | |
526 | _xTouch = _xMove = -1; |
527 | _yTouch = _yMove = -1; |
528 | _activeButton = nullptr; |
529 | _focus = nullptr; |
530 | } |
531 | |
532 | // handles the characters following the \e[ sequence. Returns whether a further call |
533 | // is required to complete the process. |
534 | bool AnsiWidget::doEscape(const char *&p, int textHeight) { |
535 | int escValue = 0; |
536 | |
537 | while (isdigit(*p)) { |
538 | escValue = (escValue * 10) + (*p - '0'); |
539 | p++; |
540 | } |
541 | |
542 | if (_back->setGraphicsRendition(*p, escValue, textHeight)) { |
543 | _back->updateFont(); |
544 | } |
545 | |
546 | bool result = false; |
547 | if (*p == ';') { |
548 | result = true; |
549 | // advance to next rendition |
550 | p++; |
551 | } |
552 | return result; |
553 | } |
554 | |
555 | // swipe handler for pointerReleaseEvent() |
556 | void AnsiWidget::doSwipe(int start, bool moveDown, int distance, int maxScroll) { |
557 | MAEvent event; |
558 | int elapsed = 0; |
559 | int vscroll = _front->_scrollY; |
560 | int scrollSize = distance / 3; |
561 | int swipeStep = SWIPE_DELAY_STEP; |
562 | while (elapsed < SWIPE_MAX_TIMER) { |
563 | if (maGetEvent(&event) && event.type == EVENT_TYPE_POINTER_RELEASED) { |
564 | // ignore the next move and release events |
565 | _swipeExit = true; |
566 | break; |
567 | } |
568 | elapsed += (maGetMilliSecondCount() - start); |
569 | if (elapsed > swipeStep && scrollSize > 1) { |
570 | // step down to a lesser scroll amount |
571 | scrollSize -= 1; |
572 | swipeStep += SWIPE_DELAY_STEP; |
573 | } |
574 | if (scrollSize == 1) { |
575 | maWait(20); |
576 | } |
577 | vscroll += moveDown ? scrollSize : -scrollSize; |
578 | if (vscroll < 0) { |
579 | vscroll = 0; |
580 | } else if (vscroll > maxScroll) { |
581 | vscroll = maxScroll; |
582 | } |
583 | if (vscroll != _front->_scrollY) { |
584 | _front->_dirty = true; // forced |
585 | _front->_scrollY = vscroll; |
586 | flush(true, true); |
587 | } else { |
588 | break; |
589 | } |
590 | } |
591 | |
592 | // pause before removing the scrollbar |
593 | maWait(500); |
594 | } |
595 | |
596 | // draws the focus screen's active button |
597 | void AnsiWidget::drawActiveButton() { |
598 | #if defined(_SDL) || defined(_FLTK) |
599 | if (_focus != nullptr && !_activeButton->hasHover()) { |
600 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); |
601 | _focus->drawShape(_activeButton); |
602 | _focus->drawLabel(); |
603 | if (_activeButton->isFullScreen()) { |
604 | _focus->drawMenu(); |
605 | } |
606 | maUpdateScreen(); |
607 | maSetDrawTarget(currentHandle); |
608 | } |
609 | #else |
610 | if (_activeButton->hasHover()) { |
611 | int dx = _front->_x; |
612 | int dy = _front->_y - _front->_scrollY; |
613 | _activeButton->drawHover(dx, dy, _activeButton->_pressed); |
614 | } else if (_focus != nullptr) { |
615 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); |
616 | _focus->drawShape(_activeButton); |
617 | _focus->drawLabel(); |
618 | if (_activeButton->isFullScreen()) { |
619 | _focus->drawMenu(); |
620 | } |
621 | maUpdateScreen(); |
622 | maSetDrawTarget(currentHandle); |
623 | } |
624 | #endif |
625 | } |
626 | |
627 | bool AnsiWidget::drawHoverLink(MAEvent &event) { |
628 | #if defined(_SDL) || defined(_FLTK) |
629 | if (_front != _screens[MENU_SCREEN]) { |
630 | int dx = _front->_x; |
631 | int dy = _front->_y - _front->_scrollY; |
632 | FormInput *active = nullptr; |
633 | if (_front->overlaps(event.point.x, event.point.y)) { |
634 | List_each(FormInput *, it, _front->_inputs) { |
635 | FormInput *widget = *it; |
636 | if (widget->hasHover() && |
637 | widget->overlaps(event.point, dx, dy)) { |
638 | active = widget; |
639 | break; |
640 | } |
641 | } |
642 | } |
643 | if (active && active != _hoverInput) { |
644 | if (_hoverInput) { |
645 | // remove old hover |
646 | _hoverInput->drawHover(dx, dy, false); |
647 | } |
648 | // display new hover |
649 | _hoverInput = active; |
650 | _hoverInput->drawHover(dx, dy, true); |
651 | } else if (!active && _hoverInput) { |
652 | // no new hover, erase old hover |
653 | _hoverInput->drawHover(dx, dy, false); |
654 | _hoverInput = nullptr; |
655 | } |
656 | } |
657 | #endif |
658 | return _hoverInput != nullptr; |
659 | } |
660 | |
661 | // print() helper |
662 | void AnsiWidget::handleEscape(const char *&p, int lineHeight) { |
663 | switch (*(p + 1)) { |
664 | case '[': |
665 | p += 2; |
666 | while (doEscape(p, lineHeight)) { |
667 | // continue |
668 | } |
669 | break; |
670 | case 'm': |
671 | // scroll to the top (M = scroll up one line) |
672 | p += 2; |
673 | _back->_scrollY = 0; |
674 | break; |
675 | case '<': |
676 | // select back screen |
677 | switch (*(p + 2)) { |
678 | case '1': |
679 | p += 3; |
680 | selectBackScreen(USER_SCREEN1); |
681 | break; |
682 | case '2': |
683 | p += 3; |
684 | selectBackScreen(USER_SCREEN2); |
685 | break; |
686 | } |
687 | break; |
688 | case '>': |
689 | // select front screen |
690 | switch (*(p + 2)) { |
691 | case '1': |
692 | p += 3; |
693 | selectFrontScreen(USER_SCREEN1); |
694 | break; |
695 | case '2': |
696 | p += 3; |
697 | selectFrontScreen(USER_SCREEN2); |
698 | break; |
699 | } |
700 | break; |
701 | default: |
702 | break; |
703 | } |
704 | } |
705 | |
706 | // returns whether the event is over the given screen |
707 | bool AnsiWidget::setActiveButton(MAEvent &event, Screen *screen) { |
708 | bool result = false; |
709 | if (_front != _screens[MENU_SCREEN] && |
710 | screen->overlaps(event.point.x, event.point.y)) { |
711 | List_each(FormInput *, it, screen->_inputs) { |
712 | FormInput *widget = *it; |
713 | bool redraw = false; |
714 | if (widget->selected(event.point, screen->_x, |
715 | screen->_y - screen->_scrollY, redraw)) { |
716 | _activeButton = widget; |
717 | _activeButton->_pressed = true; |
718 | break; |
719 | } |
720 | if (redraw) { |
721 | _front->_dirty = true; |
722 | flush(true); |
723 | } |
724 | } |
725 | // screen overlaps event - avoid search in other screens |
726 | result = true; |
727 | } |
728 | return result; |
729 | } |
730 | |
731 | void AnsiWidget::selectBackScreen(int screenId) { |
732 | _hoverInput = nullptr; |
733 | _back = createScreen(screenId); |
734 | _back->selectFont(); |
735 | } |
736 | |
737 | void AnsiWidget::selectFrontScreen(int screenId) { |
738 | _front = createScreen(screenId); |
739 | _front->_dirty = true; |
740 | flush(true); |
741 | } |
742 | |
743 | int AnsiWidget::selectScreen(int screenId, bool forceFlush) { |
744 | int result = getScreenId(true); |
745 | selectBackScreen(screenId); |
746 | _front = _back; |
747 | _front->_dirty = true; |
748 | flush(forceFlush); |
749 | return result; |
750 | } |
751 | |