void ITextRenderer::reconstruct(unsigned int ID, bool rescanStr) { MUTEXLIB::unique_lock<MUTEXLIB::recursive_mutex> lock(SpriteMutex); if (SpriteData.size() <= ID) SpriteData.resize(ID + 1, std::vector<SpriteAttr*>()); // 配列の拡張 if ((!SpriteData[ID].empty()) && rescanStr) deleteSprite(ID); // 既に存在した場合 if (!StringData[ID]) /* ぬるぽ */ return; /* ガッ */ float chrAdvance = (FontWidth() - FontPadding() * 2) * StringData[ID]->scale * StringData[ID]->width; float cursorPos = 0; if (rescanStr) { for (const auto& k : StringData[ID]->str) { SpriteData[ID].push_back(new SpriteAttr); SpriteData[ID].back()->isFullWidth = fontmap->map(k).first; SpriteData[ID].back()->chr_id = fontmap->map(k).second; spriteRecalc(ID, SpriteData[ID].back(), chrAdvance, cursorPos); if (SpriteData[ID].back()->isFullWidth) cursorPos += 1.0f; else cursorPos += .5f; } } else { for (const auto& k : SpriteData[ID]) { spriteRecalc(ID, k, chrAdvance, cursorPos); if (k->isFullWidth) cursorPos += 1.0f; else cursorPos += .5f; } } }
/* レンダリング */ void ITextRenderer::Render() { MUTEXLIB::unique_lock<MUTEXLIB::recursive_mutex> lock(SpriteMutex); for (const auto& i : SpriteData) { for (const auto& k : i) { if (!k) continue; RECT rect = { static_cast<int32_t>((k->chr_id % FontCols()) * FontWidth()), static_cast<int32_t>((k->chr_id / FontCols()) * FontBaseSize()), static_cast<int32_t>((k->chr_id % FontCols() + 1) * FontWidth()), static_cast<int32_t>((k->chr_id / FontCols() + 1) * FontBaseSize()), }; SpriteRenderer::instantiate(myDevice)->ShowSprite( font, k->X, k->Y, FontWidth(), FontBaseSize(), k->color, &rect, 0, 0, &(k->matrix)); } } }
/* Resize the text window for a terminal screen, modifying the * appropriate WM_SIZE_HINTS and taking advantage of bit gravity. */ void DoResizeScreen(XtermWidget xw) { TScreen *screen = TScreenOf(xw); int border = 2 * screen->border; int min_wide = border + screen->fullVwin.sb_info.width; int min_high = border; XtGeometryResult geomreqresult; Dimension reqWidth, reqHeight, repWidth, repHeight; #ifndef NO_ACTIVE_ICON VTwin *saveWin = WhichVWin(screen); /* all units here want to be in the normal font units */ WhichVWin(screen) = &screen->fullVwin; #endif /* NO_ACTIVE_ICON */ /* * I'm going to try to explain, as I understand it, why we * have to do XGetWMNormalHints and XSetWMNormalHints here, * although I can't guarantee that I've got it right. * * In a correctly written toolkit program, the Shell widget * parses the user supplied geometry argument. However, * because of the way xterm does things, the VT100 widget does * the parsing of the geometry option, not the Shell widget. * The result of this is that the Shell widget doesn't set the * correct window manager hints, and doesn't know that the * user has specified a geometry. * * The XtVaSetValues call below tells the Shell widget to * change its hints. However, since it's confused about the * hints to begin with, it doesn't get them all right when it * does the SetValues -- it undoes some of what the VT100 * widget did when it originally set the hints. * * To fix this, we do the following: * * 1. Get the sizehints directly from the window, going around * the (confused) shell widget. * 2. Call XtVaSetValues to let the shell widget know which * hints have changed. Note that this may not even be * necessary, since we're going to right ahead after that * and set the hints ourselves, but it's good to put it * here anyway, so that when we finally do fix the code so * that the Shell does the right thing with hints, we * already have the XtVaSetValues in place. * 3. We set the sizehints directly, this fixing up whatever * damage was done by the Shell widget during the * XtVaSetValues. * * Gross, huh? * * The correct fix is to redo VTRealize, VTInitialize and * VTSetValues so that font processing happens early enough to * give back responsibility for the size hints to the Shell. * * Someday, we hope to have time to do this. Someday, we hope * to have time to completely rewrite xterm. */ TRACE(("DoResizeScreen\n")); #if 1 /* ndef nothack */ /* * NOTE: the hints and the XtVaSetValues() must match. */ TRACE(("%s@%d -- ", __FILE__, __LINE__)); TRACE_WM_HINTS(xw); getXtermSizeHints(xw); xtermSizeHints(xw, ScrollbarWidth(screen)); /* These are obsolete, but old clients may use them */ xw->hints.width = MaxCols(screen) * FontWidth(screen) + xw->hints.min_width; xw->hints.height = MaxRows(screen) * FontHeight(screen) + xw->hints.min_height; #if OPT_MAXIMIZE /* assure single-increment resize for fullscreen */ if (xw->work.ewmh[0].mode) { xw->hints.width_inc = 1; xw->hints.height_inc = 1; } #endif /* OPT_MAXIMIZE */ #endif XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints); reqWidth = (Dimension) (MaxCols(screen) * FontWidth(screen) + min_wide); reqHeight = (Dimension) (MaxRows(screen) * FontHeight(screen) + min_high); #if OPT_MAXIMIZE /* compensate for fullscreen mode */ if (xw->work.ewmh[0].mode) { Screen *xscreen = DefaultScreenOfDisplay(xw->screen.display); reqWidth = (Dimension) WidthOfScreen(xscreen); reqHeight = (Dimension) HeightOfScreen(xscreen); ScreenResize(xw, reqWidth, reqHeight, &xw->flags); } #endif /* OPT_MAXIMIZE */ TRACE(("...requesting screensize chars %dx%d, pixels %dx%d\n", MaxRows(screen), MaxCols(screen), reqHeight, reqWidth)); geomreqresult = REQ_RESIZE((Widget) xw, reqWidth, reqHeight, &repWidth, &repHeight); if (geomreqresult == XtGeometryAlmost) { TRACE(("...almost, retry screensize %dx%d\n", repHeight, repWidth)); geomreqresult = REQ_RESIZE((Widget) xw, repWidth, repHeight, NULL, NULL); } if (geomreqresult != XtGeometryYes) { /* The resize wasn't successful, so we might need to adjust our idea of how large the screen is. */ TRACE(("...still no (%d) - resize the core-class\n", geomreqresult)); xw->core.widget_class->core_class.resize((Widget) xw); } #if 1 /* ndef nothack */ /* * XtMakeResizeRequest() has the undesirable side-effect of clearing * the window manager's hints, even on a failed request. This would * presumably be fixed if the shell did its own work. */ if (xw->hints.flags && repHeight && repWidth) { xw->hints.height = repHeight; xw->hints.width = repWidth; TRACE_HINTS(&xw->hints); XSetWMNormalHints(screen->display, VShellWindow(xw), &xw->hints); } #endif XSync(screen->display, False); /* synchronize */ if (xtermAppPending()) xevents(); #ifndef NO_ACTIVE_ICON WhichVWin(screen) = saveWin; #endif /* NO_ACTIVE_ICON */ }
unsigned ITextRenderer::strWidthByPix(const std::string& str) { return strWidthByCols(str) * (FontWidth() - FontPadding() * 2) / 2; }
/* * Interpret sixel graphics sequences. * * Resources: * http://en.wikipedia.org/wiki/Sixel * http://vt100.net/docs/vt3xx-gp/chapter14.html * ftp://ftp.cs.utk.edu/pub/shuford/terminal/sixel_graphics_news.txt * ftp://ftp.cs.utk.edu/pub/shuford/terminal/all_about_sixels.txt */ void parse_sixel(XtermWidget xw, ANSI *params, char const *string) { TScreen *screen = TScreenOf(xw); Graphic *graphic; SixelContext context; Char ch; switch (screen->terminal_id) { case 240: case 241: case 330: case 340: context.aspect_vertical = 2; context.aspect_horizontal = 1; break; case 382: context.aspect_vertical = 1; context.aspect_horizontal = 1; break; default: context.aspect_vertical = 2; context.aspect_horizontal = 1; break; } context.declared_width = 0; context.declared_height = 0; context.row = 0; context.col = 0; /* default isn't white on the VT240, but not sure what it is */ context.current_register = 3; /* FIXME: using green, but not sure what it should be */ if (xw->keyboard.flags & MODE_DECSDM) { TRACE(("sixel scrolling enabled: inline positioning for graphic at %d,%d\n", screen->cur_row, screen->cur_col)); graphic = get_new_graphic(xw, screen->cur_row, screen->cur_col, 0U); } else { TRACE(("sixel scrolling disabled: inline positioning for graphic at %d,%d\n", 0, 0)); graphic = get_new_graphic(xw, 0, 0, 0U); } { int Pmacro = params->a_param[0]; int Pbgmode = params->a_param[1]; int Phgrid = params->a_param[2]; int Pan = params->a_param[3]; int Pad = params->a_param[4]; int Ph = params->a_param[5]; int Pv = params->a_param[6]; (void) Phgrid; TRACE(("sixel bitmap graphics sequence: params=%d (Pmacro=%d Pbgmode=%d Phgrid=%d) scroll_amt=%d\n", params->a_nparam, Pmacro, Pbgmode, Phgrid, screen->scroll_amt)); switch (params->a_nparam) { case 7: if (Pan == 0 || Pad == 0) { TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad)); return; } context.aspect_vertical = Pan; context.aspect_horizontal = Pad; if (Ph == 0 || Pv == 0) { TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n", Ph, Pv)); return; } if (Ph > graphic->max_width || Pv > graphic->max_height) { TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n", Ph, Pv)); return; } context.declared_width = Ph; context.declared_height = Pv; if (context.declared_width > graphic->actual_width) { graphic->actual_width = context.declared_width; } if (context.declared_height > graphic->actual_height) { graphic->actual_height = context.declared_height; } break; case 3: case 2: case 1: switch (Pmacro) { case 0: /* keep default aspect settings */ break; case 1: case 5: case 6: context.aspect_vertical = 2; context.aspect_horizontal = 1; break; case 2: context.aspect_vertical = 5; context.aspect_horizontal = 1; break; case 3: case 4: context.aspect_vertical = 3; context.aspect_horizontal = 1; break; case 7: case 8: case 9: context.aspect_vertical = 1; context.aspect_horizontal = 1; break; default: TRACE(("DATA_ERROR: unknown sixel macro mode parameter\n")); return; } break; case 0: break; default: TRACE(("DATA_ERROR: unexpected parameter count (found %d)\n", params->a_nparam)); return; } if (Pbgmode == 1) { context.background = COLOR_HOLE; } else { /* FIXME: is the default background register always zero? what about in light background mode? */ context.background = 0; } /* Ignore the grid parameter because it seems only printers paid attention to it. * The VT3xx was always 0.0195 cm. */ } update_sixel_aspect(&context, graphic); for (;;) { ch = CharOf(*string); if (ch == '\0') break; if (ch >= 0x3f && ch <= 0x7e) { int sixel = ch - 0x3f; TRACE(("sixel=%x (%c)\n", sixel, (char) ch)); if (!graphic->valid) { init_sixel_background(graphic, &context); graphic->valid = 1; } set_sixel(graphic, &context, sixel); context.col++; } else if (ch == '$') { /* DECGCR */ /* ignore DECCRNLM in sixel mode */ TRACE(("sixel CR\n")); context.col = 0; } else if (ch == '-') { /* DECGNL */ int scroll_lines; TRACE(("sixel NL\n")); scroll_lines = 0; while (graphic->charrow - scroll_lines + (((context.row + 6) * graphic->pixh + FontHeight(screen) - 1) / FontHeight(screen)) > screen->bot_marg) { scroll_lines++; } context.col = 0; context.row += 6; /* If we hit the bottom margin on the graphics page (well, we just use the * text margin for now), the behavior is to either scroll or to discard * the remainder of the graphic depending on this setting. */ if (scroll_lines > 0) { if (xw->keyboard.flags & MODE_DECSDM) { Display *display = screen->display; xtermScroll(xw, scroll_lines); XSync(display, False); TRACE(("graphic scrolled the screen %d lines. screen->scroll_amt=%d screen->topline=%d, now starting row is %d\n", scroll_lines, screen->scroll_amt, screen->topline, graphic->charrow)); } else { break; } } } else if (ch == '!') { /* DECGRI */ int Pcount; const char *start; int sixel; int i; start = ++string; for (;;) { ch = CharOf(*string); if (ch != '0' && ch != '1' && ch != '2' && ch != '3' && ch != '4' && ch != '5' && ch != '6' && ch != '7' && ch != '8' && ch != '9' && ch != ' ' && ch != '\r' && ch != '\n') break; string++; } if (ch == '\0') { TRACE(("DATA_ERROR: sixel data string terminated in the middle of a repeat operator\n")); return; } if (string == start) { TRACE(("DATA_ERROR: sixel data string contains a repeat operator with empty count\n")); return; } Pcount = atoi(start); sixel = ch - 0x3f; TRACE(("sixel repeat operator: sixel=%d (%c), count=%d\n", sixel, (char) ch, Pcount)); if (!graphic->valid) { init_sixel_background(graphic, &context); graphic->valid = 1; } for (i = 0; i < Pcount; i++) { set_sixel(graphic, &context, sixel); context.col++; } } else if (ch == '#') { /* DECGCI */ ANSI color_params; int Pregister; parse_prefixedtype_params(&color_params, &string); Pregister = color_params.a_param[0]; if (Pregister >= (int) graphic->valid_registers) { TRACE(("DATA_WARNING: sixel color operator uses out-of-range register %d\n", Pregister)); /* FIXME: supposedly the DEC terminals wrapped register indicies -- verify */ while (Pregister >= (int) graphic->valid_registers) Pregister -= (int) graphic->valid_registers; TRACE(("DATA_WARNING: converted to %d\n", Pregister)); } if (color_params.a_nparam > 2 && color_params.a_nparam <= 5) { int Pspace = color_params.a_param[1]; int Pc1 = color_params.a_param[2]; int Pc2 = color_params.a_param[3]; int Pc3 = color_params.a_param[4]; short r, g, b; TRACE(("sixel set color register=%d space=%d color=[%d,%d,%d] (nparams=%d)\n", Pregister, Pspace, Pc1, Pc2, Pc3, color_params.a_nparam)); switch (Pspace) { case 1: /* HLS */ if (Pc1 > 360 || Pc2 > 100 || Pc3 > 100) { TRACE(("DATA_ERROR: sixel set color operator uses out-of-range HLS color coordinates %d,%d,%d\n", Pc1, Pc2, Pc3)); return; } hls2rgb(Pc1, Pc2, Pc3, &r, &g, &b); break; case 2: /* RGB */ if (Pc1 > 100 || Pc2 > 100 || Pc3 > 100) { TRACE(("DATA_ERROR: sixel set color operator uses out-of-range RGB color coordinates %d,%d,%d\n", Pc1, Pc2, Pc3)); return; } r = (short) Pc1; g = (short) Pc2; b = (short) Pc3; break; default: /* unknown */ TRACE(("DATA_ERROR: sixel set color operator uses unknown color space %d\n", Pspace)); return; } update_color_register(graphic, (RegisterNum) Pregister, r, g, b); } else if (color_params.a_nparam == 1) { TRACE(("sixel switch to color register=%d (nparams=%d)\n", Pregister, color_params.a_nparam)); context.current_register = (RegisterNum) Pregister; } else { TRACE(("DATA_ERROR: sixel switch color operator with unexpected parameter count (nparams=%d)\n", color_params.a_nparam)); return; } continue; } else if (ch == '"') /* DECGRA */ { ANSI raster_params; parse_prefixedtype_params(&raster_params, &string); if (raster_params.a_nparam < 2) { TRACE(("DATA_ERROR: sixel raster attribute operator with incomplete parameters (found %d, expected 2 or 4)\n", raster_params.a_nparam)); return; } { int Pan = raster_params.a_param[0]; int Pad = raster_params.a_param[1]; TRACE(("sixel raster attribute with h:w=%d:%d\n", Pan, Pad)); if (Pan == 0 || Pad == 0) { TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad)); return; } context.aspect_vertical = Pan; context.aspect_horizontal = Pad; update_sixel_aspect(&context, graphic); } if (raster_params.a_nparam >= 4) { int Ph = raster_params.a_param[2]; int Pv = raster_params.a_param[3]; TRACE(("sixel raster attribute with h=%d v=%d\n", Ph, Pv)); if (Ph == 0 || Pv == 0) { TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n", Ph, Pv)); return; } if (Ph > graphic->max_width || Pv > graphic->max_height) { TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n", Ph, Pv)); return; } context.declared_width = Ph; context.declared_height = Pv; if (context.declared_width > graphic->actual_width) { graphic->actual_width = context.declared_width; } if (context.declared_height > graphic->actual_height) { graphic->actual_height = context.declared_height; } } continue; } else if (ch == ' ' || ch == '\r' || ch == '\n') { /* EMPTY */ ; } else { TRACE(("DATA_ERROR: unknown sixel command %04x (%c)\n", (int) ch, ch)); } string++; } /* update the screen */ if (screen->scroll_amt) FlushScroll(xw); if (xw->keyboard.flags & MODE_DECSDM) { int new_row, new_col; if (screen->sixel_scrolls_right) { new_row = (graphic->charrow + (((graphic->actual_height * graphic->pixh) + FontHeight(screen) - 1) / FontHeight(screen)) - 1); new_col = (graphic->charcol + (((graphic->actual_width * graphic->pixw) + FontWidth(screen) - 1) / FontWidth(screen))); } else { /* FIXME: At least of the VT382 the vertical position appears to be * truncated (rounded toward zero after converting to character row. * This code rounds up, which seems more useful, but it would be * better to be compatible. Verify this is true on a VT3[34]0 as * well. */ new_row = (graphic->charrow + (((graphic->actual_height * graphic->pixh) + FontHeight(screen) - 1) / FontHeight(screen))); new_col = 0; } TRACE(("setting text position after %dx%d graphic starting on row=%d col=%d: cursor new_row=%d new_col=%d\n", graphic->actual_width * graphic->pixw, graphic->actual_height * graphic->pixh, graphic->charrow, graphic->charcol, new_row, new_col)); if (new_col > screen->rgt_marg) { new_col = screen->lft_marg; new_row++; TRACE(("column past left margin, overriding to row=%d col=%d\n", new_row, new_col)); } while (new_row > screen->bot_marg) { xtermScroll(xw, 1); new_row--; TRACE(("bottom row was past screen. new start row=%d, cursor row=%d\n", graphic->charrow, new_row)); } if (new_row < 0) { TRACE(("new row is going to be negative (%d)!", new_row)); /* FIXME: this was triggering, now it isn't */ goto finis; } set_cur_row(screen, new_row); set_cur_col(screen, new_col <= screen->rgt_marg ? new_col : screen->rgt_marg); } finis: refresh_modified_displayed_graphics(screen); TRACE(("DONE successfully parsed sixel data\n")); dump_graphic(graphic); }
static void ResizeScreen(XtermWidget xw,int min_width,int min_height ) { register TScreen *screen = &xw->screen; #ifndef nothack XSizeHints sizehints; long supp; #endif XtGeometryResult geomreqresult; Dimension reqWidth, reqHeight, repWidth, repHeight; /* * I'm going to try to explain, as I understand it, why we * have to do XGetWMNormalHints and XSetWMNormalHints here, * although I can't guarantee that I've got it right. * * In a correctly written toolkit program, the Shell widget * parses the user supplied geometry argument. However, * because of the way xterm does things, the VT100 widget does * the parsing of the geometry option, not the Shell widget. * The result of this is that the Shell widget doesn't set the * correct window manager hints, and doesn't know that the * user has specified a geometry. * * The XtVaSetValues call below tells the Shell widget to * change its hints. However, since it's confused about the * hints to begin with, it doesn't get them all right when it * does the SetValues -- it undoes some of what the VT100 * widget did when it originally set the hints. * * To fix this, we do the following: * * 1. Get the sizehints directly from the window, going around * the (confused) shell widget. * 2. Call XtVaSetValues to let the shell widget know which * hints have changed. Note that this may not even be * necessary, since we're going to right ahead after that * and set the hints ourselves, but it's good to put it * here anyway, so that when we finally do fix the code so * that the Shell does the right thing with hints, we * already have the XtVaSetValues in place. * 3. We set the sizehints directly, this fixing up whatever * damage was done by the Shell widget during the * XtVaSetValues. * * Gross, huh? * * The correct fix is to redo VTRealize, VTInitialize and * VTSetValues so that font processing happens early enough to * give back responsibility for the size hints to the Shell. * * Someday, we hope to have time to do this. Someday, we hope * to have time to completely rewrite xterm. */ #ifndef nothack /* * NOTE: If you change the way any of the hints are calculated * below, make sure you change the calculation both in the * sizehints assignments and in the XtVaSetValues. */ if (! XGetWMNormalHints(screen->display, XtWindow(XtParent(xw)), &sizehints, &supp)) sizehints.flags = 0; sizehints.base_width = min_width; sizehints.base_height = min_height; sizehints.width_inc = FontWidth(screen); sizehints.height_inc = FontHeight(screen); sizehints.min_width = sizehints.base_width + sizehints.width_inc; sizehints.min_height = sizehints.base_height + sizehints.height_inc; sizehints.flags |= (PBaseSize|PMinSize|PResizeInc); /* These are obsolete, but old clients may use them */ sizehints.width = (screen->max_col + 1) * FontWidth(screen) + min_width; sizehints.height = (screen->max_row + 1) * FontHeight(screen) + min_height; #endif /* * Note: width and height are not set here because they are * obsolete. */ XtVaSetValues(XtParent(xw), XtNbaseWidth, min_width, XtNbaseHeight, min_height, XtNwidthInc, FontWidth(screen), XtNheightInc, FontHeight(screen), XtNminWidth, min_width + FontWidth(screen), XtNminHeight, min_height + FontHeight(screen), NULL); reqWidth = (screen->max_col + 1) * FontWidth(screen) + min_width; reqHeight = FontHeight(screen) * (screen->max_row + 1) + min_height; geomreqresult = XtMakeResizeRequest ((Widget)xw, reqWidth, reqHeight, &repWidth, &repHeight); if (geomreqresult == XtGeometryAlmost) { geomreqresult = XtMakeResizeRequest ((Widget)xw, repWidth, repHeight, NULL, NULL); } #ifndef nothack XSetWMNormalHints(screen->display, XtWindow(XtParent(xw)), &sizehints); #endif }
/* // - ScrollDisplay(const char *s,uint8_t speed) // - Scrolls the string referenced in the pointer // - '*s' from right to left at a given 'speed'. // - The text is scrolled off screen leaving // - the disply blank when finished // - Returns - nothing */ void ScrollDisplay(char *s,uint8_t speed) { LEDArrayBlank(); int8_t offset = 0 , next_offset = 0; uint8_t is_started = 0; uint8_t y=0; char x=' '; uint8_t i=0; if(speed==0) speed=90; // begin loop and continue until nul character at end of string while(s[i] != '\0'){ // have we read a character? if(is_started) { // if so, place and shift the character until it is clear of column (COLS-1) while(next_offset>(COLS-1)){ delay_ms(speed); // shift the display one place LEDArrayLeftShift(); // check the end of the currently displayed characters if it's greater than zero, decrement // both the offset (character display position), and next_offset(character end position) if(next_offset > 0) { offset -= 1; next_offset -= 1; } // display the character at the new position FontDisplay(x, offset); } }else{ // if not, set offset to # of columns -1 ((COLS-1)) offset = COLS-1; } // read the next character in the string x = s[i++]; // if we have already started, set the current display position to where the last character ended if(is_started) offset = next_offset; // display the character FontDisplay(x, offset); // create the new character end position next_offset = offset + FontWidth(x)+1; // set flag to show we have been through the loop is_started = 1; if( KEY1 ) return; } // Process the last character. This is neccessary since the while bails as soon as it reads // the null character. At that point, the last read character has not yet shifted into full // view. The following while loop shifts the final string character into full view. while(next_offset>(COLS-1)){ delay_ms(speed); LEDArrayLeftShift(); if(next_offset > 0) { offset -= 1; next_offset -= 1; } FontDisplay(x, offset); if( KEY1 ) return; } delay_ms(speed); // this final loop shifts the display all the way off. for(y=0;y<COLS;y++){ LEDArrayLeftShift(); delay_ms(speed); if( KEY1 ) return; } return; }