#include "NDSUI.hpp" #include "font.h" #include #include #include #include #include #define SCREEN_BORDER_PADDING 3 #define FONT_PADDING 1 #define INPUT_LINE_HEIGHT (FONT_HEIGHT+FONT_PADDING) #define LOG_LINE_PADDING 5 NDSUI *NDSUI::currentInstance; consteval unsigned NDSUI::calcMaxCharsInLine(unsigned int width) { return width / (FONT_WIDTH + FONT_PADDING) - 1; } consteval unsigned NDSUI::calcMaxLinesInRow(unsigned height) { return height / (FONT_HEIGHT + FONT_PADDING); } void NDSUI::kbCallback(int key) { if (key > 0) currentInstance->handleInput(static_cast(key)); } consteval Box2D NDSUI::calcMessageLogDimensions() { return { {SCREEN_BORDER_PADDING, 0}, {SCREEN_WIDTH-SCREEN_BORDER_PADDING, SCREEN_HEIGHT-(SCREEN_BORDER_PADDING*2)-INPUT_LINE_HEIGHT} }; } void NDSUI::renderMessageLog() { constexpr Box2D boxDims = calcMessageLogDimensions(); // Draw box glBoxFilled(boxDims.topLeft.x, boxDims.topLeft.y, boxDims.bottomRight.x, boxDims.bottomRight.y, RGB15(6, 2, 14)); // Log messages renderer auto renderLine = [this, y = int(boxDims.bottomRight.y - FONT_PADDING - FONT_HEIGHT), boxDims] (std::string_view line) mutable { // Skip empty lines if (line.empty()) return true; // Check if line is bold bool bold = line[0] == '\t'; if (bold) { line = {line.data()+1, line.size()-1}; } // Print line itself print(line, {boxDims.topLeft.x + FONT_PADDING, unsigned(y)}, RGB15(28, 28, 28), bold); // Go to next line y -= FONT_PADDING * LOG_LINE_PADDING + FONT_HEIGHT; // Stop rendering any more lines return y >= 0; }; // Display line by line unsigned lineCount = 0; for (auto message = messages.rbegin(); message != messages.rend(); message++) { const auto& lines = message->getLines(); for (auto line = lines.rbegin(); line != lines.rend(); line++) { if (lineCount >= scrollOff) if (!renderLine(*line)) return; lineCount++; } } } consteval Box2D NDSUI::calcInputLineDimensions() { return { {SCREEN_BORDER_PADDING, SCREEN_HEIGHT-SCREEN_BORDER_PADDING-INPUT_LINE_HEIGHT}, {SCREEN_WIDTH-SCREEN_BORDER_PADDING, SCREEN_HEIGHT-SCREEN_BORDER_PADDING} }; } void NDSUI::renderInputLine() { constexpr Box2D boxDims = calcInputLineDimensions(); constexpr Position2D textTopLeft{boxDims.topLeft.x+FONT_PADDING, boxDims.topLeft.y+FONT_PADDING}; // Draw box glBoxFilled(boxDims.topLeft.x, boxDims.topLeft.y, boxDims.bottomRight.x, boxDims.bottomRight.y, RGB15(13, 8, 3)); // Measure amount of character that fit in constexpr auto maxChars = calcMaxCharsInLine(boxDims.bottomRight.x - boxDims.topLeft.x); // Mease amount of characters that don't fit in unsigned startIdx = 0; if (inputLineBufHead > maxChars) { startIdx = inputLineBufHead - maxChars; } // Get that amount of characters print(&inputLineBuf[startIdx], textTopLeft, RGB15(28, 28, 28)); } bool NDSUI::printChar(const char character, Position2D *topleftpos, const int color, bool bold) { const bool *fontChar = font_get_character(character); int y = 0; int x = 0; unsigned row = 0; unsigned y_start = 0; for (const bool *thispixel = fontChar; ; thispixel++) { if (y == FONT_HEIGHT) { y = 0; x++; } if (x == FONT_WIDTH) { break; } if (*thispixel) { if (row == 0) y_start = y; row++; } else if (row) { if (row >= 3 || boxesRendered < 1000 || ((x&1) || (y&1))) { glBoxFilled(topleftpos->x + x, topleftpos->y + y_start, topleftpos->x + x, topleftpos->y + y - (!bold)*1, color); boxesRendered++; } row = 0; } ++y; } return fontChar != fontBitmapUnk; } void NDSUI::print(std::string_view str, Position2D topleftpos, const int color, bool bold) { for (const char c : str) { printChar(c, &topleftpos, color, bold); topleftpos.x += FONT_WIDTH + FONT_PADDING; } } void NDSUI::handleInput(char c) { //TODO: Check length switch (c) { case '\b': { // Remove character if not at start if (inputLineBufHead != 0) { inputLineBuf[--inputLineBufHead] = '\0'; } } break; case '\n': { if (userInputHandler && inputLineBuf[0]) { auto msg = createLogMessage("User", inputLineBuf.data()); addLogMessage(std::move(msg)); userInputHandler->set(inputLineBuf.data()); resetInputLine(); scrollOff = 0; //userInputHandler = nullptr; } } break; default: { // Add character if not at end if (inputLineBufHead != inputLineBuf.size() - 1) { inputLineBuf[inputLineBufHead] = c; inputLineBuf[++inputLineBufHead] = '\0'; } } } } void NDSUI::scanButtons() { scanKeys(); int keys = keysDownRepeat(); // Scrolling if (scrollOff != 0) { if (keys & KEY_DOWN) { scrollOff -= 1; } else if (keys & KEY_RIGHT) { scrollOff -= 5; } } if (keys & KEY_UP) { scrollOff += 1; } else if (keys & KEY_LEFT) { scrollOff += 5; } } NDSUI::NDSUI() { resetInputLine(); glScreen2D(); auto swkbd = keyboardDemoInit(); swkbd->OnKeyReleased = kbCallback; keyboardShow(); } void NDSUI::run() { currentInstance = this; boxesRendered = 0; // Update buttons scanButtons(); // Update keyboard keyboardUpdate(); // Draw glBegin2D(); { renderInputLine(); renderMessageLog(); } glEnd2D(); // Apply changes glFlush(0); swiWaitForVBlank(); } LineWrappedString NDSUI::createLogMessage(const char *username, std::string_view message) { // Do line-wrapping, create and return Message instance return LineWrappedString("\t "+std::string(username)+"\n"+std::string(message)+"\n", calcMaxCharsInLine(calcMessageLogDimensions().width())); } void LineWrappedString::split(std::string_view str, unsigned maxCharsPerLine) { lines.clear(); const char *currStrStart = str.data(); const char *strEnd = str.data() + str.size(); for (size_t currStrLen = 0; ;) { const char& c = currStrStart[currStrLen]; bool brk = false; bool next = false; bool minusOne = false; if (c == '\n') { next = true; minusOne = true; } else if (&c == strEnd) { next = true; brk = true; minusOne = true; } else if (currStrLen > maxCharsPerLine) { next = true; } if (next) { lines.push_back({currStrStart, currStrLen}); if (brk) break; currStrStart += currStrLen + minusOne; currStrLen = 0; } else { currStrLen++; } } }