Add minimal character package loading
This commit is contained in:
@@ -14,6 +14,10 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
|||||||
|
|
||||||
qt_add_executable(QtDesktopPet
|
qt_add_executable(QtDesktopPet
|
||||||
main.cpp
|
main.cpp
|
||||||
|
src/character/CharacterPackage.h
|
||||||
|
src/character/CharacterPackage.cpp
|
||||||
|
src/character/CharacterPackageLoader.h
|
||||||
|
src/character/CharacterPackageLoader.cpp
|
||||||
src/ui/PetWindow.h
|
src/ui/PetWindow.h
|
||||||
src/ui/PetWindow.cpp
|
src/ui/PetWindow.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include "CharacterPackage.h"
|
||||||
|
|
||||||
|
bool CharacterPackage::hasState(const QString &stateName) const
|
||||||
|
{
|
||||||
|
return states.contains(stateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CharacterState *CharacterPackage::state(const QString &stateName) const
|
||||||
|
{
|
||||||
|
auto iterator = states.constFind(stateName);
|
||||||
|
if (iterator == states.constEnd())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iterator.value();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
struct CharacterState
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString relativePath;
|
||||||
|
QStringList framePaths;
|
||||||
|
int fps = 0;
|
||||||
|
bool loop = true;
|
||||||
|
QString nextState;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CharacterPackage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int schemaVersion = 0;
|
||||||
|
QString id;
|
||||||
|
QString displayName;
|
||||||
|
QString author;
|
||||||
|
QString version;
|
||||||
|
QString packagePath;
|
||||||
|
QString previewPath;
|
||||||
|
QString defaultState;
|
||||||
|
QMap<QString, CharacterState> states;
|
||||||
|
|
||||||
|
bool hasState(const QString &stateName) const;
|
||||||
|
const CharacterState *state(const QString &stateName) const;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
#include "CharacterPackageLoader.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr int SupportedSchemaVersion = 1;
|
||||||
|
|
||||||
|
QString requiredString(const QJsonObject &object, const QString &key)
|
||||||
|
{
|
||||||
|
const QJsonValue value = object.value(key);
|
||||||
|
if (!value.isString())
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.toString().trimmed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterPackage CharacterPackageLoader::load(const QString &packagePath, QString *errorMessage)
|
||||||
|
{
|
||||||
|
CharacterPackage package;
|
||||||
|
package.packagePath = QDir::cleanPath(packagePath);
|
||||||
|
|
||||||
|
const QDir packageDirectory(package.packagePath);
|
||||||
|
if (!packageDirectory.exists())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Character package directory does not exist."));
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(packageDirectory.filePath(QStringLiteral("character.json")));
|
||||||
|
if (!file.exists())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("character.json does not exist."));
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Unable to read character.json."));
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError parseError;
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &parseError);
|
||||||
|
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("character.json has invalid format."));
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject root = document.object();
|
||||||
|
package.schemaVersion = root.value(QStringLiteral("schemaVersion")).toInt(0);
|
||||||
|
if (package.schemaVersion != SupportedSchemaVersion)
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Character package schemaVersion is not supported."));
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
package.id = requiredString(root, QStringLiteral("id"));
|
||||||
|
package.displayName = requiredString(root, QStringLiteral("displayName"));
|
||||||
|
package.author = requiredString(root, QStringLiteral("author"));
|
||||||
|
package.version = requiredString(root, QStringLiteral("version"));
|
||||||
|
package.defaultState = requiredString(root, QStringLiteral("defaultState"));
|
||||||
|
|
||||||
|
if (package.id.isEmpty())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Character package id is empty."));
|
||||||
|
return CharacterPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (package.defaultState.isEmpty())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Character package defaultState is empty."));
|
||||||
|
return CharacterPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString previewRelativePath = requiredString(root, QStringLiteral("preview"));
|
||||||
|
if (!previewRelativePath.isEmpty())
|
||||||
|
{
|
||||||
|
package.previewPath = packageDirectory.filePath(previewRelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonValue statesValue = root.value(QStringLiteral("states"));
|
||||||
|
if (!statesValue.isObject() || statesValue.toObject().isEmpty())
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("Character package states is empty."));
|
||||||
|
return CharacterPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject statesObject = statesValue.toObject();
|
||||||
|
for (auto iterator = statesObject.constBegin(); iterator != statesObject.constEnd(); ++iterator)
|
||||||
|
{
|
||||||
|
if (!iterator.value().isObject())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString stateName = iterator.key();
|
||||||
|
const QJsonObject stateObject = iterator.value().toObject();
|
||||||
|
CharacterState state;
|
||||||
|
state.name = stateName;
|
||||||
|
state.relativePath = requiredString(stateObject, QStringLiteral("path"));
|
||||||
|
state.fps = stateObject.value(QStringLiteral("fps")).toInt(0);
|
||||||
|
state.loop = stateObject.value(QStringLiteral("loop")).toBool(true);
|
||||||
|
state.nextState = requiredString(stateObject, QStringLiteral("next"));
|
||||||
|
|
||||||
|
if (state.relativePath.isEmpty() || state.fps <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.framePaths = collectPngFrames(packageDirectory.filePath(state.relativePath));
|
||||||
|
if (state.framePaths.isEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
package.states.insert(state.name, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!package.hasState(package.defaultState))
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("defaultState does not exist or has no usable frames."));
|
||||||
|
return CharacterPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!package.hasState(QStringLiteral("idle")))
|
||||||
|
{
|
||||||
|
setError(errorMessage, QStringLiteral("idle state does not exist or has no usable frames."));
|
||||||
|
return CharacterPackage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CharacterPackageLoader::collectPngFrames(const QString &stateDirectoryPath)
|
||||||
|
{
|
||||||
|
const QDir stateDirectory(stateDirectoryPath);
|
||||||
|
if (!stateDirectory.exists())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const QFileInfoList frameFiles = stateDirectory.entryInfoList(
|
||||||
|
{QStringLiteral("*.png"), QStringLiteral("*.PNG")},
|
||||||
|
QDir::Files,
|
||||||
|
QDir::Name);
|
||||||
|
|
||||||
|
QStringList framePaths;
|
||||||
|
for (const QFileInfo &frameFile : frameFiles)
|
||||||
|
{
|
||||||
|
framePaths.append(frameFile.absoluteFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
return framePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterPackageLoader::setError(QString *errorMessage, const QString &message)
|
||||||
|
{
|
||||||
|
if (errorMessage != nullptr)
|
||||||
|
{
|
||||||
|
*errorMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CharacterPackage.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class CharacterPackageLoader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static CharacterPackage load(const QString &packagePath, QString *errorMessage = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QStringList collectPngFrames(const QString &stateDirectoryPath);
|
||||||
|
static bool setError(QString *errorMessage, const QString &message);
|
||||||
|
};
|
||||||
|
|
||||||
+23
-4
@@ -1,5 +1,7 @@
|
|||||||
#include "PetWindow.h"
|
#include "PetWindow.h"
|
||||||
|
|
||||||
|
#include "../character/CharacterPackageLoader.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
@@ -13,6 +15,11 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
QString characterPackagePath()
|
||||||
|
{
|
||||||
|
return QStringLiteral(PET_SOURCE_DIR) + QStringLiteral("/shiroko");
|
||||||
|
}
|
||||||
|
|
||||||
QString previewImagePath()
|
QString previewImagePath()
|
||||||
{
|
{
|
||||||
return QStringLiteral(PET_SOURCE_DIR) + QStringLiteral("/shiroko/preview.png");
|
return QStringLiteral(PET_SOURCE_DIR) + QStringLiteral("/shiroko/preview.png");
|
||||||
@@ -36,7 +43,7 @@ PetWindow::PetWindow(QWidget *parent)
|
|||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
layout->addWidget(m_imageLabel);
|
layout->addWidget(m_imageLabel);
|
||||||
|
|
||||||
loadPreviewImage();
|
loadInitialImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
void PetWindow::contextMenuEvent(QContextMenuEvent *event)
|
||||||
@@ -98,9 +105,22 @@ void PetWindow::mouseReleaseEvent(QMouseEvent *event)
|
|||||||
QWidget::mouseReleaseEvent(event);
|
QWidget::mouseReleaseEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PetWindow::loadPreviewImage()
|
void PetWindow::loadInitialImage()
|
||||||
|
{
|
||||||
|
QString loadError;
|
||||||
|
const CharacterPackage package = CharacterPackageLoader::load(characterPackagePath(), &loadError);
|
||||||
|
const CharacterState *idleState = package.state(QStringLiteral("idle"));
|
||||||
|
if (idleState != nullptr && !idleState->framePaths.isEmpty())
|
||||||
|
{
|
||||||
|
setDisplayImage(idleState->framePaths.first());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisplayImage(previewImagePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PetWindow::setDisplayImage(const QString &imagePath)
|
||||||
{
|
{
|
||||||
const QString imagePath = previewImagePath();
|
|
||||||
QPixmap pixmap(imagePath);
|
QPixmap pixmap(imagePath);
|
||||||
if (pixmap.isNull())
|
if (pixmap.isNull())
|
||||||
{
|
{
|
||||||
@@ -138,4 +158,3 @@ void PetWindow::setAlwaysOnTop(bool enabled)
|
|||||||
setWindowFlags(flags);
|
setWindowFlags(flags);
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -16,7 +16,8 @@ protected:
|
|||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadPreviewImage();
|
void loadInitialImage();
|
||||||
|
void setDisplayImage(const QString &imagePath);
|
||||||
void setAlwaysOnTop(bool enabled);
|
void setAlwaysOnTop(bool enabled);
|
||||||
|
|
||||||
QLabel *m_imageLabel;
|
QLabel *m_imageLabel;
|
||||||
|
|||||||
Reference in New Issue
Block a user