Pop messages & revisions

This commit is contained in:
Your Name 2024-05-26 17:08:14 -04:00
parent 73bf730f73
commit 8f30cada67
16 changed files with 320 additions and 89 deletions

View File

@ -35,6 +35,8 @@ class backupMenuState : public appState
std::unique_ptr<ui::slidePanel> m_BackupPanel;
// Folder listing
std::unique_ptr<fs::directoryListing> m_BackupListing;
// Listing to make sure there is a save
std::unique_ptr<fs::directoryListing> m_SaveListing;
// Folder menu
std::unique_ptr<ui::menu> m_BackupMenu;
};

View File

@ -1,4 +1,7 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <memory>
#include <string>
#include <cstdint>
@ -6,6 +9,29 @@ namespace fs
{
namespace io
{
// Default buffer size used for transfering files
static const int FILE_BUFFER_SIZE = 0x400000;
// Struct for threads reading/writing threads. Here because other parts of JKSV can share the writeFunction
typedef struct
{
// File's size
uint64_t fileSize;
// Mutex
std::mutex bufferLock;
// Conditional for making sure buffer is empty/full
std::condition_variable bufferIsReady;
// Bool to check
bool bufferIsFull = false;
// Buffer shared for transfer
std::vector<char> buffer;
// Whether file needs to be commited on write
bool commitWrite = false;
// Journal size
uint64_t journalSize = 0;
// Current offset
uint64_t currentOffset = 0;
} threadStruct;
// Just returns size of file
int getFileSize(const std::string &filePath);
// These functions are all threaded/task wrapper functions
@ -15,7 +41,10 @@ namespace fs
void copyFileCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize);
// Recusively copies source to destination
void copyDirectory(const std::string &source, const std::string &destination);
// Recursively copies source to destination
// Recursively copies source to destination
void copyDirectoryCommit(const std::string &source, const std::string &destination, const uint64_t &journalSize);
// These are functions for threaded copying that other parts of JKSV can use.
void readThreadFunction(const std::string &source, std::shared_ptr<threadStruct> sharedStruct);
void writeThreadFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct);
}
}

View File

@ -4,10 +4,16 @@
namespace jksv
{
// Inits all services/code needed
bool init(void);
// Exits and frees memory needed
void exit(void);
// Updates logic/states
void update(void);
// Renders states
void render(void);
// Returns if JKSV is still running
const bool isRunning(void);
// Pushes newAppState to back of vector
void pushNewState(std::unique_ptr<appState> &newAppState);
}

20
inc/system/timer.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
namespace sys
{
class timer
{
public:
// Inits timer with current ticks
timer(const uint32_t &triggerTicks);
// Returns if triggerTicks has been reached.
bool triggered(void);
private:
// Starting tick count
uint32_t m_StartingTicks = 0;
// Trigger tick count
uint32_t m_TriggerTicks = 0;
};
}

View File

@ -81,6 +81,22 @@
#define LANG_THREAD_COMPRESSING_UPLOAD "threadStatusCompressingSaveForUpload"
// Popup message strings
#define LANG_POP_CPU_BOOST "popCPUBoostEnabled"
#define LANG_POP_ERROR_COMMITING "popErrorCommitingFile"
#define LANG_POP_ZIP_EMPTY "popZipIsEmpty"
#define LANG_POP_SAVE_EMPTY "popSaveIsEmpty"
#define LANG_POP_FOLDER_EMPTY "popFolderIsEmpty"
#define LANG_POP_PROCESS_SHUTDOWN "popProcessShutdown"
#define LANG_POP_ADDED_PATH_FILTER "popAddedPathToFilter"
#define LANG_POP_CHANGED_OUTPUT_DIR "popChangedOutputFolder"
#define LANG_POP_CHANGED_OUTPUT_ERROR "popChangedOutputError"
#define LANG_POP_TRASH_EMPTIED "popTrashEmptied"
#define LANG_POP_SVI_EXPORTED "popSVIExported"
#define LANG_POP_DRIVE_STARTED "popDriveStarted"
#define LANG_POP_DRIVE_FAILED "popDriveFailed"
#define LANG_POP_DRIVE_INACTIVE "popDriveNotActive"
namespace ui
{
namespace strings

33
inc/ui/popMessage.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <memory>
#include "system/timer.hpp"
namespace ui
{
namespace popMessage
{
// This is the default and only used timer count for these messages.
static const int POPMESSAGE_DEFAULT_TICKS = 2500;
// Actual message struct
typedef struct
{
// Message string
std::string message;
// Timer for expiration
std::unique_ptr<sys::timer> messageTimer;
// Width of rectangle to render
int rectangleWidth = 0;
// Current Y
int y = 0;
} pMessage;
// Adds a message to the queue to be processed. tickLength is how long in ticks for it to be displayed.
void newMessage(const std::string &newMessage, const int &tickLength);
// Updates the message queue and vector
void update(void);
// Renders messages to screen
void render(void);
}
}

View File

@ -6,6 +6,7 @@
#include "ui/progressBar.hpp"
#include "ui/slidePanel.hpp"
#include "ui/titleSelection.hpp"
#include "ui/popMessage.hpp"
namespace ui
{

View File

@ -5,12 +5,13 @@
#include "config.hpp"
#include "system/input.hpp"
#include "graphics/graphics.hpp"
#include "appstates/backupMenuState.hpp"
#include "appStates/backupMenuState.hpp"
#include "appStates/taskState.hpp"
#include "filesystem/filesystem.hpp"
#include "system/task.hpp"
#include "stringUtil.hpp"
#include "jksv.hpp"
#include "log.hpp"
// This is for backup task
struct backupArgs : sys::taskArgs
@ -25,7 +26,7 @@ struct backupArgs : sys::taskArgs
// This is shared by overwrite and restore
struct pathArg : sys::taskArgs
{
std::string path;
std::string source, destination;
};
// These are the functions used for tasks
@ -33,7 +34,7 @@ struct pathArg : sys::taskArgs
void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
{
// Make sure we weren't passed nullptr
if(args == nullptr)
if (args == nullptr)
{
task->finished();
return;
@ -45,15 +46,15 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
// Process shortcuts or get name for backup
std::string backupName;
if(sys::input::buttonHeld(HidNpadButton_R) || config::getByKey(CONFIG_AUTONAME_BACKUPS))
if (sys::input::buttonHeld(HidNpadButton_R) || config::getByKey(CONFIG_AUTONAME_BACKUPS))
{
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_YMD);
}
else if(sys::input::buttonHeld(HidNpadButton_L))
else if (sys::input::buttonHeld(HidNpadButton_L))
{
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_YDM);
}
else if(sys::input::buttonHeld(HidNpadButton_ZL))
else if (sys::input::buttonHeld(HidNpadButton_ZL))
{
backupName = argsIn->currentUser->getPathSafeUsername() + " - " + stringUtil::getTimeAndDateString(stringUtil::DATE_FORMAT_ASC);
}
@ -65,13 +66,13 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
}
// If it's not empty, check ZIP option or extension. Normally I avoid nested ifs when possible but...
if(backupName.empty() == false)
if (backupName.empty() == false)
{
// Get extension and full path
std::string extension = stringUtil::getExtensionFromString(backupName);
std::string path = argsIn->outputBasePath + backupName + "/";
if(config::getByKey(CONFIG_USE_ZIP) || extension == "zip")
if (config::getByKey(CONFIG_USE_ZIP) || extension == "zip")
{
zipFile zipOut = zipOpen64(path.c_str(), 0);
fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut);
@ -83,6 +84,9 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, path);
}
}
{
logger::log("Backup name empty.");
}
// Reload list to reflect changes
argsIn->sendingState->loadDirectoryList();
// Task is finished
@ -93,7 +97,7 @@ void createNewBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
{
// Bail if no args present
if(args == nullptr)
if (args == nullptr)
{
task->finished();
return;
@ -110,20 +114,20 @@ void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
int saveFileCount = testListing.getListingCount();
// For checking if overwriting a zip
std::string fileExtension = stringUtil::getExtensionFromString(argIn->path);
std::string fileExtension = stringUtil::getExtensionFromString(argIn->destination);
if(std::filesystem::is_directory(argIn->path) && saveFileCount > 0)
if (std::filesystem::is_directory(argIn->destination) && saveFileCount > 0)
{
// Delete backup then recreate directory
std::filesystem::remove_all(argIn->path);
std::filesystem::create_directory(argIn->path);
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argIn->path);
std::filesystem::remove_all(argIn->destination);
std::filesystem::create_directories(argIn->destination);
fs::io::copyDirectory(fs::DEFAULT_SAVE_MOUNT_DEVICE, argIn->destination);
}
else if(std::filesystem::is_directory(argIn->path) == false && fileExtension == "zip" && saveFileCount > 0)
else if (std::filesystem::is_directory(argIn->destination) == false && fileExtension == "zip" && saveFileCount > 0)
{
std::filesystem::remove(argIn->path);
zipFile zipOut = zipOpen64(argIn->path.c_str(), 0);
fs::io::copyDirectoryToZip(argIn->path, zipOut);
std::filesystem::remove(argIn->destination);
zipFile zipOut = zipOpen64(argIn->destination.c_str(), 0);
fs::io::copyDirectoryToZip(fs::DEFAULT_SAVE_MOUNT_DEVICE, zipOut);
zipClose(zipOut, "");
}
task->finished();
@ -132,7 +136,7 @@ void overwriteBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
// Wipes current save for game, then copies backup from SD to filesystem
void restoreBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
{
if(args == nullptr)
if (args == nullptr)
{
task->finished();
return;
@ -142,9 +146,9 @@ void restoreBackup(sys::task *task, std::shared_ptr<sys::taskArgs> args)
std::shared_ptr<pathArg> argIn = std::static_pointer_cast<pathArg>(args);
// Test if there are actually files in the save
fs::directoryListing testListing(argIn->path);
fs::directoryListing testListing(argIn->source);
int saveFileCount = testListing.getListingCount();
if(saveFileCount <= 0)
if (saveFileCount <= 0)
{
return;
}
@ -166,34 +170,48 @@ backupMenuState::backupMenuState(data::user *currentUser, data::userSaveInfo *cu
m_BackupMenu = std::make_unique<ui::menu>(10, 4, panelWidth + 44, 18, 7);
// Directory listing of backups
m_BackupListing = std::make_unique<fs::directoryListing>(m_OutputBasePath);
// Directory listing of save
m_SaveListing = std::make_unique<fs::directoryListing>(fs::DEFAULT_SAVE_MOUNT_DEVICE);
backupMenuState::loadDirectoryList();
}
backupMenuState::~backupMenuState()
backupMenuState::~backupMenuState()
{
fs::unmountSaveData();
}
void backupMenuState::update(void)
{
// Update panel and menu
m_BackupPanel->update();
m_BackupMenu->update();
// Get selected item to check what's selected
int selected = m_BackupMenu->getSelected();
if(sys::input::buttonDown(HidNpadButton_A) && selected == 0)
if (sys::input::buttonDown(HidNpadButton_A) && selected == 0)
{
// Data to send to task
std::shared_ptr<backupArgs> backupTaskArgs = std::make_shared<backupArgs>();
backupTaskArgs->currentUser = m_CurrentUser;
backupTaskArgs->currentTitleInfo = m_CurrentTitleInfo;
backupTaskArgs->outputBasePath = m_OutputBasePath;
backupTaskArgs->sendingState = this;
// Check to make sure save data isn't empty before wasting time and space
if (m_BackupListing->getListingCount() > 0)
{
// Data to send to task
std::shared_ptr<backupArgs> backupTaskArgs = std::make_shared<backupArgs>();
backupTaskArgs->currentUser = m_CurrentUser;
backupTaskArgs->currentTitleInfo = m_CurrentTitleInfo;
backupTaskArgs->outputBasePath = m_OutputBasePath;
backupTaskArgs->sendingState = this;
// Task state
std::unique_ptr<appState> backupTask = std::make_unique<taskState> (createNewBackup, backupTaskArgs);
jksv::pushNewState(backupTask);
// Task state
std::unique_ptr<appState> backupTask = std::make_unique<taskState>(createNewBackup, backupTaskArgs);
jksv::pushNewState(backupTask);
}
else
{
ui::popMessage::newMessage(ui::strings::getString(LANG_POP_SAVE_EMPTY, 0), ui::popMessage::POPMESSAGE_DEFAULT_TICKS);
}
}
else if (sys::input::buttonDown(HidNpadButton_Y))
{
}
else if (sys::input::buttonDown(HidNpadButton_B))
{
@ -223,7 +241,7 @@ void backupMenuState::loadDirectoryList(void)
int listingCount = m_BackupListing->getListingCount();
m_BackupMenu->addOpt(ui::strings::getString(LANG_FOLDER_MENU_NEW, 0));
for(int i = 0; i < listingCount; i++)
for (int i = 0; i < listingCount; i++)
{
m_BackupMenu->addOpt(m_BackupListing->getItemAt(i));
}

View File

@ -7,6 +7,7 @@
#include "system/input.hpp"
#include "ui/ui.hpp"
#include "system/task.hpp"
#include "stringUtil.hpp"
#include "log.hpp"
// Tasks
@ -61,7 +62,8 @@ void mainMenuState::update(void)
}
else
{
logger::log("No titles found for user %s", selectedUser->getUsername().c_str());
std::string noSavesMessage = stringUtil::getFormattedString(ui::strings::getCString(LANG_SAVEDATA_NONE_FOUND, 0), selectedUser->getUsername().c_str());
ui::popMessage::newMessage(noSavesMessage, ui::popMessage::POPMESSAGE_DEFAULT_TICKS);
}
}
}

View File

@ -6,52 +6,25 @@
#include <condition_variable>
#include <vector>
#include "filesystem/filesystem.hpp"
#include "filesystem/directoryListing.hpp"
#include "filesystem/io.hpp"
#include "system/task.hpp"
#include "system/taskArgs.hpp"
#include "log.hpp"
// Generic error for opening files
static const char *s_ErrorOpening = "Error opening files for reading and/or writing.";
// Buffer size for read/write threads
#define FILE_BUFFER_SIZE 0x400000
// Struct shared for reading/writing
typedef struct
{
// File's size
uint64_t fileSize;
// Mutex
std::mutex bufferLock;
// Conditional for making sure buffer is empty/full
std::condition_variable bufferIsReady;
// Bool to check
bool bufferIsFull = false;
// Buffer shared for transfer
std::vector<char> buffer;
// Whether file needs to be commited on write
bool commitWrite = false;
// Journal size
uint64_t journalSize = 0;
} threadStruct;
// Read thread function
void readFunction(const std::string &source, std::shared_ptr<threadStruct> sharedStruct)
void fs::io::readThreadFunction(const std::string &source, std::shared_ptr<threadStruct> sharedStruct)
{
// Bytes read
uint64_t bytesRead = 0;
// File stream
std::ifstream sourceFile(source, std::ios::binary);
// Local buffer for reading
std::vector<char> localBuffer(FILE_BUFFER_SIZE);
std::vector<char> localBuffer(fs::io::FILE_BUFFER_SIZE);
// Loop until file is fully read
while(bytesRead < sharedStruct->fileSize)
{
// Read to localBuffer
sourceFile.read(localBuffer.data(), FILE_BUFFER_SIZE);
sourceFile.read(localBuffer.data(), fs::io::FILE_BUFFER_SIZE);
// Wait for shared buffer to be emptied by write thread
std::unique_lock sharedBufferLock(sharedStruct->bufferLock);
@ -75,7 +48,7 @@ void readFunction(const std::string &source, std::shared_ptr<threadStruct> share
}
// File writing thread function
void writeFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct)
void fs::io::writeThreadFunction(const std::string &destination, std::shared_ptr<threadStruct> sharedStruct)
{
// Keep track of bytes written
uint64_t bytesWritten = 0;
@ -96,6 +69,9 @@ void writeFunction(const std::string &destination, std::shared_ptr<threadStruct>
// Copy shared buffer to localBuffer so read thread can continue
localBuffer.assign(sharedStruct->buffer.begin(), sharedStruct->buffer.end());
// Update offet
sharedStruct->currentOffset += localBuffer.size();
// Signal
sharedStruct->bufferIsFull = false;
@ -145,8 +121,8 @@ void fs::io::copyFile(const std::string &source, const std::string &destination)
sharedStruct->fileSize = fs::io::getFileSize(source);
// Read & write thread
std::thread readThread(readFunction, source, sharedStruct);
std::thread writeThread(writeFunction, destination, sharedStruct);
std::thread readThread(fs::io::readThreadFunction, source, sharedStruct);
std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct);
// Wait for finish
readThread.join();
@ -164,8 +140,8 @@ void fs::io::copyFileCommit(const std::string &source, const std::string &destin
sharedStruct->journalSize = journalSize;
// Threads
std::thread readThread(readFunction, source, sharedStruct);
std::thread writeThread(writeFunction, destination, sharedStruct);
std::thread readThread(fs::io::readThreadFunction, source, sharedStruct);
std::thread writeThread(fs::io::writeThreadFunction, destination, sharedStruct);
// Wait
readThread.join();
@ -184,7 +160,6 @@ void fs::io::copyDirectory(const std::string &source, const std::string &destina
std::string newSource = source + list.getItemAt(i) + "/";
std::string newDestination = destination + list.getItemAt(i) + "/";
std::filesystem::create_directories(newDestination);
//std::filesystem::create_directory(newDestination.substr(0, newDestination.npos - 1));
fs::io::copyDirectory(newSource, newDestination);
}
else

View File

@ -42,7 +42,7 @@ void fs::io::copyDirectoryToZip(const std::string &source, zipFile zip)
.tm_hour = local->tm_hour,
.tm_mday = local->tm_mday,
.tm_mon = local->tm_mon,
.tm_year = 1900 + local->tm_year
.tm_year = local->tm_year + 1900
},
.dosDate = 0,
.internal_fa = 0,

View File

@ -37,12 +37,14 @@ bool jksv::init(void)
// Config doesn't return anything
config::init();
// Filesystem
if(fs::init() == false)
{
return false;
}
// ui doesn't return anything
// ui just loads some things
ui::init();
// Load data from system
@ -93,6 +95,9 @@ void jksv::update(void)
// Update back of vector
s_AppStateVector.back()->update();
// Update pop up messages
ui::popMessage::update();
}
void jksv::render(void)
@ -103,11 +108,16 @@ void jksv::render(void)
graphics::renderLine(NULL, 30, 648, 1250, 648, COLOR_WHITE);
graphics::textureRender(s_HeaderIcon, NULL, 66, 27);
graphics::systemFont::renderText("JKSV", NULL, 130, 38, 24, COLOR_WHITE);
// Render the state vector
for (std::unique_ptr<appState> &a : s_AppStateVector)
{
a->render();
}
// Render pop up messages
ui::popMessage::render();
graphics::endFrame();
}

View File

@ -1,9 +1,15 @@
#include <array>
#include <string>
#include <cstring>
#include <cstdarg>
#include <cstdint>
#include <ctime>
#include <switch.h>
#include "stringUtil.hpp"
// va buffer size
static const int VA_BUFFER_SIZE = 0x800;
// Chars that shouldn't be in paths & function to test for them
static const uint32_t forbiddenPathChars[] = {L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'', L'©', L'®'};
@ -27,12 +33,15 @@ static bool codepointIsASCII(const uint32_t &codepoint)
std::string stringUtil::getFormattedString(const char *format, ...)
{
char vaBuffer[0x800];
// Use C++ array for fun
std::array<char, VA_BUFFER_SIZE> vaBuffer;
// Use va to assemble string
va_list args;
va_start(args, format);
vsnprintf(vaBuffer, 0x800, format, args);
vsnprintf(vaBuffer.data(), VA_BUFFER_SIZE, format, args);
va_end(args);
return std::string(vaBuffer);
// Return array as C++ string
return std::string(vaBuffer.data());
}
std::string stringUtil::getPathSafeString(const std::string &str)
@ -113,6 +122,9 @@ std::string stringUtil::getExtensionFromString(const std::string &path)
std::string stringUtil::getTimeAndDateString(const dateFormats &dateFormat)
{
// String to return
std::string dateString;
// Get local time from system
std::time_t rawTime;
std::time(&rawTime);
@ -125,24 +137,25 @@ std::string stringUtil::getTimeAndDateString(const dateFormats &dateFormat)
{
case stringUtil::dateFormats::DATE_FORMAT_YMD:
{
return stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_min, local->tm_sec);
dateString = stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_hour, local->tm_min, local->tm_sec);
}
break;
case stringUtil::dateFormats::DATE_FORMAT_YDM:
{
return stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mday, local->tm_mon + 1, local->tm_hour, local->tm_min, local->tm_sec);
dateString = stringUtil::getFormattedString("%04d.%02d.%02d @ %02d.%02d.%02d", currentYear, local->tm_mday, local->tm_mon + 1, local->tm_hour, local->tm_min, local->tm_sec);
}
break;
case stringUtil::dateFormats::DATE_FORMAT_ASC:
{
std::string dateString = stringUtil::getFormattedString("%04d%02d%02d_%02d%02d", currentYear, local->tm_mon + 1, local->tm_mday, local->tm_hour, local->tm_min);
// Assign date
dateString.assign(std::asctime(local));
// These need to be removed
stringUtil::replaceInString(dateString, ":", "_");
stringUtil::replaceInString(dateString, "\n", 0x00);
return dateString;
}
break;
}
return std::string("");
return dateString;
}

View File

@ -1,3 +1,4 @@
#include <vector>
#include <cstring>
#include "system/input.hpp"
#include "log.hpp"
@ -31,11 +32,10 @@ bool sys::input::buttonHeld(const HidNpadButton &button)
std::string sys::input::getString(const std::string &defaultText, const std::string &headerText, const size_t &maximumLength)
{
// Keyboard config
SwkbdConfig config;
char inputBuffer[maximumLength + 1];
// Clear inputBuffer
std::memset(inputBuffer, 0x00, maximumLength + 1);
// Input buffer
std::vector<char> inputBuffer(maximumLength + 1);
// Setup soft keyboard
swkbdCreate(&config, 0);
@ -46,10 +46,13 @@ std::string sys::input::getString(const std::string &defaultText, const std::str
swkbdConfigSetStringLenMax(&config, maximumLength);
swkbdConfigSetKeySetDisableBitmask(&config, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent);
Result showKeyboard = swkbdShow(&config, inputBuffer, maximumLength + 1);
Result showKeyboard = swkbdShow(&config, inputBuffer.data(), maximumLength + 1);
if(R_FAILED(showKeyboard))
{
logger::log("Error showing keyboard: 0x%X", showKeyboard);
logger::log("Error showing keyboard: 0x%X: %s", showKeyboard, inputBuffer.data());
}
return std::string(inputBuffer);
// Free keyboard
swkbdClose(&config);
return std::string(inputBuffer.data());
}

21
src/system/timer.cpp Normal file
View File

@ -0,0 +1,21 @@
#include <SDL2/SDL.h>
#include "system/timer.hpp"
sys::timer::timer(const uint32_t &triggerTicks) : m_TriggerTicks(triggerTicks)
{
// Save starting ticks
m_StartingTicks = SDL_GetTicks();
}
bool sys::timer::triggered(void)
{
// Get current ticks
uint32_t currentTicks = SDL_GetTicks();
// If amount is greater or equal, triggered
if(currentTicks - m_StartingTicks >= m_TriggerTicks)
{
m_StartingTicks = currentTicks;
return true;
}
return false;
}

82
src/ui/popMessage.cpp Normal file
View File

@ -0,0 +1,82 @@
#include <vector>
#include <SDL2/SDL.h>
#include "config.hpp"
#include "graphics/graphics.hpp"
#include "ui/ui.hpp"
#include "ui/popMessage.hpp"
static const int POPMESSAGE_X_COORDINATE = 24;
static const int POPMESSAGE_Y_COORDINATE = 640;
namespace
{
// Queue to be processed. Needed to avoid threading graphics issues.
std::vector<ui::popMessage::pMessage> s_MessageQueue;
// Actual vector of messages
std::vector<ui::popMessage::pMessage> s_Messages;
}
void ui::popMessage::newMessage(const std::string &newMessage, const int &tickCount)
{
// Create message to push to processing queue
ui::popMessage::pMessage newPopMessage =
{
.message = newMessage,
.messageTimer = std::make_unique<sys::timer>(tickCount),
.rectangleWidth = 0, // Will be calculated at update.
.y = 720
};
// Push it to queue
s_MessageQueue.push_back(std::move(newPopMessage));
}
void ui::popMessage::update(void)
{
// Loop through queue to make sure they're processed on main thread
for(ui::popMessage::pMessage &pMessage : s_MessageQueue)
{
// Calculate width of message rectangle
pMessage.rectangleWidth = graphics::systemFont::getTextWidth(pMessage.message, 24) + 32;
// Move it to actual message vector
s_Messages.push_back(std::move(pMessage));
}
// Clear queue
s_MessageQueue.clear();
// Need animation scaling to calculate positions
float animationScaling = config::getAnimationScaling();
// Loop through message vector like this because we need current offset
// targetY is starting target coordinate for messages to be displayed vertically upward
int targetY = POPMESSAGE_Y_COORDINATE;
for(unsigned int i = 0; i < s_Messages.size(); i++, targetY -= 52)
{
// Get reference to message we're working with
ui::popMessage::pMessage &currentMessage = s_Messages.at(i);
// If it has expired, erase, continue
if(currentMessage.messageTimer->triggered())
{
s_Messages.erase(s_Messages.begin() + i);
continue;
}
// Calculate updated Y position
if(currentMessage.y != targetY)
{
currentMessage.y += (targetY - currentMessage.y) / animationScaling;
}
}
}
void ui::popMessage::render(void)
{
// Loop and render messages. Drawn directly to framebuffer
for(ui::popMessage::pMessage &pMessage : s_Messages)
{
ui::renderDialogBox(NULL, POPMESSAGE_X_COORDINATE, pMessage.y, pMessage.rectangleWidth, 44);
graphics::systemFont::renderText(pMessage.message, NULL, POPMESSAGE_X_COORDINATE + 16, pMessage.y + 10, 24, COLOR_WHITE);
}
}