mirror of
https://github.com/risingPhil/PokeMe64.git
synced 2026-04-23 07:27:17 -05:00
Add SelectFileScene and connect everything together.
This commit is contained in:
parent
d5e8c05ad8
commit
82fbcd8b86
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ enum class DataCopyOperation
|
|||
typedef struct DataCopySceneContext
|
||||
{
|
||||
DataCopyOperation operation;
|
||||
ManagedString saveToRestorePath;
|
||||
} DataCopySceneContext;
|
||||
|
||||
class DataCopyScene : public SceneWithProgressBar
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ enum class SceneType
|
|||
STATS,
|
||||
TEST,
|
||||
POKETRANSPORTER_GB_REF,
|
||||
SELECT_FILE,
|
||||
COPY_DATA,
|
||||
ABOUT
|
||||
};
|
||||
|
|
|
|||
59
include/scenes/SelectFileScene.h
Executable file
59
include/scenes/SelectFileScene.h
Executable 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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*)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
152
src/scenes/SelectFileScene.cpp
Executable 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;
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user