Add SelectFileScene and connect everything together.

This commit is contained in:
Philippe Symons 2024-09-10 00:09:26 +02:00
parent d5e8c05ad8
commit 82fbcd8b86
13 changed files with 316 additions and 64 deletions

View File

@ -30,6 +30,26 @@ typedef struct Rectangle
int height;
} Rectangle;
/**
* @brief This class wraps a raw char pointer.
* This is to allow for automatic memory management without all of the bloat
* of std::string. This is useful to pass it as part of a SceneContext and have
* the string automatically released when the context gets deleted.
*/
class ManagedString
{
public:
ManagedString(char* rawString);
~ManagedString();
const char* get() const;
void operator = (char* rawString);
protected:
private:
char* rawString_;
};
/**
* Whether or not the rectangle has a size of 0.
*/

View File

@ -17,6 +17,7 @@ enum class DataCopyOperation
typedef struct DataCopySceneContext
{
DataCopyOperation operation;
ManagedString saveToRestorePath;
} DataCopySceneContext;
class DataCopyScene : public SceneWithProgressBar

View File

@ -20,6 +20,7 @@ enum class SceneType
STATS,
TEST,
POKETRANSPORTER_GB_REF,
SELECT_FILE,
COPY_DATA,
ABOUT
};

View File

@ -0,0 +1,59 @@
#ifndef _SELECTFILESCENE_H
#define _SELECTFILESCENE_H
#include "scenes/SceneWithDialogWidget.h"
#include "widget/FileBrowserWidget.h"
typedef struct SelectFileSceneContext
{
struct {
const char* text;
TextRenderSettings renderSettings;
Rectangle bounds;
} title;
struct {
SceneType type;
void* context;
void (*deleteContextFunc)(void*);
} nextScene;
// initial path. If left NULL, it will default to sd:/
const char* initialPath;
// file extension filter. if set, only files with the specified extension will be shown
const char* fileExtensionFilter;
// HACK: indicates that -instead of showing this scene- we want to navigate back to the previous scene instead
// this is useful for influencing back behaviour after the DataCopyScene is done.
// we want to end up back in the backup menu, so we need to go back twice in the scene history.
// This was the easiest option, because the main menu is game specific (and therefore much more difficult to go to directly)
// and so is the backup menu.
bool goBackToPreviousSceneInstead;
} SelectFileSceneContext;
class SelectFileScene : public SceneWithDialogWidget
{
public:
SelectFileScene(SceneDependencies& deps, void* sceneContext);
virtual ~SelectFileScene();
void init() override;
void destroy() override;
void render(RDPQGraphics& gfx, const Rectangle& sceneBounds) override;
void onDialogDone();
void onFileConfirmed(const char* path);
void showDialog(DialogData* diagData) override;
protected:
void setupDialog(DialogWidgetStyle& style) override;
private:
FileBrowserWidget fileBrowser_;
WidgetFocusChainSegment fileBrowserFocusSegment_;
DialogData diag_;
SelectFileSceneContext* context_;
sprite_t* dialogWidgetBackgroundSprite_;
};
void deleteSelectFileSceneContext(void* context);
#endif

View File

@ -11,28 +11,7 @@
typedef struct FileBrowserWidgetStyle
{
struct {
sprite_t* sprite;
SpriteRenderSettings renderSettings;
} background;
struct {
/**
* @brief left margin -> the widgets will start rendering after this x spacing offset from the left edge of the list
*/
int left;
/**
* @brief right margin -> the widgets will stop rendering x pixels from the right of this list
*/
int right;
/**
* @brief top margin -> the widgets will start rendering after this y spacing offset from the top edge of the list
*/
int top;
/**
* @brief bottom margin -> the widgets will stop rendering y pixels from the bottom edge of this list.
*/
int bottom;
} margin;
VerticalListStyle listStyle;
MenuItemStyle itemStyle;
} FileBrowserWidgetStyle;

View File

@ -1,5 +1,32 @@
#include "core/common.h"
#include <cstdlib>
ManagedString::ManagedString(char* rawString)
: rawString_(rawString)
{
}
ManagedString::~ManagedString()
{
free(rawString_);
rawString_ = nullptr;
}
const char* ManagedString::get() const
{
return rawString_;
}
void ManagedString::operator = (char* rawString)
{
if(rawString_)
{
free(rawString_);
}
rawString_ = rawString;
}
bool isZeroSizeRectangle(const Rectangle &rect)
{
return (!rect.width || !rect.height);

View File

@ -27,10 +27,6 @@ MenuItemData gen1MenuEntries[] = {
{
.title = "About",
.onConfirmAction = goToAboutScene
},
{
.title = "Test",
.onConfirmAction = goToTestScene
}
};
@ -60,10 +56,6 @@ MenuItemData gen2MenuEntries[] = {
{
.title = "About",
.onConfirmAction = goToAboutScene
},
{
.title = "Test",
.onConfirmAction = goToTestScene
}
};
@ -97,10 +89,6 @@ MenuItemData gen2CrystalMenuEntries[] = {
{
.title = "About",
.onConfirmAction = goToAboutScene
},
{
.title = "Test",
.onConfirmAction = goToTestScene
}
};

View File

@ -4,6 +4,7 @@
#include "scenes/DistributionPokemonListScene.h"
#include "scenes/StatsScene.h"
#include "scenes/MenuScene.h"
#include "scenes/SelectFileScene.h"
#include "scenes/SceneManager.h"
#include "gen2/Gen2GameReader.h"
#include "transferpak/TransferPakManager.h"
@ -142,11 +143,27 @@ void goToDataCopyScene(void* context, const void* param)
const DataCopyOperation operation = (*((const DataCopyOperation*)param));
SceneManager& sceneManager = scene->getDependencies().sceneManager;
auto sceneContext = new DataCopySceneContext{
.operation = operation
auto dataCopyContext = new DataCopySceneContext{
.operation = operation,
.saveToRestorePath = nullptr
};
sceneManager.switchScene(SceneType::COPY_DATA, deleteDataCopySceneContext, sceneContext);
if(operation == DataCopyOperation::RESTORE_SAVE)
{
auto fileSelectContext = new SelectFileSceneContext{
.nextScene = {
.type = SceneType::COPY_DATA,
.context = dataCopyContext,
.deleteContextFunc = deleteDataCopySceneContext
},
.fileExtensionFilter = ".sav"
};
sceneManager.switchScene(SceneType::SELECT_FILE, deleteSelectFileSceneContext, fileSelectContext);
}
else
{
sceneManager.switchScene(SceneType::COPY_DATA, deleteDataCopySceneContext, dataCopyContext);
}
}
void goToGen1DistributionPokemonMenu(void* context, const void*)

View File

@ -3,6 +3,11 @@
#include "scenes/SceneManager.h"
#include "menu/MenuFunctions.h"
#include <system.h>
//missing function declaration in libdragons' system.h, but the definition exists in system.c
int mkdir( const char * path, mode_t mode );
/**
* Copying from or to the transfer pak is a blocking operation.
* So while we're doing that, we can't render anything.
@ -49,6 +54,9 @@ DataCopyScene::~DataCopyScene()
void DataCopyScene::init()
{
char savOutputPath[4096];
char romOutputPath[4096];
char gameTitle[12];
dialogWidgetSprite_ = sprite_load("rom://menu-bg-9slice.sprite");
progressBackgroundSprite_ = sprite_load("rom://bg-nineslice-transparant-border.sprite");
@ -70,12 +78,19 @@ void DataCopyScene::init()
return;
}
const char* savOutputPath = "sd:/gb_out.sav";
const char* romOutputPath = "sd:/rom.gb";
mkdir("sd:/PokeMe64", 0777);
gameboy_cartridge_header gbHeader;
deps_.tpakManager.readCartridgeHeader(gbHeader);
// the title field of the gameboy header is likely truncated.
// create a copy and make sure to append a null character so we won't crash when trying to use it as a string
memcpy(gameTitle, gbHeader.new_title.title, 11);
gameTitle[11] = '\0';
snprintf(savOutputPath, sizeof(savOutputPath) - 1, "sd:/PokeMe64/%s.sav", gameTitle);
snprintf(romOutputPath, sizeof(savOutputPath) - 1, "sd:/PokeMe64/%s.gbc", gameTitle);
auto msg2 = new DialogData{
.shouldDeleteWhenDone = true
};
@ -95,7 +110,7 @@ void DataCopyScene::init()
setDialogDataText(*msg2, "The cartridge rom was backed up to %s!", romOutputPath);
break;
case DataCopyOperation::RESTORE_SAVE:
copySource_ = new TransferPakFileCopySource(savOutputPath);
copySource_ = new TransferPakFileCopySource(sceneContext_->saveToRestorePath.get());
copyDestination_ = new TransferPakSaveManagerDestination(saveManager_);
totalBytesToCopy_ = convertSRAMSizeIntoNumBytes(gbHeader.ram_size_code);
setDialogDataText(*msg2, "The save file was restored to the cartridge!", romOutputPath);
@ -106,7 +121,7 @@ void DataCopyScene::init()
{
if(sceneContext_->operation == DataCopyOperation::RESTORE_SAVE)
{
setDialogDataText(diag_, "ERROR: Could not read from file %s!", savOutputPath);
setDialogDataText(diag_, "ERROR: Could not read from file %s!", sceneContext_->saveToRestorePath.get());
}
else
{

View File

@ -5,6 +5,7 @@
#include "scenes/AboutScene.h"
#include "scenes/InitTransferPakScene.h"
#include "scenes/DistributionPokemonListScene.h"
#include "scenes/SelectFileScene.h"
#include "scenes/DataCopyScene.h"
#include <libdragon.h>
@ -141,6 +142,9 @@ void SceneManager::loadScene()
case SceneType::POKETRANSPORTER_GB_REF:
scene_ = new PokeTransporterGBRefScene(sceneDeps_, newSceneContext_);
break;
case SceneType::SELECT_FILE:
scene_ = new SelectFileScene(sceneDeps_, newSceneContext_);
break;
case SceneType::COPY_DATA:
scene_ = new DataCopyScene(sceneDeps_, newSceneContext_);
break;
@ -151,6 +155,7 @@ void SceneManager::loadScene()
break;
}
newSceneType_ = SceneType::NONE;
if(!scene_)
{
scene_ = oldScene;
@ -166,7 +171,6 @@ void SceneManager::loadScene()
}
scene_->init();
newSceneType_ = SceneType::NONE;
}
void SceneManager::unloadScene(IScene* scene)

152
src/scenes/SelectFileScene.cpp Executable file
View File

@ -0,0 +1,152 @@
#include "scenes/SelectFileScene.h"
#include "scenes/SceneManager.h"
#include "scenes/DataCopyScene.h"
static const Rectangle fileBrowserBounds = {20, 20, 280, 200};
static void fileConfirmedCallback(void* context, const char* path)
{
auto scene = (SelectFileScene*)context;
scene->onFileConfirmed(path);
}
static void dialogFinishedCallback(void* context)
{
auto scene = (SelectFileScene*)context;
scene->onDialogDone();
}
SelectFileScene::SelectFileScene(SceneDependencies& deps, void* context)
: SceneWithDialogWidget(deps)
, fileBrowser_(deps.animationManager)
, fileBrowserFocusSegment_{
.current = &fileBrowser_
}
, diag_({0})
, context_((SelectFileSceneContext*)context)
, dialogWidgetBackgroundSprite_(nullptr)
{
}
SelectFileScene::~SelectFileScene()
{
}
void SelectFileScene::init()
{
if(context_->goBackToPreviousSceneInstead)
{
// the context indicates that we should go back to the previous scene instead.
// this is likely because we ended up back here because DataCopyScene is done
// and we want to go back to the backup menu.
deps_.sceneManager.goBackToPreviousScene();
return;
}
dialogWidgetBackgroundSprite_ = sprite_load("rom://menu-bg-9slice.sprite");
SceneWithDialogWidget::init();
const FileBrowserWidgetStyle browserStyle = {
.listStyle = {
.background = {
.sprite = dialogWidgetBackgroundSprite_,
.spriteSettings = {
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = {6, 6, 6, 6}
}
},
.margin = {
.top = 5
}
},
.itemStyle = {
.size = {280, 16},
.titleNotFocused = {
.fontId = arialId_,
.fontStyleId = fontStyleWhiteId_
},
.titleFocused = {
.fontId = arialId_,
.fontStyleId = fontStyleYellowId_
},
.leftMargin = 10,
.topMargin = 1
}
};
setFocusChain(&fileBrowserFocusSegment_);
fileBrowser_.setBounds(fileBrowserBounds);
fileBrowser_.setStyle(browserStyle);
fileBrowser_.setItemConfirmedCallback(fileConfirmedCallback, this);
fileBrowser_.setFileExtensionToFilter(context_->fileExtensionFilter);
fileBrowser_.setPath((context_->initialPath) ? context_->initialPath : "sd:/");
}
void SelectFileScene::destroy()
{
SceneWithDialogWidget::destroy();
if(dialogWidgetBackgroundSprite_)
{
sprite_free(dialogWidgetBackgroundSprite_);
dialogWidgetBackgroundSprite_ = nullptr;
}
}
void SelectFileScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds)
{
fileBrowser_.render(gfx, sceneBounds);
SceneWithDialogWidget::render(gfx, sceneBounds);
if(context_->title.text && !isZeroSizeRectangle(context_->title.bounds))
{
const Rectangle absoluteTextBounds = addOffset(context_->title.bounds, sceneBounds);
gfx.drawText(absoluteTextBounds, context_->title.text, context_->title.renderSettings);
}
}
void SelectFileScene::onDialogDone()
{
deps_.sceneManager.goBackToPreviousScene();
}
void SelectFileScene::onFileConfirmed(const char* path)
{
if(context_->nextScene.type == SceneType::COPY_DATA)
{
auto nextSceneContext = (DataCopySceneContext*)context_->nextScene.context;
nextSceneContext->saveToRestorePath = strdup(path);
}
context_->goBackToPreviousSceneInstead = true;
deps_.sceneManager.switchScene(context_->nextScene.type, context_->nextScene.deleteContextFunc, context_->nextScene.context);
}
void SelectFileScene::showDialog(DialogData* diagData)
{
SceneWithDialogWidget::showDialog(diagData);
fileBrowser_.setVisible(false);
setFocusChain(&dialogFocusChainSegment_);
}
void SelectFileScene::setupDialog(DialogWidgetStyle& style)
{
style.background.sprite = dialogWidgetBackgroundSprite_;
style.background.spriteSettings = {
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = { 6, 6, 6, 6 }
};
SceneWithDialogWidget::setupDialog(style);
dialogWidget_.setOnDialogFinishedCallback(dialogFinishedCallback, this);
dialogWidget_.setVisible(false);
}
void deleteSelectFileSceneContext(void* context)
{
auto sceneContext = (SelectFileSceneContext*)context;
delete sceneContext;
}

View File

@ -38,16 +38,18 @@ void TestScene::init()
SceneWithDialogWidget::init();
const FileBrowserWidgetStyle browserStyle = {
.background = {
.sprite = dialogWidgetBackgroundSprite_,
.renderSettings = {
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = {6, 6, 6, 6}
.listStyle = {
.background = {
.sprite = dialogWidgetBackgroundSprite_,
.spriteSettings = {
.renderMode = SpriteRenderMode::NINESLICE,
.srcRect = {6, 6, 6, 6}
}
},
.margin = {
.top = 5
}
},
.margin = {
.top = 5
},
.itemStyle = {
.size = {280, 16},
.titleNotFocused = {

View File

@ -79,20 +79,7 @@ Dimensions FileBrowserWidget::getSize() const
void FileBrowserWidget::setStyle(const FileBrowserWidgetStyle& style)
{
style_ = style;
const VerticalListStyle listStyle = {
.background = {
.sprite = style.background.sprite,
.spriteSettings = style.background.renderSettings
},
.margin = {
.left = style.margin.left,
.right = style.margin.right,
.top = style.margin.top,
.bottom = style.margin.bottom
},
};
listWidget_.setStyle(listStyle);
listWidget_.setStyle(style.listStyle);
}
bool FileBrowserWidget::handleUserInput(const joypad_inputs_t& userInput)