Minor work: Block home menu and exiting from tasks.

This commit is contained in:
J-D-K 2025-01-09 21:21:05 -05:00
parent 24e73971ff
commit 95cc75c41d
11 changed files with 153 additions and 63 deletions

View File

@ -5,7 +5,7 @@
class AppState
{
public:
AppState(void) = default;
AppState(bool IsClosable = true) : m_IsClosable(IsClosable) {};
virtual ~AppState() {};
virtual void Update(void) = 0;
@ -23,16 +23,19 @@ class AppState
return m_IsActive;
}
// Deactivates state and allows JKSV to kill it.
void Deactivate(void)
{
m_IsActive = false;
}
// Tells state it has the current focus of the app.
void GiveFocus(void)
{
m_HasFocus = true;
}
// Takes focus away from the state.
void TakeFocus(void)
{
m_HasFocus = false;
@ -44,9 +47,17 @@ class AppState
return m_HasFocus;
}
// Returns whether or not state is closable with +.
bool IsClosable(void) const
{
return m_IsClosable;
}
private:
// Whether state is still active or can be purged.
bool m_IsActive = true;
// Whether or not state has focus
bool m_HasFocus = false;
// Whether or not state should allow exiting JKSV
bool m_IsClosable = false;
};

View File

@ -6,39 +6,71 @@
#include "Input.hpp"
#include "JKSV.hpp"
#include "SDL.hpp"
#include "Strings.hpp"
#include "System/Task.hpp"
#include "UI/RenderFunctions.hpp"
#include <memory>
#include <string>
#include <switch.h>
#include <tuple>
// To do: I didn't want to accomplish this with structs, but templating it was holding me up too much.
struct ConfirmStruct
{
};
template <typename TaskType, typename StateType>
template <typename TaskType, typename StateType, typename StructType>
class ConfirmState : public AppState
{
public:
// All functions using confirmation must follow this signature. The struct must be cast to the intended type for now.
using TaskFunction = void (*)(TaskType *, std::shared_ptr<ConfirmStruct>);
// All functions using confirmation must follow this signature.
using TaskFunction = void (*)(TaskType *, std::shared_ptr<StructType>);
// Constructor
ConfirmState(std::string_view QueryString, bool HoldRequired, TaskFunction Function, std::shared_ptr<ConfirmStruct> DataStruct)
: m_QueryString(QueryString.data()), m_Hold(HoldRequired), m_Function(Function), m_DataStruct(DataStruct) {};
ConfirmState(std::string_view QueryString, bool HoldRequired, TaskFunction Function, std::shared_ptr<StructType> DataStruct)
: AppState(false), m_QueryString(QueryString.data()), m_YesString(Strings::GetByName(Strings::Names::YesNo, 0)),
m_Hold(HoldRequired), m_Function(Function), m_DataStruct(DataStruct)
{
appletBeginBlockingHomeButton(0);
}
~ConfirmState() {};
~ConfirmState()
{
appletEndBlockingHomeButton();
}
void Update(void)
{
if (Input::ButtonPressed(HidNpadButton_A))
if (Input::ButtonPressed(HidNpadButton_A) && !m_Hold)
{
AppState::Deactivate();
JKSV::PushState(std::make_shared<StateType>(m_Function, m_DataStruct));
}
else if (Input::ButtonPressed(HidNpadButton_A) && m_Hold)
{
// Get the starting tick count and change the Yes string to the first holding string.
m_StartingTickCount = SDL_GetTicks64();
m_YesString = Strings::GetByName(Strings::Names::HoldingStrings, 0);
}
else if (Input::ButtonHeld(HidNpadButton_A) && m_Hold)
{
uint64_t TickCount = SDL_GetTicks64() - m_StartingTickCount;
// If the TickCount is >= 3 seconds, confirmed. Else, just change the string so we can see we're not holding for nothing?
if (TickCount >= 3000)
{
AppState::Deactivate();
JKSV::PushState(std::make_shared<StateType>(m_Function, m_DataStruct));
}
else if (TickCount >= 2000)
{
m_YesString = Strings::GetByName(Strings::Names::HoldingStrings, 2);
}
else if (TickCount >= 1000)
{
m_YesString = Strings::GetByName(Strings::Names::HoldingStrings, 1);
}
}
else if (Input::ButtonReleased(HidNpadButton_A))
{
m_YesString = Strings::GetByName(Strings::Names::YesNo, 0);
}
else if (Input::ButtonPressed(HidNpadButton_B))
{
// Just deactivate and don't do anything.
AppState::Deactivate();
}
}
@ -54,15 +86,23 @@ class ConfirmState : public AppState
// Fake buttons. Maybe real later.
SDL::RenderLine(NULL, 280, 454, 999, 454, Colors::White);
SDL::RenderLine(NULL, 640, 454, 640, 517, Colors::White);
// To do: Position this better. Currently brought over from old code.
int YesX = 458 - SDL::Text::GetWidth(22, m_YesString.c_str());
SDL::Text::Render(NULL, YesX, 478, 22, SDL::Text::NO_TEXT_WRAP, Colors::White, m_YesString.c_str());
SDL::Text::Render(NULL, 782, 478, 22, SDL::Text::NO_TEXT_WRAP, Colors::White, Strings::GetByName(Strings::Names::YesNo, 1));
}
private:
// Query string
std::string m_QueryString;
// Yes string.
std::string m_YesString;
// Whether or not holding is required to confirm.
bool m_Hold;
// For tick counting/holding
uint64_t m_StartingTickCount = 0;
// Function
TaskFunction m_Function;
// Shared ptr to data to send to confirmation function.
std::shared_ptr<ConfirmStruct> m_DataStruct;
std::shared_ptr<StructType> m_DataStruct;
};

View File

@ -2,14 +2,22 @@
#include "AppStates/AppState.hpp"
#include "System/ProgressTask.hpp"
#include <string>
#include <switch.h>
class ProgressState : public AppState
{
public:
template <typename... Args>
ProgressState(void (*Function)(System::ProgressTask *, Args...), Args... Arguments)
: m_Task(Function, std::forward<Args>(Arguments)...){};
~ProgressState() {};
: AppState(false), m_Task(Function, std::forward<Args>(Arguments)...)
{
appletBeginBlockingHomeButton(0);
}
~ProgressState()
{
appletEndBlockingHomeButton();
}
void Update(void);
void Render(void);

View File

@ -20,9 +20,10 @@ class SaveCreateState : public AppState
// Pointer to user and title select
Data::User *m_User;
TitleSelectCommon *m_TitleSelect;
// Menu
UI::Menu m_SaveMenu;
// Vector of pointers to save info we're using
std::vector<Data::TitleInfo *> m_TitleInfoVector;
// All instances shared these so they're static.
// All instances shared this so they're static.
static inline std::unique_ptr<UI::SlideOutPanel> m_SlidePanel = nullptr;
static inline std::unique_ptr<UI::Menu> m_SaveDataMenu = nullptr;
};

View File

@ -1,13 +1,22 @@
#pragma once
#include "AppStates/AppState.hpp"
#include "System/Task.hpp"
#include <switch.h>
class TaskState : public AppState
{
public:
template <typename... Args>
TaskState(void (*Function)(System::Task *, Args...), Args... Arguments) : m_Task(Function, std::forward<Args>(Arguments)...){};
~TaskState() {};
TaskState(void (*Function)(System::Task *, Args...), Args... Arguments)
: AppState(false), m_Task(Function, std::forward<Args>(Arguments)...)
{
appletBeginBlockingHomeButton(0);
}
~TaskState()
{
appletEndBlockingHomeButton();
}
void Update(void);
void Render(void);

View File

@ -16,6 +16,8 @@ namespace Strings
static constexpr std::string_view MainMenuNames = "MainMenuNames";
static constexpr std::string_view SettingsMenu = "SettingsMenu";
static constexpr std::string_view ExtrasMenu = "ExtrasMenu";
static constexpr std::string_view YesNo = "YesNo";
static constexpr std::string_view HoldingStrings = "HoldingStrings";
static constexpr std::string_view OnOff = "OnOff";
static constexpr std::string_view BackupMenu = "BackupMenu";
static constexpr std::string_view CopyingFiles = "CopyingFiles";

View File

@ -49,6 +49,15 @@
"Terminate Process",
"Mount System Save"
],
"YesNo": [
"Yes [A]",
"No [B]"
],
"HoldingStrings": [
"Hold [A]",
"Keep Holding [A]",
"Almost There! [A]"
],
"OnOff": [
"Off",
">On>"

View File

@ -19,7 +19,7 @@
#include <cstring>
// This struct is used to pass data to Restore, Delete, and upload.
struct TargetStruct : ConfirmStruct
struct TargetStruct
{
FsLib::Path TargetPath;
uint64_t JournalSize = 0;
@ -44,7 +44,7 @@ static void CreateNewBackup(System::ProgressTask *Task, FsLib::Path DestinationP
Task->Finished();
}
static void RestoreBackup(System::ProgressTask *Task, std::shared_ptr<ConfirmStruct> DataStruct)
static void RestoreBackup(System::ProgressTask *Task, std::shared_ptr<TargetStruct> DataStruct)
{
// Wipe the save root first.
if (!FsLib::DeleteDirectoryRecursively(FS::DEFAULT_SAVE_PATH))
@ -54,50 +54,45 @@ static void RestoreBackup(System::ProgressTask *Task, std::shared_ptr<ConfirmStr
return;
}
// Cast struct to what we really need.
std::shared_ptr<TargetStruct> Data = std::static_pointer_cast<TargetStruct>(DataStruct);
if (FsLib::DirectoryExists(Data->TargetPath))
if (FsLib::DirectoryExists(DataStruct->TargetPath))
{
FS::CopyDirectory(Data->TargetPath, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
FS::CopyDirectory(DataStruct->TargetPath, FS::DEFAULT_SAVE_PATH, DataStruct->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
}
else if (std::strstr(Data->TargetPath.CString(), ".zip") != NULL)
else if (std::strstr(DataStruct->TargetPath.CString(), ".zip") != NULL)
{
unzFile TargetZip = unzOpen64(Data->TargetPath.CString());
unzFile TargetZip = unzOpen64(DataStruct->TargetPath.CString());
if (!TargetZip)
{
Logger::Log("Error opening zip for reading.");
Task->Finished();
return;
}
FS::CopyZipToDirectory(TargetZip, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
FS::CopyZipToDirectory(TargetZip, FS::DEFAULT_SAVE_PATH, DataStruct->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
unzClose(TargetZip);
}
else
{
FS::CopyFile(Data->TargetPath, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
FS::CopyFile(DataStruct->TargetPath, FS::DEFAULT_SAVE_PATH, DataStruct->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
}
Task->Finished();
}
static void DeleteBackup(System::Task *Task, std::shared_ptr<ConfirmStruct> DataStruct)
static void DeleteBackup(System::Task *Task, std::shared_ptr<TargetStruct> DataStruct)
{
std::shared_ptr<TargetStruct> Data = std::static_pointer_cast<TargetStruct>(DataStruct);
if (Task)
{
Task->SetStatus(Strings::GetByName(Strings::Names::DeletingFiles, 0), Data->TargetPath.CString());
Task->SetStatus(Strings::GetByName(Strings::Names::DeletingFiles, 0), DataStruct->TargetPath.CString());
}
if (FsLib::DirectoryExists(Data->TargetPath) && !FsLib::DeleteDirectoryRecursively(Data->TargetPath))
if (FsLib::DirectoryExists(DataStruct->TargetPath) && !FsLib::DeleteDirectoryRecursively(DataStruct->TargetPath))
{
Logger::Log("Error deleting folder backup: %s", FsLib::GetErrorString());
}
else if (!FsLib::DeleteFile(Data->TargetPath))
else if (!FsLib::DeleteFile(DataStruct->TargetPath))
{
Logger::Log("Error deleting backup: %s", FsLib::GetErrorString());
}
Data->CreatingState->RefreshListing();
DataStruct->CreatingState->RefreshListing();
Task->Finished();
}
@ -163,14 +158,18 @@ void BackupMenuState::Update(void)
{
int Selected = m_BackupMenu->GetSelected() - 1;
std::shared_ptr<ConfirmStruct> DataStruct = std::make_shared<TargetStruct>();
std::static_pointer_cast<TargetStruct>(DataStruct)->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
std::static_pointer_cast<TargetStruct>(DataStruct)->JournalSize = m_TitleInfo->GetJournalSize(m_SaveType);
std::shared_ptr<TargetStruct> DataStruct(new TargetStruct);
DataStruct->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
DataStruct->JournalSize = m_TitleInfo->GetJournalSize(m_SaveType);
std::string QueryString =
StringUtil::GetFormattedString(Strings::GetByName(Strings::Names::BackupMenuConfirmations, 0), m_DirectoryListing[Selected]);
JKSV::PushState(std::make_shared<ConfirmState<System::ProgressTask, ProgressState>>(QueryString, false, RestoreBackup, DataStruct));
JKSV::PushState(std::make_shared<ConfirmState<System::ProgressTask, ProgressState, TargetStruct>>(
QueryString,
Config::GetByKey(Config::Keys::HoldForRestoration),
RestoreBackup,
DataStruct));
}
else if (Input::ButtonPressed(HidNpadButton_X) && m_BackupMenu->GetSelected() > 0)
{
@ -178,16 +177,19 @@ void BackupMenuState::Update(void)
int Selected = m_BackupMenu->GetSelected() - 1;
// Create struct to pass.
std::shared_ptr<ConfirmStruct> DataStruct = std::make_shared<TargetStruct>();
std::static_pointer_cast<TargetStruct>(DataStruct)->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
std::static_pointer_cast<TargetStruct>(DataStruct)->CreatingState = this;
std::shared_ptr<TargetStruct> DataStruct(new TargetStruct);
DataStruct->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
DataStruct->CreatingState = this;
// Get the string.
std::string QueryString =
StringUtil::GetFormattedString(Strings::GetByName(Strings::Names::BackupMenuConfirmations, 1), m_DirectoryListing[Selected]);
// Create/push new state.
JKSV::PushState(std::make_shared<ConfirmState<System::Task, TaskState>>(QueryString, false, DeleteBackup, DataStruct));
JKSV::PushState(std::make_shared<ConfirmState<System::Task, TaskState, TargetStruct>>(QueryString,
Config::GetByKey(Config::Keys::HoldForDeletion),
DeleteBackup,
DataStruct));
}
else if (Input::ButtonPressed(HidNpadButton_B))
{

View File

@ -79,18 +79,16 @@ static void CreateSaveDataFor(System::Task *Task, Data::User *TargetUser, Data::
Task->Finished();
}
SaveCreateState::SaveCreateState(Data::User *TargetUser, TitleSelectCommon *TitleSelect) : m_User(TargetUser), m_TitleSelect(TitleSelect)
SaveCreateState::SaveCreateState(Data::User *TargetUser, TitleSelectCommon *TitleSelect)
: m_User(TargetUser), m_TitleSelect(TitleSelect), m_SaveMenu(8, 8, 624, 22, 720)
{
// If these aren't null, just return since they're already initialized.
if (m_SlidePanel && m_SaveDataMenu)
// If the panel is null, create it.
if (!m_SlidePanel)
{
return;
// Create panel and menu.
m_SlidePanel = std::make_unique<UI::SlideOutPanel>(640, UI::SlideOutPanel::Side::Right);
}
// Create panel and menu.
m_SlidePanel = std::make_unique<UI::SlideOutPanel>(640, UI::SlideOutPanel::Side::Right);
m_SaveDataMenu = std::make_unique<UI::Menu>(8, 8, 624, 22, 720);
// Get title info vector and copy titles to menu.
Data::GetTitleInfoByType(FsSaveDataType_Account, m_TitleInfoVector);
@ -99,26 +97,35 @@ SaveCreateState::SaveCreateState(Data::User *TargetUser, TitleSelectCommon *Titl
for (size_t i = 0; i < m_TitleInfoVector.size(); i++)
{
m_SaveDataMenu->AddOption(m_TitleInfoVector.at(i)->GetTitle());
m_SaveMenu.AddOption(m_TitleInfoVector.at(i)->GetTitle());
}
}
void SaveCreateState::Update(void)
{
m_SlidePanel->Update(AppState::HasFocus());
m_SaveDataMenu->Update(AppState::HasFocus());
m_SaveMenu.Update(AppState::HasFocus());
if (Input::ButtonPressed(HidNpadButton_A))
{
Data::TitleInfo *TargetTitle = m_TitleInfoVector.at(m_SaveDataMenu->GetSelected());
Data::TitleInfo *TargetTitle = m_TitleInfoVector.at(m_SaveMenu.GetSelected());
JKSV::PushState(std::make_shared<TaskState>(CreateSaveDataFor, m_User, TargetTitle));
}
else if (Input::ButtonPressed(HidNpadButton_B))
{
m_SlidePanel->Close();
}
else if (m_SlidePanel->IsClosed())
{
m_SlidePanel->Reset();
AppState::Deactivate();
}
}
void SaveCreateState::Render(void)
{
// Clear slide target, render menu, render slide to frame buffer.
m_SlidePanel->ClearTarget();
m_SaveDataMenu->Render(m_SlidePanel->Get(), AppState::HasFocus());
m_SaveMenu.Render(m_SlidePanel->Get(), AppState::HasFocus());
m_SlidePanel->Render(NULL, AppState::HasFocus());
}

View File

@ -126,7 +126,7 @@ void JKSV::Update(void)
{
Input::Update();
if (Input::ButtonPressed(HidNpadButton_Plus))
if (Input::ButtonPressed(HidNpadButton_Plus) && !m_StateVector.empty() && m_StateVector.back()->IsClosable())
{
m_IsRunning = false;
}
@ -165,7 +165,10 @@ void JKSV::Render(void)
Strings::GetByName(Strings::Names::TranslationInfo, 0),
Strings::GetByName(Strings::Names::TranslationInfo, 1));
}
// Build date
SDL::Text::Render(NULL, 8, 700, 14, SDL::Text::NO_TEXT_WRAP, Colors::White, "v. %02d.%02d.%04d", BUILD_MON, BUILD_DAY, BUILD_YEAR);
// State render loop.
if (!m_StateVector.empty())
{
for (auto &CurrentState : m_StateVector)
@ -174,8 +177,6 @@ void JKSV::Render(void)
}
}
SDL::Text::Render(NULL, 8, 700, 14, SDL::Text::NO_TEXT_WRAP, Colors::White, "v. %02d.%02d.%04d", BUILD_MON, BUILD_DAY, BUILD_YEAR);
SDL::FrameEnd();
}

View File

@ -5,7 +5,7 @@
int main(void)
{
JKSV Jksv{};
while (Jksv.IsRunning())
while (appletMainLoop() && Jksv.IsRunning())
{
Jksv.Update();
Jksv.Render();