diff --git a/Makefile b/Makefile index 541054d..b19fab7 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ INCLUDES := inc EXEFS_SRC := exefs_src APP_TITLE := JKSV APP_AUTHOR := JK_ -APP_VERSION := 06/29/2018 +APP_VERSION := 07/01/2018 ROMFS := romfs #--------------------------------------------------------------------------------- @@ -56,7 +56,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := `freetype-config --libs` -lpng +LIBS := `freetype-config --libs` -lpng -ljpeg #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/README.MD b/README.MD index ba02e05..e106ce3 100644 --- a/README.MD +++ b/README.MD @@ -7,7 +7,7 @@ JKSV for Switch. Mostly to get used to libnx. Reuses a lot of code of JKSM for 3 # Building: 1. Requires [devkitPro](https://devkitpro.org/) devkitA64 2. Requires switch-freetype -3. libpng +3. libpng and libjpeg-turbo # Credits and Thanks: * [shared-font](https://github.com/switchbrew/switch-portlibs-examples) example by yellows8 diff --git a/inc/data.h b/inc/data.h index 1e6c0ba..0819b6f 100644 --- a/inc/data.h +++ b/inc/data.h @@ -6,12 +6,32 @@ #include #include +#include "gfx.h" + namespace data { extern bool sysSave, forceMountable; //Loads user + title info void loadDataInfo(); + void exit(); + + //Class to help not load the same icons over and over + class icn + { + public: + void load(const uint64_t& _id, const uint8_t *jpegData, const size_t& jpegSize); + void setIcon(const uint64_t& _id, const gfx::tex& _set); + void draw(unsigned x, unsigned y); + void drawHalf(unsigned x, unsigned y); + uint64_t getTitleID(); + + void deleteData(); + + private: + uint64_t titleID; + gfx::tex iconTex; + }; //Class to store title info class titledata @@ -30,6 +50,9 @@ namespace data //Returns ID uint64_t getID(); + //Game icon + icn icon; + FsSaveDataType getType(); private: @@ -59,6 +82,8 @@ namespace data //Vector for storing save data info for user std::vector titles; + gfx::tex icn; + private: u128 userID; std::string username, userSafe; diff --git a/inc/gfx.h b/inc/gfx.h index bcce80f..367faa1 100644 --- a/inc/gfx.h +++ b/inc/gfx.h @@ -7,7 +7,7 @@ namespace gfx { //Inits graphics and shared font. Code for shared font is from switch-portlibs examples bool init(); - bool fini(); + bool exit(); //Changes gfx mode to linear double void switchMode(); @@ -42,11 +42,21 @@ namespace gfx Others will have issues. */ void loadFromFile(const std::string& path); + //Icons + void loadJpegMem(const uint8_t *txData, const uint32_t& jpegSz); //Frees memory used by data - ~tex(); + void deleteData(); + uint16_t getWidth(); + uint16_t getHeight(); + const uint32_t *getDataPointer(); + void draw(uint32_t x, uint32_t y); void drawNoBlend(uint32_t x, uint32_t y); + //For lazy-scaling icons + //Skips every-other pixel and row, so 1/2 scale + void drawNoBlendSkip(unsigned x, unsigned y); + //These only repeat pixels. void drawRepeatHori(uint32_t x, uint32_t y, uint32_t w); void drawRepeatHoriNoBlend(uint32_t x, uint32_t y, uint32_t w); diff --git a/inc/miscui.h b/inc/miscui.h index 6f9be72..a152d4f 100644 --- a/inc/miscui.h +++ b/inc/miscui.h @@ -44,6 +44,7 @@ namespace ui bool confirm(const std::string& q); bool confirmTransfer(const std::string& f, const std::string& t); bool confirmDelete(const std::string& p); + void debShowTex(gfx::tex tx); void drawTextbox(unsigned x, unsigned y, unsigned w, unsigned h); } diff --git a/inc/ui.h b/inc/ui.h index ec7f7ba..2456f70 100644 --- a/inc/ui.h +++ b/inc/ui.h @@ -22,6 +22,7 @@ namespace ui //Loads in the A, B, X, Y button graphics void init(); + void exit(); //Prepares menus for use void userMenuInit(); diff --git a/romfs/img/icn/icnDefault.png b/romfs/img/icn/icnDefault.png new file mode 100644 index 0000000..6a5929c Binary files /dev/null and b/romfs/img/icn/icnDefault.png differ diff --git a/romfs/img/topbar_drk.png b/romfs/img/topbar_drk.png deleted file mode 100644 index af95ed9..0000000 Binary files a/romfs/img/topbar_drk.png and /dev/null differ diff --git a/romfs/img/topbar_lght.png b/romfs/img/topbar_lght.png deleted file mode 100644 index c038d51..0000000 Binary files a/romfs/img/topbar_lght.png and /dev/null differ diff --git a/src/data.cpp b/src/data.cpp index 191b16a..ffb3484 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -38,15 +38,27 @@ static int getUserIndex(const u128& id) return -1; } +//Default icon if one isn't found. For system saves +gfx::tex defIcn; + namespace data { titledata curData; user curUser; + std::vector icons; std::vector users; bool sysSave = false, forceMountable = true; void loadDataInfo() { + //Load default icon + defIcn.loadFromFile("romfs:/img/icn/icnDefault.png"); + + //Create icon for it + icn defIcnIcon; + defIcnIcon.setIcon((uint64_t)0, defIcn); + icons.push_back(defIcnIcon); + for(unsigned i = 0; i < users.size(); i++) users[i].titles.clear(); @@ -102,6 +114,60 @@ namespace data for(unsigned i = 0; i < users.size(); i++) std::sort(users[i].titles.begin(), users[i].titles.end(), sortTitles); + curUser = users[0]; + } + + void exit() + { + for(unsigned i = 0; i < users.size(); i++) + users[i].icn.deleteData(); + + for(unsigned i = 1; i < icons.size(); i++) + icons[i].deleteData(); + } + + void icn::load(const uint64_t& _id, const uint8_t *jpegData, const size_t& jpegSize) + { + titleID = _id; + + iconTex.loadJpegMem(jpegData, jpegSize); + } + + void icn::setIcon(const uint64_t& _id, const gfx::tex& _set) + { + titleID = _id; + iconTex = _set; + } + + void icn::draw(unsigned x, unsigned y) + { + iconTex.drawNoBlend(x, y); + } + + void icn::drawHalf(unsigned x, unsigned y) + { + iconTex.drawNoBlendSkip(x, y); + } + + uint64_t icn::getTitleID() + { + return titleID; + } + + void icn::deleteData() + { + iconTex.deleteData(); + } + + int findIcnIndex(const uint64_t& titleID) + { + for(unsigned i = 0; i < icons.size(); i++) + { + if(icons[i].getTitleID() == titleID) + return i; + } + + return -1; } bool titledata::init(const FsSaveDataInfo& inf) @@ -142,6 +208,20 @@ namespace data { title.assign(ent->name); titleSafe = util::safeString(ent->name); + + int iconIndex = findIcnIndex(id); + if(iconIndex == -1) + { + uint32_t icnSize = outSz - sizeof(dat->nacp); + icn newIcon; + newIcon.load(id, dat->icon, icnSize); + icons.push_back(newIcon); + + icon = icons[findIcnIndex(id)]; + } + else + icon = icons[iconIndex]; + delete dat; } else @@ -150,6 +230,7 @@ namespace data sprintf(tmp, "%016lX", (u64)id); title.assign(tmp); titleSafe = util::safeString(tmp); + icon = icons[0]; } return true; @@ -208,6 +289,15 @@ namespace data username = "Unknown"; userSafe = util::safeString(username); + size_t sz = 0; + accountProfileGetImageSize(&prof, &sz); + uint8_t *profJpeg = new uint8_t[sz]; + + accountProfileLoadImage(&prof, profJpeg, sz, &sz); + icn.loadJpegMem(profJpeg, sz); + + delete[] profJpeg; + accountProfileClose(&prof); return true; @@ -237,6 +327,7 @@ namespace data { username = "Unknown"; userSafe = "Unknown"; + icn = defIcn; } return true; diff --git a/src/gfx.cpp b/src/gfx.cpp index 0b5d9f8..4ff856f 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -4,6 +4,9 @@ #include #include #include +#include + +#include #include #include FT_FREETYPE_H @@ -62,7 +65,7 @@ namespace gfx return true; } - bool fini() + bool exit() { if(faceret == 0) FT_Done_Face(face); @@ -306,10 +309,77 @@ namespace gfx } } - tex::~tex() + void tex::loadJpegMem(const uint8_t *txData, const uint32_t& jpegSz) + { + struct jpeg_decompress_struct jpegInfo; + struct jpeg_error_mgr error; + + jpegInfo.err = jpeg_std_error(&error); + + jpeg_create_decompress(&jpegInfo); + jpeg_mem_src(&jpegInfo, txData, jpegSz); + jpeg_read_header(&jpegInfo, true); + + //make sure we have RGB + if(jpegInfo.jpeg_color_space == JCS_YCbCr) + jpegInfo.out_color_space = JCS_RGB; + + width = jpegInfo.image_width; + height = jpegInfo.image_height; + + data = new uint32_t[width * height]; + + jpeg_start_decompress(&jpegInfo); + + JSAMPARRAY row = (JSAMPARRAY)new JSAMPARRAY[sizeof(JSAMPROW)]; + row[0] = (JSAMPROW)new JSAMPROW[(sizeof(JSAMPLE) * width) * 3]; + + uint8_t *ptr; + + for(unsigned y = 0, i = 0; y < height; y++) + { + jpeg_read_scanlines(&jpegInfo, row, 1); + unsigned x; + for(x = 0, ptr = row[0]; x < width; x++, ptr += 3, i++) + { + uint8_t r, g, b; + r = ptr[0]; + g = ptr[1]; + b = ptr[2]; + + data[i] = 0xFF << 24 | b << 16 | g << 8 | r; + } + } + + jpeg_finish_decompress(&jpegInfo); + jpeg_destroy_decompress(&jpegInfo); + + delete row[0]; + delete[] row; + } + + void tex::deleteData() { if(data != NULL) + { delete[] data; + data = NULL; + } + } + + uint16_t tex::getWidth() + { + return width; + } + + uint16_t tex::getHeight() + { + return height; + } + + const uint32_t *tex::getDataPointer() + { + return data; } void tex::draw(uint32_t x, uint32_t y) @@ -347,6 +417,23 @@ namespace gfx } } + void tex::drawNoBlendSkip(unsigned x, unsigned y) + { + if(data != NULL) + { + unsigned tY, tX, i = 0; + uint32_t *fb = (uint32_t *)gfxGetFramebuffer(NULL, NULL); + + for(tY = y; tY < y + (height / 2); tY++, i += width) + { + for(tX = x; tX < x + (width / 2); tX++, i += 2) + { + fb[tY * frameBufWidth + tX] = data[i]; + } + } + } + } + void tex::drawRepeatHori(uint32_t x, uint32_t y, uint32_t w) { if(data != NULL) diff --git a/src/main.cpp b/src/main.cpp index 11ce07a..3acd20e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,14 +33,11 @@ extern "C" } } -extern int mstate; - int main(int argc, const char *argv[]) { gfx::init(); - data::loadDataInfo(); ui::init(); - ui::userMenuInit(); + data::loadDataInfo(); bool run = true; while(appletMainLoop() && run) @@ -57,12 +54,9 @@ int main(int argc, const char *argv[]) data::sysSave = true; data::forceMountable = false; data::loadDataInfo(); - ui::userMenuInit(); //Just to be sure fsdevUnmountDevice("sv"); - - mstate = 0; } } else if(down & KEY_PLUS) @@ -72,5 +66,7 @@ int main(int argc, const char *argv[]) gfx::handleBuffs(); } - gfx::fini(); + gfx::exit(); + ui::exit(); + data::exit(); } diff --git a/src/miscui.cpp b/src/miscui.cpp index 56ec5fe..a233d1f 100644 --- a/src/miscui.cpp +++ b/src/miscui.cpp @@ -186,6 +186,30 @@ namespace ui return confirm(confMess); } + void debShowTex(gfx::tex tx) + { + unsigned tbX = 256, tbY = 128; + unsigned tbW = 768, tbH = 464; + + unsigned texX = tbX + ((tbW / 2) - (tx.getWidth() / 2)); + unsigned texY = tbY + ((tbH / 2) - (tx.getHeight() / 2)); + + while(true) + { + hidScanInput(); + + uint64_t down = hidKeysDown(CONTROLLER_P1_AUTO); + + if(down & KEY_A) + break; + + ui::drawTextbox(tbX, tbY, tbW, tbH); + tx.drawNoBlend(texX, texY); + + gfx::handleBuffs(); + } + } + void drawTextbox(unsigned x, unsigned y, unsigned w, unsigned h) { //Top diff --git a/src/ui.cpp b/src/ui.cpp index 23db72d..a464afb 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -21,10 +21,10 @@ enum menuState int mstate = USR_SEL; //Button controlled menus -static ui::menu userMenu, titleMenu, folderMenu, devMenu, saveMenu, sdMenu, copyMenu; +static ui::menu folderMenu, devMenu, saveMenu, sdMenu, copyMenu; //Bar at top -static gfx::tex buttonA, buttonB, buttonX, buttonY, titleBar; +static gfx::tex buttonA, buttonB, buttonX, buttonY; //Shown if someone tries to write to system saves static const std::string sysSaveMess = "No system saves until we get public NAND restore."; @@ -53,8 +53,6 @@ namespace ui switch(gthm) { case ColorSetId_Light: - titleBar.loadFromFile("romfs:/img/topbar_lght.png"); - //Dark corners cornerTopLeft.loadFromFile("romfs:/img/tbox/tboxCornerTopLeft_drk.png"); cornerTopRight.loadFromFile("romfs:/img/tbox/tboxCornerTopRight_drk.png"); @@ -77,8 +75,6 @@ namespace ui default: case ColorSetId_Dark: - titleBar.loadFromFile("romfs:/img/topbar_drk.png"); - //Light corners cornerTopLeft.loadFromFile("romfs:/img/tbox/tboxCornerTopLeft_lght.png"); cornerTopRight.loadFromFile("romfs:/img/tbox/tboxCornerTopRight_lght.png"); @@ -113,19 +109,22 @@ namespace ui copyMenu.addOpt("Back"); } - void userMenuInit() + void exit() { - userMenu.reset(); - for(unsigned i = 0; i < data::users.size(); i++) - userMenu.addOpt(data::users[i].getUsername()); - } + cornerTopLeft.deleteData(); + cornerTopRight.deleteData(); + cornerBottomLeft.deleteData(); + cornerBottomRight.deleteData(); - void titleMenuPrepare(data::user& usr) - { - titleMenu.reset(); + horEdgeTop.deleteData(); + horEdgeBot.deleteData(); + vertEdgeLeft.deleteData(); + vertEdgeRight.deleteData(); - for(unsigned i = 0; i < usr.titles.size(); i++) - titleMenu.addOpt(usr.titles[i].getTitle()); + buttonA.deleteData(); + buttonB.deleteData(); + buttonX.deleteData(); + buttonY.deleteData(); } void folderMenuPrepare(data::user& usr, data::titledata& dat) @@ -143,19 +142,16 @@ namespace ui void drawTitleBar(const std::string& txt) { - titleBar.drawRepeatHoriNoBlend(0, 0, 1280); gfx::drawText(txt, 16, 16, 64, mnuTxt); } void drawUI() { gfx::clearBufferColor(clearClr); - ui::drawTitleBar("JKSV - 06/29/2018"); + ui::drawTitleBar("JKSV - 07/01/2018"); switch(mstate) { - case USR_SEL: - case TTL_SEL: case FLD_SEL: gfx::drawRectangle(448, 64, 1, 592, rectLt); gfx::drawRectangle(449, 64, 2, 592, rectSh); @@ -164,6 +160,15 @@ namespace ui gfx::drawRectangle(16, 657, 1248, 2, rectSh); break; + case USR_SEL: + case TTL_SEL: + gfx::drawRectangle(16, 64, 1248, 1, rectLt); + gfx::drawRectangle(16, 65, 1248, 2, rectSh); + + gfx::drawRectangle(16, 656, 1248, 1, rectLt); + gfx::drawRectangle(16, 657, 1248, 2, rectSh); + break; + case ADV_MDE: gfx::drawRectangle(640, 64, 1, 592, rectLt); gfx::drawRectangle(641, 64, 2, 592, rectSh); @@ -177,8 +182,6 @@ namespace ui { case USR_SEL: { - //Menu - userMenu.print(16, 88, mnuTxt, 424); //Input guide unsigned startX = 1010; buttonA.draw(startX, 672); @@ -190,9 +193,6 @@ namespace ui case TTL_SEL: { - //Menu - titleMenu.print(16, 88, mnuTxt, 424); - //Input guide unsigned startX = 914; buttonA.draw(startX, 672); gfx::drawText("Select", startX += 38, 668, 32, mnuTxt); @@ -205,8 +205,6 @@ namespace ui case FLD_SEL: { - //Menus - titleMenu.print(16, 88, mnuTxt, 424); folderMenu.print(458, 88, mnuTxt, 806); //Input guide unsigned startX = 726; @@ -234,13 +232,68 @@ namespace ui void updateUserMenu(const uint64_t& down, const uint64_t& held) { - userMenu.handleInput(down, held); + static int start = 0, selected = 0; - if(down & KEY_A) + static uint8_t clrShft = 0; + static bool clrAdd = true; + + static unsigned selRectX = 64, selRectY = 74; + + if(clrAdd) { - data::curUser = data::users[userMenu.getSelected()]; - titleMenuPrepare(data::curUser); + clrShft += 4; + if(clrShft > 63) + clrAdd = false; + } + else + { + clrShft--; + if(clrShft == 0) + clrAdd = true; + } + + if(down & KEY_RIGHT) + { + if((unsigned)selected < data::users.size() - 1) + selected++; + + if(selected >= (int)start + 32) + start += 8; + } + else if(down & KEY_LEFT) + { + if(selected > 0) + selected--; + + if(selected < (int)start) + start -= 8; + } + else if(down & KEY_UP) + { + selected -= 8; + if(selected < 0) + selected = 0; + + if(selected - start >= 32) + start -= 8; + } + else if(down & KEY_DOWN) + { + selected += 8; + if(selected > (int)data::users.size() - 1) + selected = data::users.size() - 1; + + if(selected - start >= 32) + start += 8; + } + else if(down & KEY_A) + { + data::curUser = data::users[selected]; + //Reset this + start = 0; + selected = 0; + selRectX = 64, selRectY = 74; mstate = TTL_SEL; } else if(down & KEY_Y) @@ -248,22 +301,126 @@ namespace ui for(unsigned i = 0; i < data::users.size(); i++) fs::dumpAllUserSaves(data::users[i]); } + + unsigned x = 70, y = 80; + unsigned endUser = start + 32; + if(start + 32 > (int)data::users.size()) + endUser = data::users.size(); + + uint32_t rectClr = 0xFF << 24 | ((0xBB + clrShft) & 0xFF) << 16 | ((0x60 + clrShft)) << 8 | 0x00; + gfx::drawRectangle(selRectX, selRectY, 140, 140, rectClr); + + for(unsigned i = start; i < endUser; y += 144) + { + unsigned endRow = i + 8; + for(unsigned tX = x; i < endRow; i++, tX += 144) + { + if(i == endUser) + break; + + if((int)i == selected) + { + if(selRectX != tX - 6) + { + if(selRectX < tX - 6) + selRectX += 24; + else + selRectX -= 24; + } + + if(selRectY != y - 6) + { + if(selRectY < y - 6) + selRectY += 24; + else + selRectX -= 24; + } + + std::string username = data::users[selected].getUsername(); + unsigned userWidth = gfx::getTextWidth(username, 32); + int userRectWidth = userWidth + 32, userRectX = (tX + 64) - (userRectWidth / 2); + if(userRectX < 16) + userRectX = 16; + + if(userRectX + userRectWidth > 1264) + userRectX = 1264 - userRectWidth; + + drawTextbox(userRectX, y - 50, userRectWidth, 38); + gfx::drawText(username, userRectX + 16, y - 50, 32, txtClr); + } + data::users[i].icn.drawNoBlendSkip(tX, y); + } + } } void updateTitleMenu(const uint64_t& down, const uint64_t& held) { - titleMenu.handleInput(down, held); + //Static vars so they don't change on every loop + //Where to start in titles, selected title + static int start = 0, selected = 0; - if(down & KEY_A) + //Color shift for rect + static uint8_t clrShft = 0; + //Whether or not we're adding or subtracting from clrShft + static bool clrAdd = true; + + //Selected rectangle X and Y. + static unsigned selRectX = 64, selRectY = 74; + + if(clrAdd) { - data::curData = data::curUser.titles[titleMenu.getSelected()]; + clrShft += 4; + if(clrShft > 63) + clrAdd = false; + } + else + { + clrShft--; + if(clrShft == 0) + clrAdd = true; + } + if(down & KEY_RIGHT) + { + if(selected < (int)data::curUser.titles.size() - 1) + selected++; + + if(selected >= (int)start + 32) + start += 8; + } + else if(down & KEY_LEFT) + { + if(selected > 0) + selected--; + + if(selected < (int)start) + start -= 8; + } + else if(down & KEY_UP) + { + selected -= 8; + if(selected < 0) + selected = 0; + + if(selected < start) + start -= 8; + } + else if(down & KEY_DOWN) + { + selected += 8; + if(selected > (int)data::curUser.titles.size() - 1) + selected = data::curUser.titles.size() - 1; + + if(selected - start >= 32) + start += 8; + } + else if(down & KEY_A) + { + data::curData = data::curUser.titles[selected]; if(fs::mountSave(data::curUser, data::curData)) { util::makeTitleDir(data::curUser, data::curData); - folderMenuPrepare(data::curUser, data::curData); - mstate = FLD_SEL; } } @@ -272,40 +429,68 @@ namespace ui fs::dumpAllUserSaves(data::curUser); } else if(down & KEY_B) - mstate = USR_SEL; - - if(data::sysSave) { - std::string drawType = "Type: "; - switch(data::curUser.titles[titleMenu.getSelected()].getType()) - { - case FsSaveDataType_SystemSaveData: - drawType += "System Save"; - break; - - case FsSaveDataType_SaveData: - drawType += "Save Data"; - break; - - case FsSaveDataType_BcatDeliveryCacheStorage: - drawType += "Bcat Delivery Cache"; - break; - - case FsSaveDataType_DeviceSaveData: - drawType += "Device Save"; - break; - - case FsSaveDataType_TemporaryStorage: - drawType += "Temp Storage"; - break; - - case FsSaveDataType_CacheStorage: - drawType += "Cache Storage"; - break; - } - - gfx::drawText(drawType, 16, 668, 32, mnuTxt); + start = 0; + selected = 0; + selRectX = 64; + selRectY = 74; + mstate = USR_SEL; + return; } + + unsigned x = 70, y = 80; + + unsigned endTitle = start + 32; + if(start + 32 > (int)data::curUser.titles.size()) + endTitle = data::curUser.titles.size(); + + //draw Rect so it's always behind icons + uint32_t rectClr = 0xFF << 24 | ((0xBB + clrShft) & 0xFF) << 16 | ((0x60 + clrShft)) << 8 | 0x00; + gfx::drawRectangle(selRectX, selRectY, 140, 140, rectClr); + + for(unsigned i = start; i < endTitle; y += 144) + { + unsigned endRow = i + 8; + for(unsigned tX = x; i < endRow; i++, tX += 144) + { + if(i == endTitle) + break; + + if((int)i == selected) + { + //Most Switch icons seem to be 256x256, we're drawing them 128x128 + if(selRectX != tX - 6) + { + if(selRectX < tX - 6) + selRectX += 24; + else + selRectX -= 24; + } + + if(selRectY != y - 6) + { + if(selRectY < y - 6) + selRectY += 24; + else + selRectY -= 24; + } + + std::string title = data::curUser.titles[selected].getTitle(); + unsigned titleWidth = gfx::getTextWidth(title, 32); + int rectWidth = titleWidth + 32, rectX = (tX + 64) - (rectWidth / 2); + if(rectX < 16) + rectX = 16; + + if(rectX + rectWidth > 1264) + rectX = 1264 - rectWidth; + + drawTextbox(rectX, y - 50, rectWidth, 38); + gfx::drawText(title, rectX + 16, y - 50, 32, txtClr); + } + data::curUser.titles[i].icon.drawHalf(tX, y); + } + } + } void updateFolderMenu(const uint64_t& down, const uint64_t& held)