1 | // This file is part of SmallBASIC |
2 | // |
3 | // Copyright(C) 2001-2019 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 | |
11 | #include "ui/screen.h" |
12 | |
13 | #define WHITE 15 |
14 | #define SCROLL_IND 4 |
15 | #define MAX_HEIGHT 10000 |
16 | #define TEXT_ROWS 1000 |
17 | |
18 | #define DRAW_SHAPE \ |
19 | Shape *rect = (*it); \ |
20 | if (rect->_y >= _scrollY && \ |
21 | rect->_y <= _scrollY + _height) \ |
22 | rect->draw(_x + rect->_x, _y + rect->_y - _scrollY, w(), h(), _charWidth) |
23 | |
24 | int compareZIndex(const void *p1, const void *p2) { |
25 | ImageDisplay **i1 = (ImageDisplay **)p1; |
26 | ImageDisplay **i2 = (ImageDisplay **)p2; |
27 | return (*i1)->_zIndex < (*i2)->_zIndex ? -1 : (*i1)->_zIndex == (*i2)->_zIndex ? 0 : 1; |
28 | } |
29 | |
30 | bool Shape::isFullScreen() const { |
31 | MAExtent screenSize = maGetScrSize(); |
32 | return _width == EXTENT_X(screenSize) && _height == EXTENT_Y(screenSize); |
33 | } |
34 | |
35 | Screen::Screen(int x, int y, int width, int height, int fontSize) : |
36 | Shape(x, y, width, height), |
37 | _font(0), |
38 | _fontSize(fontSize), |
39 | _fontStyle(0), |
40 | _charWidth(0), |
41 | _charHeight(0), |
42 | _scrollX(0), |
43 | _scrollY(0), |
44 | _bg(0), |
45 | _fg(0), |
46 | _curX(INITXY), |
47 | _curY(INITXY), |
48 | _dirty(0), |
49 | _linePadding(0) { |
50 | } |
51 | |
52 | Screen::~Screen() { |
53 | if (_font) { |
54 | maFontDelete(_font); |
55 | } |
56 | _images.removeAll(); |
57 | } |
58 | |
59 | // converts ANSI colors to MoSync colors |
60 | int Screen::ansiToMosync(long c) { |
61 | int result; |
62 | if (c < 0) { |
63 | result = -c; |
64 | } else { |
65 | result = (c > 15) ? colors[WHITE] : colors[c]; |
66 | } |
67 | return result; |
68 | } |
69 | |
70 | void Screen::add(Shape *button) { |
71 | _shapes.add(button); |
72 | if (button->_x + button->_width > _curX) { |
73 | _curX = button->_x + button->_width; |
74 | } |
75 | if (button->_y > _curY) { |
76 | _curY = button->_y; |
77 | } |
78 | } |
79 | |
80 | void Screen::addImage(ImageDisplay &image) { |
81 | bool exists = false; |
82 | List_each(ImageDisplay *, it, _images) { |
83 | ImageDisplay *next = (*it); |
84 | if (next->_id == image._id) { |
85 | exists = true; |
86 | next->copyImage(image); |
87 | break; |
88 | } |
89 | } |
90 | if (!exists) { |
91 | _images.add(new ImageDisplay(image)); |
92 | } |
93 | _images.sort(compareZIndex); |
94 | setDirty(); |
95 | } |
96 | |
97 | void Screen::clear() { |
98 | _curX = INITXY; |
99 | _curY = INITXY; |
100 | _scrollX = 0; |
101 | _scrollY = 0; |
102 | |
103 | // cleanup any shapes |
104 | _shapes.removeAll(); |
105 | _inputs.removeAll(); |
106 | _images.removeAll(); |
107 | _label.clear(); |
108 | } |
109 | |
110 | void Screen::drawLabel() { |
111 | if (!_label.empty()) { |
112 | int labelLen = _label.length(); |
113 | int w = _charWidth * (labelLen + 2); |
114 | int h = _charHeight + 2; |
115 | int top = _height - h; |
116 | int left = (_width - w) / 2; |
117 | int textY = top + ((h - _charHeight) / 2); |
118 | |
119 | maSetColor(0xbfbfbf); |
120 | maFillRect(left, top, w, h); |
121 | maSetColor(0xe5e5e5); |
122 | maLine(left, top, left + w, top); |
123 | maSetColor(0x737373); |
124 | maLine(left, top + h - 1, left + w, top + h - 1); |
125 | maLine(left + w, top + 1, left + w, top + h - 1); |
126 | maSetColor(0x403c44); |
127 | maDrawText(left + _charWidth, textY, _label.c_str(), labelLen); |
128 | } |
129 | } |
130 | |
131 | void Screen::() { |
132 | static const char dot[] = {'\260', '\0'}; |
133 | int gap = _charHeight / 3; |
134 | int left = _width - _charWidth - (_charWidth / 2); |
135 | int top = (_height - _charHeight) + gap; |
136 | maSetColor(_fg); |
137 | maDrawText(left, top, dot, 1); |
138 | maDrawText(left, top - gap, dot, 1); |
139 | maDrawText(left, top - gap - gap, dot, 1); |
140 | } |
141 | |
142 | void Screen::drawShape(Shape *rect) { |
143 | if (rect != NULL && |
144 | rect->_y >= _scrollY && |
145 | rect->_y + rect->_height <= _scrollY + _height) { |
146 | rect->draw(_x + rect->_x, _y + rect->_y - _scrollY, w(), h(), _charWidth); |
147 | |
148 | List_each(FormInput *, it, _inputs) { |
149 | FormInput *input = (*it); |
150 | if (input->isVisible() && |
151 | input->isDrawTop() && |
152 | input->_y >= _scrollY - _height && |
153 | input->_y + input->_height <= _scrollY + _height) { |
154 | input->draw(_x + input->_x, _y + input->_y - _scrollY, w(), h(), _charWidth); |
155 | } |
156 | } |
157 | } |
158 | } |
159 | |
160 | void Screen::drawOverlay(bool vscroll) { |
161 | // draw any visible shapes |
162 | List_each(Shape *, it, _shapes) { |
163 | DRAW_SHAPE; |
164 | } |
165 | |
166 | List_each(ImageDisplay *, it, _images) { |
167 | DRAW_SHAPE; |
168 | } |
169 | |
170 | FormInput *drawTop = NULL; |
171 | List_each(FormInput *, it, _inputs) { |
172 | FormInput *input = (*it); |
173 | if (input->_y >= _scrollY - _height && |
174 | input->isVisible()) { |
175 | if (input->isDrawTop()) { |
176 | drawTop = input; |
177 | } else { |
178 | input->draw(_x + input->_x, _y + input->_y - _scrollY, w(), h(), _charWidth); |
179 | } |
180 | } |
181 | } |
182 | if (drawTop != NULL) { |
183 | drawTop->draw(_x + drawTop->_x, _y + drawTop->_y - _scrollY, w(), h(), _charWidth); |
184 | } |
185 | |
186 | if (vscroll && _curY) { |
187 | // display the vertical scrollbar |
188 | int pageHeight = _curY + _charHeight + _charHeight; |
189 | int barSize = _height * _height / pageHeight; |
190 | |
191 | if (barSize < _height) { |
192 | int barRange = _height - (barSize + SCROLL_IND * 2); |
193 | int barTop = SCROLL_IND + (barRange * _scrollY / (pageHeight - _height)); |
194 | int barBottom = _y + barTop + barSize; |
195 | if (barBottom + SCROLL_IND > _height) { |
196 | barBottom = _height - SCROLL_IND; |
197 | } |
198 | maSetColor(_fg); |
199 | maLine(_x + _width - 3, _y + barTop, _x + _width - 3, barBottom); |
200 | maLine(_x + _width - 4, _y + barTop, _x + _width - 4, barBottom); |
201 | } |
202 | } |
203 | |
204 | drawLabel(); |
205 | |
206 | #if defined(_FLTK) |
207 | drawMenu(); |
208 | #else |
209 | if ((!_inputs.empty() || !_label.empty()) && isFullScreen()) { |
210 | // draw the menu widget when in UI mode |
211 | drawMenu(); |
212 | } |
213 | #endif |
214 | } |
215 | |
216 | void Screen::drawInto(bool background) { |
217 | maSetColor(background ? _bg : _fg); |
218 | setDirty(); |
219 | } |
220 | |
221 | int Screen::getIndex(FormInput *input) const { |
222 | int index; |
223 | if (input == NULL) { |
224 | index = -1; |
225 | } else { |
226 | index = 0; |
227 | List_each(FormInput *, it, _inputs) { |
228 | FormInput *next = (*it); |
229 | if (next == input) { |
230 | break; |
231 | } else { |
232 | index++; |
233 | } |
234 | } |
235 | } |
236 | return index; |
237 | } |
238 | |
239 | FormInput *Screen::(FormInput *prev, int px, int py) { |
240 | FormInput *result = _inputs[0]; |
241 | if (result != NULL && overlaps(px, py)) { |
242 | int item = (py - _y) / result->_height; |
243 | result = _inputs[item]; |
244 | } else { |
245 | result = NULL; |
246 | } |
247 | if (result != prev) { |
248 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); |
249 | if (prev != NULL) { |
250 | prev->_pressed = false; |
251 | drawShape(prev); |
252 | } |
253 | if (result != NULL) { |
254 | result->_pressed = true; |
255 | drawShape(result); |
256 | } |
257 | maUpdateScreen(); |
258 | maSetDrawTarget(currentHandle); |
259 | } |
260 | return result; |
261 | } |
262 | |
263 | FormInput *Screen::(FormInput *prev, bool up) { |
264 | int index; |
265 | if (prev == NULL) { |
266 | index = 0; |
267 | } else { |
268 | index = getIndex(prev) + (up ? -1 : 1); |
269 | } |
270 | FormInput *next = prev; |
271 | if (index > -1 && index < _inputs.size()) { |
272 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); |
273 | next = _inputs.get(index); |
274 | next->_pressed = true; |
275 | drawShape(next); |
276 | if (prev != NULL) { |
277 | prev->_pressed = false; |
278 | drawShape(prev); |
279 | } |
280 | maUpdateScreen(); |
281 | maSetDrawTarget(currentHandle); |
282 | } |
283 | return next; |
284 | } |
285 | |
286 | void Screen::layoutInputs(int newWidth, int newHeight) { |
287 | List_each(FormInput *, it, _inputs) { |
288 | FormInput *r1 = (*it); |
289 | r1->layout(newWidth, newHeight); |
290 | } |
291 | } |
292 | |
293 | // whether the point overlaps the label text |
294 | bool Screen::overLabel(int px, int py) { |
295 | bool result; |
296 | if (!_label.empty()) { |
297 | int w = _charWidth * (_label.length() + 2); |
298 | int h = _charHeight + 2; |
299 | int top = _height - h; |
300 | int left = (_width - w) / 2; |
301 | result = (!OUTSIDE_RECT(px, py, left, top, w, h)); |
302 | } else { |
303 | result = false; |
304 | } |
305 | return result; |
306 | } |
307 | |
308 | // whether the point overlaps the menu widget |
309 | bool Screen::(int px, int py) { |
310 | int w = _charWidth * 3; |
311 | int h = _charHeight * 2; |
312 | return (!OUTSIDE_RECT(px, py, _width - w, _height - h, w, h)); |
313 | } |
314 | |
315 | // whether the given point overlaps with the screen rectangle |
316 | bool Screen::overlaps(int px, int py) { |
317 | return (!OUTSIDE_RECT(px, py, _x, _y, _width, _height)); |
318 | } |
319 | |
320 | int Screen::print(const char *p, int lineHeight, bool allChars) { |
321 | // print minimum of one character |
322 | int numChars = 1; |
323 | int cx = _charWidth; |
324 | int w = _width - 1; |
325 | |
326 | // print further non-control, non-null characters |
327 | // up to the width of the line |
328 | while (p[numChars] > 31) { |
329 | cx += _charWidth; |
330 | if (allChars || _curX + cx < w) { |
331 | numChars++; |
332 | } else { |
333 | break; |
334 | } |
335 | } |
336 | |
337 | _curX += cx; |
338 | return numChars; |
339 | } |
340 | |
341 | // remove the button from the list |
342 | void Screen::remove(Shape *button) { |
343 | List_each(Shape *, it, _shapes) { |
344 | Shape *next = (*it); |
345 | if (next == button) { |
346 | _shapes.remove(it); |
347 | setDirty(); |
348 | break; |
349 | } |
350 | } |
351 | } |
352 | |
353 | // remove the image from the list |
354 | bool Screen::removeInput(FormInput *input) { |
355 | bool result = false; |
356 | List_each(FormInput *, it, _inputs) { |
357 | FormInput *next = (*it); |
358 | if (next == input) { |
359 | _inputs.remove(it); |
360 | result = true; |
361 | break; |
362 | } |
363 | } |
364 | return result; |
365 | } |
366 | |
367 | // remove the image from the list |
368 | void Screen::removeImage(unsigned imageId) { |
369 | List_each(ImageDisplay *, it, _images) { |
370 | ImageDisplay *next = (*it); |
371 | if (next->_id == imageId) { |
372 | _images.remove(it); |
373 | delete next; |
374 | setDirty(); |
375 | break; |
376 | } |
377 | } |
378 | } |
379 | |
380 | void Screen::replaceFont(int type) { |
381 | logEntered(); |
382 | if (_font) { |
383 | maFontDelete(_font); |
384 | } |
385 | _font = maFontLoadDefault(type, _fontStyle, _fontSize); |
386 | if (_font != -1) { |
387 | maFontSetCurrent(_font); |
388 | MAExtent extent = maGetTextSize("W" ); |
389 | _charWidth = EXTENT_X(extent); |
390 | _charHeight = EXTENT_Y(extent) + LINE_SPACING; |
391 | } else { |
392 | trace("maFontLoadDefault failed: style=%d size=%d" , _fontStyle, _fontSize); |
393 | } |
394 | } |
395 | |
396 | void Screen::reset(int fontSize) { |
397 | _fg = DEFAULT_FOREGROUND; |
398 | _bg = DEFAULT_BACKGROUND; |
399 | setFont(false, false, fontSize); |
400 | } |
401 | |
402 | void Screen::setColor(long color) { |
403 | _fg = ansiToMosync(color); |
404 | } |
405 | |
406 | void Screen::setTextColor(long foreground, long background) { |
407 | _bg = ansiToMosync(background); |
408 | _fg = ansiToMosync(foreground); |
409 | } |
410 | |
411 | // updated the current font according to accumulated flags |
412 | void Screen::setFont(bool bold, bool italic, int size) { |
413 | int style = FONT_STYLE_NORMAL; |
414 | int type = FONT_TYPE_MONOSPACE; |
415 | |
416 | if (italic) { |
417 | style |= FONT_STYLE_ITALIC; |
418 | type = FONT_TYPE_SERIF; |
419 | } |
420 | if (bold) { |
421 | style |= FONT_STYLE_BOLD; |
422 | if (!italic) { |
423 | type = FONT_TYPE_SANS_SERIF; |
424 | } |
425 | } |
426 | |
427 | if ((style != _fontStyle || size != _fontSize) || !_font) { |
428 | _fontStyle = style; |
429 | _fontSize = size; |
430 | replaceFont(type); |
431 | } |
432 | } |
433 | |
434 | FormInput *Screen::getNextField(FormInput *field) { |
435 | FormInput *result = NULL; |
436 | bool setNext = false; |
437 | List_each(FormInput *, it, _inputs) { |
438 | FormInput *next = (*it); |
439 | if (!next->isNoFocus()) { |
440 | if (result == NULL) { |
441 | // set result to first item |
442 | result = next; |
443 | if (field == NULL) { |
444 | // no next item |
445 | break; |
446 | } |
447 | } |
448 | if (setNext) { |
449 | result = next; |
450 | break; |
451 | } else if (next == field) { |
452 | setNext = true; |
453 | } |
454 | } |
455 | } |
456 | return result; |
457 | } |
458 | |
459 | void Screen::updateInputs(var_p_t form, bool setVars) { |
460 | List_each(FormInput *, it, _inputs) { |
461 | FormInput *next = (*it); |
462 | if (setVars) { |
463 | next->updateField(form); |
464 | } else { |
465 | var_p_t field = next->getField(form); |
466 | if (field == NULL) { |
467 | _inputs.remove(it); |
468 | delete next; |
469 | setDirty(); |
470 | it--; |
471 | } else if (next->updateUI(form, field)) { |
472 | setDirty(); |
473 | } |
474 | } |
475 | } |
476 | } |
477 | |
478 | // |
479 | // Graphics and text based screen with limited scrollback support |
480 | // |
481 | GraphicScreen::GraphicScreen(int width, int height, int fontSize) : |
482 | Screen(0, 0, width, height, fontSize), |
483 | _image(0), |
484 | _underline(0), |
485 | _invert(0), |
486 | _bold(0), |
487 | _italic(0), |
488 | _imageWidth(width), |
489 | _imageHeight(height), |
490 | _curYSaved(0), |
491 | _curXSaved(0), |
492 | _tabSize(40) { // tab size in pixels (160/32 = 5) |
493 | } |
494 | |
495 | GraphicScreen::~GraphicScreen() { |
496 | if (_image) { |
497 | maDestroyPlaceholder(_image); |
498 | } |
499 | } |
500 | |
501 | // calculate the pixel movement for the given cursor position |
502 | void GraphicScreen::calcTab() { |
503 | int c = 1; |
504 | int x = _curX + 1; |
505 | while (x > _tabSize) { |
506 | x -= _tabSize; |
507 | c++; |
508 | } |
509 | _curX = c * _tabSize; |
510 | } |
511 | |
512 | bool GraphicScreen::construct() { |
513 | bool result = true; |
514 | _image = maCreatePlaceholder(); |
515 | if (maCreateDrawableImage(_image, _imageWidth, _imageHeight) == RES_OK) { |
516 | reset(_fontSize); |
517 | } else { |
518 | result = false; |
519 | } |
520 | return result; |
521 | } |
522 | |
523 | void GraphicScreen::clear() { |
524 | drawInto(true); |
525 | maSetColor(_bg); |
526 | maFillRect(0, 0, _imageWidth, _imageHeight); |
527 | Screen::clear(); |
528 | } |
529 | |
530 | void GraphicScreen::drawArc(int xc, int yc, double r, double start, double end, double aspect) { |
531 | drawInto(); |
532 | maArc(xc, yc, r, start, end, aspect); |
533 | } |
534 | |
535 | void GraphicScreen::drawBase(bool vscroll, bool update) { |
536 | MARect srcRect; |
537 | MAPoint2d dstPoint; |
538 | srcRect.left = 0; |
539 | srcRect.top = _scrollY; |
540 | srcRect.width = _width; |
541 | srcRect.height = _height; |
542 | dstPoint.x = _x; |
543 | dstPoint.y = _y; |
544 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); |
545 | maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE); |
546 | |
547 | drawOverlay(vscroll); |
548 | _dirty = 0; |
549 | if (update) { |
550 | maUpdateScreen(); |
551 | } |
552 | maSetDrawTarget(currentHandle); |
553 | } |
554 | |
555 | void GraphicScreen::drawEllipse(int xc, int yc, int rx, int ry, int fill) { |
556 | drawInto(); |
557 | maEllipse(xc, yc, rx, ry, fill); |
558 | } |
559 | |
560 | void GraphicScreen::drawImage(ImageDisplay &image) { |
561 | drawInto(); |
562 | image.draw(image._x, image._y, image._width, image._height, 0); |
563 | } |
564 | |
565 | void GraphicScreen::drawInto(bool background) { |
566 | maSetDrawTarget(_image); |
567 | Screen::drawInto(background); |
568 | } |
569 | |
570 | void GraphicScreen::drawLine(int x1, int y1, int x2, int y2) { |
571 | drawInto(); |
572 | maLine(x1, y1, x2, y2); |
573 | } |
574 | |
575 | void GraphicScreen::drawRect(int x1, int y1, int x2, int y2) { |
576 | drawInto(); |
577 | maLine(x1, y1, x2, y1); // top |
578 | maLine(x1, y2, x2, y2); // bottom |
579 | maLine(x1, y1, x1, y2); // left |
580 | maLine(x2, y1, x2, y2); // right |
581 | } |
582 | |
583 | void GraphicScreen::drawRectFilled(int x1, int y1, int x2, int y2) { |
584 | drawInto(); |
585 | maFillRect(x1, y1, x2 - x1, y2 - y1); |
586 | } |
587 | |
588 | // returns the color of the pixel at the given xy location |
589 | int GraphicScreen::getPixel(int x, int y) { |
590 | MARect rc; |
591 | rc.left = x; |
592 | rc.top = y; |
593 | rc.width = 1; |
594 | rc.height = 1; |
595 | int data[1]; |
596 | |
597 | if (x < 0 || y < 0) { |
598 | rc.left = x < 0 ? -x : x; |
599 | rc.top = y < 0 ? -y : y; |
600 | drawBase(false); |
601 | maGetImageData(HANDLE_SCREEN, &data, &rc, 1); |
602 | } else { |
603 | maGetImageData(_image, &data, &rc, 1); |
604 | } |
605 | return -(data[0] & 0x00FFFFFF); |
606 | } |
607 | |
608 | // extend the image to allow for additional content on the newline |
609 | void GraphicScreen::imageAppend(MAHandle newImage) { |
610 | MARect srcRect; |
611 | MAPoint2d dstPoint; |
612 | |
613 | srcRect.left = 0; |
614 | srcRect.top = 0; |
615 | srcRect.width = _imageWidth; |
616 | srcRect.height = _imageHeight; |
617 | dstPoint.x = 0; |
618 | dstPoint.y = 0; |
619 | |
620 | maSetDrawTarget(newImage); |
621 | maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE); |
622 | |
623 | // clear the new segment |
624 | maSetColor(_bg); |
625 | maFillRect(0, _imageHeight, _imageWidth, _imageHeight + _height); |
626 | _imageHeight += _height; |
627 | |
628 | // cleanup the old image |
629 | maDestroyPlaceholder(_image); |
630 | _image = newImage; |
631 | } |
632 | |
633 | // scroll back the image to allow for additioal content on the newline |
634 | void GraphicScreen::imageScroll() { |
635 | MAHandle newImage = maCreatePlaceholder(); |
636 | int newHeight = _imageHeight; |
637 | if (maCreateDrawableImage(newImage, _imageWidth, newHeight) == RES_OK) { |
638 | MARect srcRect; |
639 | MAPoint2d dstPoint; |
640 | int scrollBack = _height; |
641 | int copiedHeight = _imageHeight - scrollBack; |
642 | |
643 | srcRect.left = 0; |
644 | srcRect.top = scrollBack; |
645 | srcRect.width = _imageWidth; |
646 | srcRect.height = copiedHeight; |
647 | dstPoint.x = 0; |
648 | dstPoint.y = 0; |
649 | |
650 | maSetDrawTarget(newImage); |
651 | maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE); |
652 | |
653 | // clear the new segment |
654 | maSetColor(_bg); |
655 | maFillRect(0, copiedHeight, _imageWidth, scrollBack); |
656 | |
657 | // cleanup the old image |
658 | maDestroyPlaceholder(_image); |
659 | _image = newImage; |
660 | _scrollY -= scrollBack; |
661 | _curY -= scrollBack; |
662 | } else { |
663 | // unable to create duplicate |
664 | maDestroyPlaceholder(newImage); |
665 | clear(); |
666 | } |
667 | } |
668 | |
669 | // handles the \n character |
670 | void GraphicScreen::newLine(int lineHeight) { |
671 | lineHeight += _linePadding; |
672 | _linePadding = 0; |
673 | _curX = INITXY; |
674 | if (_height < MAX_HEIGHT) { |
675 | int offset = _curY + (lineHeight * 2); |
676 | if (offset >= _height) { |
677 | if (offset >= _imageHeight) { |
678 | // extend the base image by another page size |
679 | MAHandle newImage = maCreatePlaceholder(); |
680 | int newHeight = _imageHeight + _height; |
681 | if (maCreateDrawableImage(newImage, _imageWidth, newHeight) != RES_OK) { |
682 | // maximum image size reached |
683 | maDestroyPlaceholder(newImage); |
684 | imageScroll(); |
685 | lineHeight = 0; |
686 | } else { |
687 | imageAppend(newImage); |
688 | } |
689 | } |
690 | _scrollY += lineHeight; |
691 | } |
692 | _curY += lineHeight; |
693 | } else { |
694 | // overflow |
695 | clear(); |
696 | } |
697 | } |
698 | |
699 | int GraphicScreen::print(const char *p, int lineHeight, bool allChars) { |
700 | if (_curX + _charWidth >= _width - 1) { |
701 | newLine(lineHeight); |
702 | } |
703 | |
704 | int cx = _curX; |
705 | int numChars = Screen::print(p, lineHeight); |
706 | |
707 | // erase the background |
708 | maSetColor(_invert ? _fg : _bg); |
709 | maFillRect(cx, _curY, _curX-cx, lineHeight); |
710 | |
711 | // draw the text buffer |
712 | maSetColor(_invert ? _bg : _fg); |
713 | maDrawText(cx, _curY, p, numChars); |
714 | |
715 | if (_underline) { |
716 | maLine(cx, _curY + lineHeight - 2, _curX, _curY + lineHeight - 2); |
717 | } |
718 | |
719 | return numChars; |
720 | } |
721 | |
722 | // reset the current drawing variables |
723 | void GraphicScreen::reset(int fontSize) { |
724 | Screen::reset(fontSize); |
725 | _curXSaved = 0; |
726 | _curYSaved = 0; |
727 | _invert = false; |
728 | _underline = false; |
729 | _bold = false; |
730 | _italic = false; |
731 | } |
732 | |
733 | // update the widget to new dimensions |
734 | void GraphicScreen::resize(int newWidth, int newHeight, int oldWidth, |
735 | int oldHeight, int lineHeight) { |
736 | logEntered(); |
737 | bool fullscreen = ((_width - _x) == oldWidth && (_height - _y) == oldHeight); |
738 | if (fullscreen && (newWidth > _imageWidth || newHeight > _imageHeight)) { |
739 | // screen is larger than existing virtual size |
740 | MARect srcRect; |
741 | MAPoint2d dstPoint; |
742 | MAHandle newImage = maCreatePlaceholder(); |
743 | int newImageWidth = MAX(newWidth, _imageWidth); |
744 | int newImageHeight = MAX(newHeight, _imageHeight); |
745 | |
746 | srcRect.left = 0; |
747 | srcRect.top = 0; |
748 | srcRect.width = MIN(_imageWidth, newImageWidth); |
749 | srcRect.height = MIN(_imageHeight, newImageHeight); |
750 | dstPoint.x = 0; |
751 | dstPoint.y = 0; |
752 | |
753 | if (maCreateDrawableImage(newImage, newImageWidth, newImageHeight) == RES_OK) { |
754 | maSetDrawTarget(newImage); |
755 | maSetColor(_bg); |
756 | maFillRect(0, 0, newImageWidth, newImageHeight); |
757 | maDrawImageRegion(_image, &srcRect, &dstPoint, TRANS_NONE); |
758 | maDestroyPlaceholder(_image); |
759 | } else { |
760 | // cannot resize - alert and abort |
761 | deviceLog("Failed to resize to %d %d" , newImageWidth, newImageHeight); |
762 | abort(); |
763 | } |
764 | |
765 | _image = newImage; |
766 | _imageWidth = newImageWidth; |
767 | _imageHeight = newImageHeight; |
768 | |
769 | if (_curY >= _imageHeight) { |
770 | _curY = _height - lineHeight; |
771 | } |
772 | if (_curX >= _imageWidth) { |
773 | _curX = 0; |
774 | } |
775 | } |
776 | _scrollY = 0; |
777 | _width = newWidth; |
778 | _height = newHeight; |
779 | if (!fullscreen) { |
780 | drawBase(false); |
781 | } |
782 | layoutInputs(newWidth, newHeight); |
783 | } |
784 | |
785 | void GraphicScreen::updateFont(int size) { |
786 | setFont(_bold, _italic, size > 0 ? size : _fontSize); |
787 | } |
788 | |
789 | // handles the given escape character. Returns whether the font has changed |
790 | bool GraphicScreen::setGraphicsRendition(const char c, int escValue, int lineHeight) { |
791 | switch (c) { |
792 | case 'K': |
793 | maSetColor(_bg); // \e[K - clear to eol |
794 | maFillRect(_curX, _curY, _width - _curX, lineHeight); |
795 | break; |
796 | case 'G': // move to column |
797 | _curX = escValue * _charWidth; |
798 | break; |
799 | case 's': // save cursor position |
800 | _curYSaved = _curX; |
801 | _curXSaved = _curY; |
802 | break; |
803 | case 'u': // restore cursor position |
804 | _curX = _curYSaved; |
805 | _curY = _curXSaved; |
806 | break; |
807 | case ';': // fallthru |
808 | case 'm': // \e[...m - ANSI terminal |
809 | switch (escValue) { |
810 | case 0: // reset |
811 | reset(_fontSize); |
812 | break; |
813 | case 1: // set bold on |
814 | _bold = true; |
815 | return true; |
816 | case 2: // set faint on |
817 | break; |
818 | case 3: // set italic on |
819 | _italic = true; |
820 | return true; |
821 | case 4: // set underline on |
822 | _underline = true; |
823 | break; |
824 | case 5: // set blink on |
825 | break; |
826 | case 6: // rapid blink on |
827 | break; |
828 | case 7: // reverse video on |
829 | _invert = true; |
830 | break; |
831 | case 8: // conceal on |
832 | break; |
833 | case 21: // set bold off |
834 | _bold = false; |
835 | return true; |
836 | case 23: |
837 | _italic = false; |
838 | return true; |
839 | case 24: // set underline off |
840 | _underline = false; |
841 | break; |
842 | case 27: // reverse video off |
843 | _invert = false; |
844 | break; |
845 | // colors - 30..37 foreground, 40..47 background |
846 | case 30: // set black fg |
847 | _fg = ansiToMosync(0); |
848 | break; |
849 | case 31: // set red fg |
850 | _fg = ansiToMosync(4); |
851 | break; |
852 | case 32: // set green fg |
853 | _fg = ansiToMosync(2); |
854 | break; |
855 | case 33: // set yellow fg |
856 | _fg = ansiToMosync(6); |
857 | break; |
858 | case 34: // set blue fg |
859 | _fg = ansiToMosync(1); |
860 | break; |
861 | case 35: // set magenta fg |
862 | _fg = ansiToMosync(5); |
863 | break; |
864 | case 36: // set cyan fg |
865 | _fg = ansiToMosync(3); |
866 | break; |
867 | case 37: // set white fg |
868 | _fg = ansiToMosync(7); |
869 | break; |
870 | case 40: // set black bg |
871 | _bg = ansiToMosync(0); |
872 | break; |
873 | case 41: // set red bg |
874 | _bg = ansiToMosync(4); |
875 | break; |
876 | case 42: // set green bg |
877 | _bg = ansiToMosync(2); |
878 | break; |
879 | case 43: // set yellow bg |
880 | _bg = ansiToMosync(6); |
881 | break; |
882 | case 44: // set blue bg |
883 | _bg = ansiToMosync(1); |
884 | break; |
885 | case 45: // set magenta bg |
886 | _bg = ansiToMosync(5); |
887 | break; |
888 | case 46: // set cyan bg |
889 | _bg = ansiToMosync(3); |
890 | break; |
891 | case 47: // set white bg |
892 | _bg = ansiToMosync(15); |
893 | break; |
894 | case 48: // subscript on |
895 | break; |
896 | case 49: // superscript |
897 | break; |
898 | }; |
899 | } |
900 | return false; |
901 | } |
902 | |
903 | void GraphicScreen::setPixel(int x, int y, int c) { |
904 | drawInto(); |
905 | maSetColor(ansiToMosync(c)); |
906 | maPlot(x, y); |
907 | } |
908 | |
909 | struct LineShape : Shape { |
910 | LineShape(int x, int y, int w, int h) : Shape(x, y, w, h) {} |
911 | void draw(int ax, int ay, int, int, int) { |
912 | maLine(_x, _y, _width, _height); |
913 | } |
914 | }; |
915 | |
916 | struct RectShape : Shape { |
917 | RectShape(int x, int y, int w, int h) : Shape(x, y, w, h) {} |
918 | void draw(int ax, int ay, int, int, int) { |
919 | int x1 = _x; |
920 | int y1 = _y; |
921 | int x2 = _x + _width; |
922 | int y2 = _y + _width; |
923 | maLine(x1, y1, x2, y1); // top |
924 | maLine(x1, y2, x2, y2); // bottom |
925 | maLine(x1, y1, x1, y2); // left |
926 | maLine(x2, y1, x2, y2); // right |
927 | } |
928 | }; |
929 | |
930 | struct RectFilledShape : Shape { |
931 | RectFilledShape(int x, int y, int w, int h) : Shape(x, y, w, h) {} |
932 | void draw(int ax, int ay, int, int, int) { |
933 | maFillRect(_x, _y, _width, _height); |
934 | } |
935 | }; |
936 | |
937 | // |
938 | // Text based screen with a large scrollback buffer |
939 | // |
940 | TextScreen::TextScreen(int width, int height, int fontSize) : |
941 | Screen(0, 0, width, height, fontSize), |
942 | _over(NULL), |
943 | _inset(0, 0, 0, 0), |
944 | _buffer(NULL), |
945 | _head(0), |
946 | _tail(0), |
947 | _rows(TEXT_ROWS), |
948 | _cols(0) { |
949 | } |
950 | |
951 | TextScreen::~TextScreen() { |
952 | delete[] _buffer; |
953 | } |
954 | |
955 | void TextScreen::calcTab() { |
956 | Row *line = getLine(_head); // pointer to current line |
957 | line->tab(); |
958 | } |
959 | |
960 | bool TextScreen::construct() { |
961 | reset(_fontSize); |
962 | _buffer = new Row[_rows]; |
963 | return (_buffer != NULL); |
964 | } |
965 | |
966 | // |
967 | // clear the screen |
968 | // |
969 | void TextScreen::clear() { |
970 | _head = _tail = _cols = 0; |
971 | getLine(0)->clear(); |
972 | Screen::clear(); |
973 | } |
974 | |
975 | // |
976 | // draw the text |
977 | // |
978 | void TextScreen::drawBase(bool vscroll, bool update) { |
979 | // prepare escape state variables |
980 | bool bold = false; |
981 | bool italic = false; |
982 | bool underline = false; |
983 | bool invert = false; |
984 | int color = DEFAULT_FOREGROUND; |
985 | |
986 | // calculate rows to display |
987 | int = getPageRows(); |
988 | int textRows = getTextRows(); |
989 | int numRows = textRows < pageRows ? textRows : pageRows; |
990 | int firstRow = _tail + (_scrollY / _charHeight); |
991 | int yoffs = _scrollY % _charHeight; // smooth scrolling offset |
992 | |
993 | // prevent drawing beyond available text |
994 | if (numRows > textRows - firstRow) { |
995 | numRows = textRows - firstRow; |
996 | } |
997 | |
998 | if (_over != NULL && _over != this) { |
999 | _over->drawBase(vscroll, false); |
1000 | } |
1001 | |
1002 | // setup the background colour |
1003 | MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN_BUFFER); |
1004 | maSetColor(_bg); |
1005 | maFillRect(_x, _y, _width, _height); |
1006 | maSetColor(color); |
1007 | |
1008 | // draw the visible segments |
1009 | int pageWidth = 0; |
1010 | for (int row = firstRow, rows = 0, py = _y - yoffs; |
1011 | rows < numRows; |
1012 | row++, rows++, py += _charHeight) { |
1013 | Row *line = getLine(row); // next logical row |
1014 | TextSeg *seg = line->_head; |
1015 | int px = (_x + INITXY) - _scrollX; |
1016 | while (seg != NULL) { |
1017 | if (seg->escape(&bold, &italic, &underline, &invert)) { |
1018 | setFont(bold, italic, _fontSize); |
1019 | } else if (seg->isReset()) { |
1020 | reset(_fontSize); |
1021 | bold = false; |
1022 | italic = false; |
1023 | underline = false; |
1024 | invert = false; |
1025 | color = DEFAULT_FOREGROUND; |
1026 | maSetColor(color); |
1027 | } |
1028 | if (seg->_color != NO_COLOR) { |
1029 | color = seg->_color; |
1030 | maSetColor(color); |
1031 | } |
1032 | int width = seg->width(); |
1033 | if (seg->_str) { |
1034 | if (invert) { |
1035 | maSetColor(_fg); |
1036 | maFillRect(px, py, width, _charHeight); |
1037 | maSetColor(_bg); |
1038 | maDrawText(px, py, seg->_str, seg->numChars()); |
1039 | maSetColor(color); |
1040 | } else { |
1041 | maDrawText(px, py, seg->_str, seg->numChars()); |
1042 | } |
1043 | if (underline) { |
1044 | maLine(px, py + _charHeight, width, py + _charHeight); |
1045 | } |
1046 | } |
1047 | px += width; |
1048 | seg = seg->_next; |
1049 | } |
1050 | int rowWidth = line->width(); |
1051 | if (rowWidth > pageWidth) { |
1052 | pageWidth = rowWidth; |
1053 | } |
1054 | } |
1055 | |
1056 | // draw the base components |
1057 | drawOverlay(vscroll); |
1058 | _dirty = 0; |
1059 | maUpdateScreen(); |
1060 | maSetDrawTarget(currentHandle); |
1061 | } |
1062 | |
1063 | void TextScreen::drawLine(int x1, int y1, int x2, int y2) { |
1064 | add(new LineShape(x1, y1, x2, y2)); |
1065 | } |
1066 | |
1067 | void TextScreen::drawRect(int x1, int y1, int x2, int y2) { |
1068 | add(new RectShape(x1, y1, x2, y2)); |
1069 | } |
1070 | |
1071 | void TextScreen::drawRectFilled(int x1, int y1, int x2, int y2) { |
1072 | add(new RectFilledShape(x1, y1, x2 - x1, y2 - y1)); |
1073 | } |
1074 | |
1075 | // |
1076 | // return a pointer to the specified line of the display. |
1077 | // |
1078 | Row *TextScreen::getLine(int pos) { |
1079 | if (pos < 0) { |
1080 | pos += _rows; |
1081 | } |
1082 | if (pos > _rows - 1) { |
1083 | pos -= _rows; |
1084 | } |
1085 | |
1086 | return &_buffer[pos]; |
1087 | } |
1088 | |
1089 | void TextScreen::inset(int x, int y, int w, int h, Screen *over) { |
1090 | _x = over->_x; |
1091 | _y = over->_y; |
1092 | _width = over->_width; |
1093 | _height = over->_height; |
1094 | _inset._x = x; |
1095 | _inset._y = y; |
1096 | _inset._width = w; |
1097 | _inset._height = h; |
1098 | _over = over; |
1099 | resize(_width, _height, 0, 0, 0); |
1100 | } |
1101 | |
1102 | void TextScreen::newLine(int lineHeight) { |
1103 | // scroll by moving logical last line |
1104 | if (getTextRows() == _rows) { |
1105 | _tail = (_tail + 1 >= _rows) ? 0 : _tail + 1; |
1106 | } |
1107 | _head = (_head + 1 >= _rows) ? 0 : _head + 1; |
1108 | |
1109 | // clear the new line |
1110 | Row* line = getLine(_head); |
1111 | line->clear(); |
1112 | |
1113 | lineHeight += _linePadding; |
1114 | _linePadding = 0; |
1115 | |
1116 | _curX = INITXY; |
1117 | _curY += lineHeight; |
1118 | } |
1119 | |
1120 | void TextScreen::resize(int newWidth, int newHeight, int, int, int) { |
1121 | if (_inset._width != 0 && _inset._height != 0) { |
1122 | _x = newWidth * _inset._x / 100; |
1123 | _y = newHeight * _inset._y / 100; |
1124 | _width = (newWidth * _inset._width / 100) - _x; |
1125 | _height = (newHeight * _inset._height / 100) - _y; |
1126 | } else { |
1127 | _width = newWidth; |
1128 | _height = newHeight; |
1129 | } |
1130 | layoutInputs(newWidth, newHeight); |
1131 | } |
1132 | |
1133 | // |
1134 | // Creates a new segment then prints the line |
1135 | // |
1136 | int TextScreen::print(const char *p, int lineHeight, bool allChars) { |
1137 | Row *line = getLine(_head); |
1138 | TextSeg *segment = new TextSeg(); |
1139 | line->append(segment); |
1140 | |
1141 | int numChars = Screen::print(p, lineHeight, true); |
1142 | |
1143 | // Print the next (possible) line of text |
1144 | segment->setText(p, numChars); |
1145 | |
1146 | // remember the maximum line length |
1147 | if (numChars > _cols) { |
1148 | _cols = numChars; |
1149 | } |
1150 | |
1151 | return numChars; |
1152 | } |
1153 | |
1154 | // |
1155 | // performs the ANSI text SGI function. |
1156 | // |
1157 | bool TextScreen::setGraphicsRendition(const char c, int escValue, int lineHeight) { |
1158 | if (c == ';' || c == 'm') { |
1159 | Row *line = getLine(_head); |
1160 | TextSeg *segment = line->next(); |
1161 | |
1162 | if (segment->_flags || segment->_color != NO_COLOR) { |
1163 | // avoid overwriting existing flags |
1164 | segment = new TextSeg(); |
1165 | line->append(segment); |
1166 | } |
1167 | |
1168 | switch (escValue) { |
1169 | case 0: |
1170 | segment->reset(); |
1171 | reset(_fontSize); |
1172 | break; |
1173 | |
1174 | case 1: // Bold on |
1175 | segment->set(TextSeg::BOLD, true); |
1176 | break; |
1177 | |
1178 | case 2: // Faint on |
1179 | segment->set(TextSeg::BOLD, false); |
1180 | break; |
1181 | |
1182 | case 3: // Italic on |
1183 | segment->set(TextSeg::ITALIC, true); |
1184 | break; |
1185 | |
1186 | case 4: // Underscrore |
1187 | segment->set(TextSeg::UNDERLINE, true); |
1188 | break; |
1189 | |
1190 | case 7: // reverse video on |
1191 | segment->set(TextSeg::INVERT, true); |
1192 | break; |
1193 | |
1194 | case 21: // set bold off |
1195 | segment->set(TextSeg::BOLD, false); |
1196 | break; |
1197 | |
1198 | case 23: |
1199 | segment->set(TextSeg::ITALIC, false); |
1200 | break; |
1201 | |
1202 | case 24: // set underline off |
1203 | segment->set(TextSeg::UNDERLINE, false); |
1204 | break; |
1205 | |
1206 | case 27: // reverse video off |
1207 | segment->set(TextSeg::INVERT, false); |
1208 | break; |
1209 | |
1210 | case 30: // Black |
1211 | _fg = segment->_color = ansiToMosync(0); |
1212 | break; |
1213 | |
1214 | case 31: // Red |
1215 | _fg = segment->_color = ansiToMosync(4); |
1216 | break; |
1217 | |
1218 | case 32: // Green |
1219 | _fg = segment->_color = ansiToMosync(2); |
1220 | break; |
1221 | |
1222 | case 33: // Yellow |
1223 | _fg = segment->_color = ansiToMosync(6); |
1224 | break; |
1225 | |
1226 | case 34: // Blue |
1227 | _fg = segment->_color = ansiToMosync(1); |
1228 | break; |
1229 | |
1230 | case 35: // Magenta |
1231 | _fg = segment->_color = ansiToMosync(5); |
1232 | break; |
1233 | |
1234 | case 36: // Cyan |
1235 | _fg = segment->_color = ansiToMosync(3); |
1236 | break; |
1237 | |
1238 | case 37: // White |
1239 | _fg = segment->_color = ansiToMosync(7); |
1240 | break; |
1241 | } |
1242 | } |
1243 | return false; |
1244 | } |
1245 | |
1246 | |