1
0
Fork 0
mirror of https://gitlab.com/niansa/llama_nds.git synced 2025-03-06 20:53:28 +01:00
llama_nds/NDSUI.cpp
2023-04-08 18:43:23 +02:00

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++;
}
}
}