// console.cpp - GUIConsole object #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../twcommon.h" #include "../minuitwrp/minui.h" } #include "rapidxml.hpp" #include "objects.hpp" static std::vector gConsole; static std::vector gConsoleColor; static FILE* ors_file; extern "C" void __gui_print(const char *color, char *buf) { char *start, *next; if (buf[0] == '\n' && strlen(buf) < 2) { // This prevents the double lines bug seen in the console during zip installs return; } for (start = next = buf; *next != '\0';) { if (*next == '\n') { *next = '\0'; gConsole.push_back(start); gConsoleColor.push_back(color); start = ++next; } else ++next; } // The text after last \n (or whole string if there is no \n) if(*start) { gConsole.push_back(start); gConsoleColor.push_back(color); } if (ors_file) { fprintf(ors_file, "%s\n", buf); fflush(ors_file); } } extern "C" void gui_print(const char *fmt, ...) { char buf[512]; // We're going to limit a single request to 512 bytes va_list ap; va_start(ap, fmt); vsnprintf(buf, 512, fmt, ap); va_end(ap); fputs(buf, stdout); __gui_print("normal", buf); return; } extern "C" void gui_print_color(const char *color, const char *fmt, ...) { char buf[512]; // We're going to limit a single request to 512 bytes va_list ap; va_start(ap, fmt); vsnprintf(buf, 512, fmt, ap); va_end(ap); fputs(buf, stdout); __gui_print(color, buf); return; } extern "C" void gui_set_FILE(FILE* f) { ors_file = f; } GUIConsole::GUIConsole(xml_node<>* node) : GUIObject(node) { xml_attribute<>* attr; xml_node<>* child; mFont = NULL; mCurrentLine = -1; memset(&mForegroundColor, 255, sizeof(COLOR)); memset(&mBackgroundColor, 0, sizeof(COLOR)); mBackgroundColor.alpha = 255; memset(&mScrollColor, 0x08, sizeof(COLOR)); mScrollColor.alpha = 255; mLastCount = 0; mSlideout = 0; RenderCount = 0; mSlideoutState = hidden; mRender = true; mRenderX = 0; mRenderY = 0; mRenderW = gr_fb_width(); mRenderH = gr_fb_height(); if (!node) { mSlideoutX = 0; mSlideoutY = 0; mSlideoutW = 0; mSlideoutH = 0; mConsoleX = 0; mConsoleY = 0; mConsoleW = gr_fb_width(); mConsoleH = gr_fb_height(); } else { child = node->first_node("font"); if (child) { attr = child->first_attribute("resource"); if (attr) mFont = PageManager::FindResource(attr->value()); } child = node->first_node("color"); if (child) { attr = child->first_attribute("foreground"); if (attr) { std::string color = attr->value(); ConvertStrToColor(color, &mForegroundColor); } attr = child->first_attribute("background"); if (attr) { std::string color = attr->value(); ConvertStrToColor(color, &mBackgroundColor); } attr = child->first_attribute("scroll"); if (attr) { std::string color = attr->value(); ConvertStrToColor(color, &mScrollColor); } } // Load the placement LoadPlacement(node->first_node("placement"), &mConsoleX, &mConsoleY, &mConsoleW, &mConsoleH); child = node->first_node("slideout"); if (child) { mSlideout = 1; LoadPlacement(child, &mSlideoutX, &mSlideoutY); attr = child->first_attribute("resource"); if (attr) mSlideoutImage = PageManager::FindResource(attr->value()); if (mSlideoutImage && mSlideoutImage->GetResource()) { mSlideoutW = gr_get_width(mSlideoutImage->GetResource()); mSlideoutH = gr_get_height(mSlideoutImage->GetResource()); } } } mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH); SetRenderPos(mConsoleX, mConsoleY); return; } int GUIConsole::RenderSlideout(void) { if (!mSlideoutImage || !mSlideoutImage->GetResource()) return -1; gr_blit(mSlideoutImage->GetResource(), 0, 0, mSlideoutW, mSlideoutH, mSlideoutX, mSlideoutY); return 0; } int GUIConsole::RenderConsole(void) { void* fontResource = NULL; if (mFont) fontResource = mFont->GetResource(); // We fill the background gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255); gr_fill(mConsoleX, mConsoleY, mConsoleW, mConsoleH); gr_color(mScrollColor.red, mScrollColor.green, mScrollColor.blue, mScrollColor.alpha); gr_fill(mConsoleX + (mConsoleW * 9 / 10), mConsoleY, (mConsoleW / 10), mConsoleH); // Don't try to continue to render without data int prevCount = mLastCount; mLastCount = gConsole.size(); mRender = false; if (mLastCount == 0) return (mSlideout ? RenderSlideout() : 0); // Due to word wrap, figure out what / how the newly added text needs to be added to the render vector that is word wrapped // Note, that multiple consoles on different GUI pages may be different widths or use different fonts, so the word wrapping // may different in different console windows for (int i = prevCount; i < mLastCount; i++) { string curr_line = gConsole[i]; string curr_color = gConsoleColor[i]; int line_char_width; for(;;) { line_char_width = gr_maxExW(curr_line.c_str(), fontResource, mConsoleW); if (line_char_width < curr_line.size()) { rConsole.push_back(curr_line.substr(0, line_char_width)); rConsoleColor.push_back(curr_color); curr_line = curr_line.substr(line_char_width); } else { rConsole.push_back(curr_line); rConsoleColor.push_back(curr_color); break; } } } RenderCount = rConsole.size(); // Find the start point int start; int curLine = mCurrentLine; // Thread-safing (Another thread updates this value) if (curLine == -1) { start = RenderCount - mMaxRows; } else { if (curLine > (int) RenderCount) curLine = (int) RenderCount; if ((int) mMaxRows > curLine) curLine = (int) mMaxRows; start = curLine - mMaxRows; } unsigned int line; for (line = 0; line < mMaxRows; line++) { if ((start + (int) line) >= 0 && (start + (int) line) < (int) RenderCount) { if (rConsoleColor[start + line] == "normal") { gr_color(mForegroundColor.red, mForegroundColor.green, mForegroundColor.blue, mForegroundColor.alpha); } else { COLOR mFontColor; std::string color = rConsoleColor[start + line]; ConvertStrToColor(color, &mFontColor); mFontColor.alpha = 255; gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha); } gr_textExW(mConsoleX, mStartY + (line * mFontHeight), rConsole[start + line].c_str(), fontResource, mConsoleW + mConsoleX); } } return (mSlideout ? RenderSlideout() : 0); } int GUIConsole::Render(void) { if(!isConditionTrue()) return 0; if (mSlideout && mSlideoutState == hidden) return RenderSlideout(); return RenderConsole(); } int GUIConsole::Update(void) { if(!isConditionTrue()) return 0; if (mSlideout && mSlideoutState != visible) { if (mSlideoutState == hidden) return 0; if (mSlideoutState == request_hide) mSlideoutState = hidden; if (mSlideoutState == request_show) mSlideoutState = visible; // Any time we activate the slider, we reset the position mCurrentLine = -1; return 2; } if (mCurrentLine == -1 && mLastCount != gConsole.size()) { // We can use Render, and return for just a flip Render(); return 2; } else if (mRender) { // They're still touching, so re-render Render(); return 2; } return 0; } int GUIConsole::SetRenderPos(int x, int y, int w, int h) { // Adjust the stub position accordingly mSlideoutX += (x - mConsoleX); mSlideoutY += (y - mConsoleY); mConsoleX = x; mConsoleY = y; if (w || h) { mConsoleW = w; mConsoleH = h; } // Calculate the max rows mMaxRows = mConsoleH / mFontHeight; // Adjust so we always fit to bottom mStartY = mConsoleY + (mConsoleH % mFontHeight); return 0; } // IsInRegion - Checks if the request is handled by this object // Return 1 if this object handles the request, 0 if not int GUIConsole::IsInRegion(int x, int y) { if (mSlideout) { // Check if they tapped the slideout button if (x >= mSlideoutX && x <= mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) return 1; // If we're only rendering the slideout, bail now if (mSlideoutState == hidden) return 0; } return (x < mConsoleX || x >= mConsoleX + mConsoleW || y < mConsoleY || y >= mConsoleY + mConsoleH) ? 0 : 1; } // NotifyTouch - Notify of a touch event // Return 0 on success, >0 to ignore remainder of touch, and <0 on error int GUIConsole::NotifyTouch(TOUCH_STATE state, int x, int y) { if(!isConditionTrue()) return -1; if (mSlideout && mSlideoutState == hidden) { if (state == TOUCH_START) { mSlideoutState = request_show; return 1; } } else if (mSlideout && mSlideoutState == visible) { // Are we sliding it back in? if (state == TOUCH_START && x > mSlideoutX && x < (mSlideoutX + mSlideoutW) && y > mSlideoutY && y < (mSlideoutY + mSlideoutH)) { mSlideoutState = request_hide; return 1; } } // If we don't have enough lines to scroll, throw this away. if (RenderCount < mMaxRows) return 1; // We are scrolling!!! switch (state) { case TOUCH_START: mLastTouchX = x; mLastTouchY = y; break; case TOUCH_DRAG: if (x < mConsoleX || x > mConsoleX + mConsoleW || y < mConsoleY || y > mConsoleY + mConsoleH) break; // touch is outside of the console area -- do nothing if (y > mLastTouchY + mFontHeight) { while (y > mLastTouchY + mFontHeight) { if (mCurrentLine == -1) mCurrentLine = RenderCount - 1; else if (mCurrentLine > mMaxRows) mCurrentLine--; mLastTouchY += mFontHeight; } mRender = true; } else if (y < mLastTouchY - mFontHeight) { while (y < mLastTouchY - mFontHeight) { if (mCurrentLine >= 0) mCurrentLine++; mLastTouchY -= mFontHeight; } if (mCurrentLine >= (int) RenderCount) mCurrentLine = -1; mRender = true; } break; case TOUCH_RELEASE: mLastTouchY = -1; case TOUCH_REPEAT: case TOUCH_HOLD: break; } return 0; }