mirror of
https://gitlab.com/niansa/llama_nds.git
synced 2025-03-06 20:53:28 +01:00
240 lines
6.9 KiB
C++
240 lines
6.9 KiB
C++
#include "NDSUI.hpp"
|
|
#include "font.h"
|
|
|
|
#include <exception>
|
|
#include <gl2d.h>
|
|
#include <nds.h>
|
|
#include <nds/touch.h>
|
|
#include <nds/input.h>
|
|
|
|
#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<char>(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) {
|
|
glBoxFilled(topleftpos->x + x, topleftpos->y + y_start, topleftpos->x + x, topleftpos->y + y - (!bold)*1, color);
|
|
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;
|
|
// 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++;
|
|
}
|
|
}
|
|
}
|