新增根据帧数控制可见性
This commit is contained in:
305
client/core/library/EntityJson.cpp
Normal file
305
client/core/library/EntityJson.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include "library/EntityJson.h"
|
||||
|
||||
#include <functional>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
namespace {
|
||||
|
||||
QJsonArray pointToJson(const QPointF& p) {
|
||||
return QJsonArray{p.x(), p.y()};
|
||||
}
|
||||
|
||||
bool pointFromJson(const QJsonValue& v, QPointF& out) {
|
||||
if (!v.isArray()) {
|
||||
return false;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
if (a.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
out = QPointF(a.at(0).toDouble(), a.at(1).toDouble());
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonArray pointsToJson(const QVector<QPointF>& pts) {
|
||||
QJsonArray a;
|
||||
for (const auto& p : pts) {
|
||||
a.append(pointToJson(p));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
bool pointsFromJson(const QJsonValue& v, QVector<QPointF>& out) {
|
||||
out.clear();
|
||||
if (!v.isArray()) {
|
||||
return false;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
out.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
QPointF p;
|
||||
if (!pointFromJson(it, p)) {
|
||||
return false;
|
||||
}
|
||||
out.push_back(p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
QJsonArray vecToJson(const QVector<T>& v, const std::function<QJsonObject(const T&)>& fn) {
|
||||
QJsonArray a;
|
||||
for (const auto& x : v) {
|
||||
a.append(fn(x));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QJsonObject entityToJson(const core::Project::Entity& e) {
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("id"), e.id);
|
||||
o.insert(QStringLiteral("displayName"), e.displayName);
|
||||
o.insert(QStringLiteral("visible"), e.visible);
|
||||
o.insert(QStringLiteral("polygonLocal"), pointsToJson(e.polygonLocal));
|
||||
o.insert(QStringLiteral("cutoutPolygonWorld"), pointsToJson(e.cutoutPolygonWorld));
|
||||
o.insert(QStringLiteral("originWorld"), pointToJson(e.originWorld));
|
||||
o.insert(QStringLiteral("depth"), e.depth);
|
||||
o.insert(QStringLiteral("imagePath"), e.imagePath);
|
||||
o.insert(QStringLiteral("imageTopLeftWorld"), pointToJson(e.imageTopLeftWorld));
|
||||
o.insert(QStringLiteral("userScale"), e.userScale);
|
||||
o.insert(QStringLiteral("distanceScaleCalibMult"), e.distanceScaleCalibMult);
|
||||
o.insert(QStringLiteral("ignoreDistanceScale"), e.ignoreDistanceScale);
|
||||
o.insert(QStringLiteral("parentId"), e.parentId);
|
||||
o.insert(QStringLiteral("parentOffsetWorld"), pointToJson(e.parentOffsetWorld));
|
||||
|
||||
o.insert(QStringLiteral("entityPayloadPath"), e.entityPayloadPath);
|
||||
o.insert(QStringLiteral("legacyAnimSidecarPath"), e.legacyAnimSidecarPath);
|
||||
|
||||
o.insert(QStringLiteral("locationKeys"),
|
||||
vecToJson<core::Project::Entity::KeyframeVec2>(
|
||||
e.locationKeys, [](const core::Project::Entity::KeyframeVec2& k) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("value"), pointToJson(k.value));
|
||||
return ko;
|
||||
}));
|
||||
|
||||
o.insert(QStringLiteral("depthScaleKeys"),
|
||||
vecToJson<core::Project::Entity::KeyframeFloat01>(
|
||||
e.depthScaleKeys, [](const core::Project::Entity::KeyframeFloat01& k) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("value"), k.value);
|
||||
return ko;
|
||||
}));
|
||||
|
||||
o.insert(QStringLiteral("userScaleKeys"),
|
||||
vecToJson<core::Project::Entity::KeyframeDouble>(
|
||||
e.userScaleKeys, [](const core::Project::Entity::KeyframeDouble& k) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("value"), k.value);
|
||||
return ko;
|
||||
}));
|
||||
|
||||
o.insert(QStringLiteral("imageFrames"),
|
||||
vecToJson<core::Project::Entity::ImageFrame>(
|
||||
e.imageFrames, [](const core::Project::Entity::ImageFrame& k) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("imagePath"), k.imagePath);
|
||||
return ko;
|
||||
}));
|
||||
|
||||
o.insert(QStringLiteral("visibilityKeys"),
|
||||
vecToJson<core::Project::ToolKeyframeBool>(
|
||||
e.visibilityKeys, [](const core::Project::ToolKeyframeBool& k) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("value"), k.value);
|
||||
return ko;
|
||||
}));
|
||||
|
||||
{
|
||||
QJsonObject intro;
|
||||
intro.insert(QStringLiteral("title"), e.intro.title);
|
||||
intro.insert(QStringLiteral("bodyText"), e.intro.bodyText);
|
||||
QJsonArray imgs;
|
||||
for (const auto& p : e.intro.imagePathsRelative) {
|
||||
imgs.append(p);
|
||||
}
|
||||
intro.insert(QStringLiteral("imagePathsRelative"), imgs);
|
||||
intro.insert(QStringLiteral("videoPathRelative"), e.intro.videoPathRelative);
|
||||
o.insert(QStringLiteral("intro"), intro);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
bool entityFromJson(const QJsonObject& o, core::Project::Entity& out) {
|
||||
core::Project::Entity e;
|
||||
e.id = o.value(QStringLiteral("id")).toString();
|
||||
e.displayName = o.value(QStringLiteral("displayName")).toString();
|
||||
e.visible = o.value(QStringLiteral("visible")).toBool(true);
|
||||
if (!pointsFromJson(o.value(QStringLiteral("polygonLocal")), e.polygonLocal)) {
|
||||
return false;
|
||||
}
|
||||
if (!pointsFromJson(o.value(QStringLiteral("cutoutPolygonWorld")), e.cutoutPolygonWorld)) {
|
||||
// cutout 允许不存在:按空处理
|
||||
e.cutoutPolygonWorld.clear();
|
||||
}
|
||||
{
|
||||
QPointF p;
|
||||
if (!pointFromJson(o.value(QStringLiteral("originWorld")), p)) {
|
||||
p = QPointF();
|
||||
}
|
||||
e.originWorld = p;
|
||||
}
|
||||
e.depth = o.value(QStringLiteral("depth")).toInt(0);
|
||||
e.imagePath = o.value(QStringLiteral("imagePath")).toString();
|
||||
{
|
||||
QPointF p;
|
||||
if (!pointFromJson(o.value(QStringLiteral("imageTopLeftWorld")), p)) {
|
||||
p = QPointF();
|
||||
}
|
||||
e.imageTopLeftWorld = p;
|
||||
}
|
||||
e.userScale = o.value(QStringLiteral("userScale")).toDouble(1.0);
|
||||
e.distanceScaleCalibMult = o.value(QStringLiteral("distanceScaleCalibMult")).toDouble(0.0);
|
||||
e.ignoreDistanceScale = o.value(QStringLiteral("ignoreDistanceScale")).toBool(false);
|
||||
e.parentId = o.value(QStringLiteral("parentId")).toString();
|
||||
{
|
||||
QPointF p;
|
||||
if (!pointFromJson(o.value(QStringLiteral("parentOffsetWorld")), p)) {
|
||||
p = QPointF();
|
||||
}
|
||||
e.parentOffsetWorld = p;
|
||||
}
|
||||
e.entityPayloadPath = o.value(QStringLiteral("entityPayloadPath")).toString();
|
||||
e.legacyAnimSidecarPath = o.value(QStringLiteral("legacyAnimSidecarPath")).toString();
|
||||
|
||||
auto parseKeyframesVec2 = [&](const QString& key, QVector<core::Project::Entity::KeyframeVec2>& dst) -> bool {
|
||||
dst.clear();
|
||||
const QJsonValue v = o.value(key);
|
||||
if (!v.isArray()) {
|
||||
return true;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
dst.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
if (!it.isObject()) return false;
|
||||
const QJsonObject ko = it.toObject();
|
||||
core::Project::Entity::KeyframeVec2 k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
QPointF pv;
|
||||
if (!pointFromJson(ko.value(QStringLiteral("value")), pv)) {
|
||||
return false;
|
||||
}
|
||||
k.value = pv;
|
||||
dst.push_back(k);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parseKeyframesFloat01 = [&](const QString& key, QVector<core::Project::Entity::KeyframeFloat01>& dst) -> bool {
|
||||
dst.clear();
|
||||
const QJsonValue v = o.value(key);
|
||||
if (!v.isArray()) {
|
||||
return true;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
dst.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
if (!it.isObject()) return false;
|
||||
const QJsonObject ko = it.toObject();
|
||||
core::Project::Entity::KeyframeFloat01 k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.value = ko.value(QStringLiteral("value")).toDouble(0.5);
|
||||
dst.push_back(k);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parseKeyframesDouble = [&](const QString& key, QVector<core::Project::Entity::KeyframeDouble>& dst) -> bool {
|
||||
dst.clear();
|
||||
const QJsonValue v = o.value(key);
|
||||
if (!v.isArray()) {
|
||||
return true;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
dst.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
if (!it.isObject()) return false;
|
||||
const QJsonObject ko = it.toObject();
|
||||
core::Project::Entity::KeyframeDouble k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.value = ko.value(QStringLiteral("value")).toDouble(1.0);
|
||||
dst.push_back(k);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto parseImageFrames = [&](const QString& key, QVector<core::Project::Entity::ImageFrame>& dst) -> bool {
|
||||
dst.clear();
|
||||
const QJsonValue v = o.value(key);
|
||||
if (!v.isArray()) {
|
||||
return true;
|
||||
}
|
||||
const QJsonArray a = v.toArray();
|
||||
dst.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
if (!it.isObject()) return false;
|
||||
const QJsonObject ko = it.toObject();
|
||||
core::Project::Entity::ImageFrame k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.imagePath = ko.value(QStringLiteral("imagePath")).toString();
|
||||
dst.push_back(k);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!parseKeyframesVec2(QStringLiteral("locationKeys"), e.locationKeys)) return false;
|
||||
if (!parseKeyframesFloat01(QStringLiteral("depthScaleKeys"), e.depthScaleKeys)) return false;
|
||||
if (!parseKeyframesDouble(QStringLiteral("userScaleKeys"), e.userScaleKeys)) return false;
|
||||
if (!parseImageFrames(QStringLiteral("imageFrames"), e.imageFrames)) return false;
|
||||
|
||||
// visibilityKeys:可缺省(默认永远可见)
|
||||
e.visibilityKeys.clear();
|
||||
if (o.value(QStringLiteral("visibilityKeys")).isArray()) {
|
||||
const QJsonArray a = o.value(QStringLiteral("visibilityKeys")).toArray();
|
||||
e.visibilityKeys.reserve(a.size());
|
||||
for (const auto& it : a) {
|
||||
if (!it.isObject()) return false;
|
||||
const QJsonObject ko = it.toObject();
|
||||
core::Project::ToolKeyframeBool k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.value = ko.value(QStringLiteral("value")).toBool(true);
|
||||
e.visibilityKeys.push_back(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (o.contains(QStringLiteral("intro")) && o.value(QStringLiteral("intro")).isObject()) {
|
||||
const QJsonObject intro = o.value(QStringLiteral("intro")).toObject();
|
||||
e.intro.title = intro.value(QStringLiteral("title")).toString();
|
||||
e.intro.bodyText = intro.value(QStringLiteral("bodyText")).toString();
|
||||
e.intro.videoPathRelative = intro.value(QStringLiteral("videoPathRelative")).toString();
|
||||
e.intro.imagePathsRelative.clear();
|
||||
if (intro.value(QStringLiteral("imagePathsRelative")).isArray()) {
|
||||
const QJsonArray imgs = intro.value(QStringLiteral("imagePathsRelative")).toArray();
|
||||
e.intro.imagePathsRelative.reserve(imgs.size());
|
||||
for (const auto& iv : imgs) {
|
||||
e.intro.imagePathsRelative.push_back(iv.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out = e;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
13
client/core/library/EntityJson.h
Normal file
13
client/core/library/EntityJson.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "domain/Project.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
QJsonObject entityToJson(const core::Project::Entity& e);
|
||||
bool entityFromJson(const QJsonObject& o, core::Project::Entity& out);
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
19
client/core/library/OnlineResourceLibraryProvider.cpp
Normal file
19
client/core/library/OnlineResourceLibraryProvider.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "library/OnlineResourceLibraryProvider.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
OnlineResourceLibraryProvider::OnlineResourceLibraryProvider(QObject* parent)
|
||||
: ResourceLibraryProvider(parent) {}
|
||||
|
||||
void OnlineResourceLibraryProvider::fetchResourcesAsync() {
|
||||
// 在线资源预留:当前不返回假数据。
|
||||
// 未来接入真实服务时保持“多次调用 fetchResourcesAsync()”用于分页即可。
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
emit resourcesReady({});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
16
client/core/library/OnlineResourceLibraryProvider.h
Normal file
16
client/core/library/OnlineResourceLibraryProvider.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "library/ResourceLibraryProvider.h"
|
||||
|
||||
namespace core::library {
|
||||
|
||||
/// 在线资源库(预留):当前不做真实网络请求,仅提供接口占位。
|
||||
class OnlineResourceLibraryProvider final : public ResourceLibraryProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OnlineResourceLibraryProvider(QObject* parent = nullptr);
|
||||
void fetchResourcesAsync() override;
|
||||
};
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
95
client/core/library/ResourceLibraryProvider.cpp
Normal file
95
client/core/library/ResourceLibraryProvider.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "library/ResourceLibraryProvider.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
FakeResourceLibraryProvider::FakeResourceLibraryProvider(QObject* parent)
|
||||
: ResourceLibraryProvider(parent) {}
|
||||
|
||||
void FakeResourceLibraryProvider::fetchResourcesAsync() {
|
||||
// 预留:未来在这里用 QNetworkAccessManager 请求在线资源库。
|
||||
// 当前返回内置假数据,并保持异步语义,避免 UI 假设同步返回。
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
QVector<LibraryResource> out;
|
||||
|
||||
{
|
||||
LibraryResource r;
|
||||
r.resourceId = QStringLiteral("demo/tree");
|
||||
r.displayName = QStringLiteral("树(示例资源)");
|
||||
r.kind = LibraryResource::Kind::Entity;
|
||||
r.imageSize = QSize(220, 260);
|
||||
r.accent = QColor(46, 204, 113);
|
||||
r.imageOffsetFromOrigin = QPointF(-110, -130);
|
||||
r.entityTemplate.displayName = QStringLiteral("树");
|
||||
r.entityTemplate.visible = true;
|
||||
r.entityTemplate.depth = 160;
|
||||
r.entityTemplate.userScale = 1.0;
|
||||
r.entityTemplate.distanceScaleCalibMult = 0.0;
|
||||
// 以 origin 为中心的简单多边形(局部坐标)
|
||||
r.entityTemplate.polygonLocal = {
|
||||
QPointF(-60, 80), QPointF(-90, 20), QPointF(-60, -80), QPointF(0, -110),
|
||||
QPointF(60, -80), QPointF(90, 20), QPointF(60, 80)};
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
{
|
||||
LibraryResource r;
|
||||
r.resourceId = QStringLiteral("demo/rock");
|
||||
r.displayName = QStringLiteral("岩石(示例资源)");
|
||||
r.kind = LibraryResource::Kind::Entity;
|
||||
r.imageSize = QSize(240, 180);
|
||||
r.accent = QColor(120, 120, 120);
|
||||
r.imageOffsetFromOrigin = QPointF(-120, -90);
|
||||
r.entityTemplate.displayName = QStringLiteral("岩石");
|
||||
r.entityTemplate.visible = true;
|
||||
r.entityTemplate.depth = 90;
|
||||
r.entityTemplate.userScale = 1.0;
|
||||
r.entityTemplate.distanceScaleCalibMult = 0.0;
|
||||
r.entityTemplate.polygonLocal = {
|
||||
QPointF(-100, 10), QPointF(-70, -60), QPointF(0, -80), QPointF(90, -40),
|
||||
QPointF(110, 20), QPointF(40, 70), QPointF(-40, 60)};
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
{
|
||||
LibraryResource r;
|
||||
r.resourceId = QStringLiteral("demo/house");
|
||||
r.displayName = QStringLiteral("小屋(示例资源)");
|
||||
r.kind = LibraryResource::Kind::Entity;
|
||||
r.imageSize = QSize(280, 220);
|
||||
r.accent = QColor(231, 76, 60);
|
||||
r.imageOffsetFromOrigin = QPointF(-140, -110);
|
||||
r.entityTemplate.displayName = QStringLiteral("小屋");
|
||||
r.entityTemplate.visible = true;
|
||||
r.entityTemplate.depth = 200;
|
||||
r.entityTemplate.userScale = 1.0;
|
||||
r.entityTemplate.distanceScaleCalibMult = 0.0;
|
||||
r.entityTemplate.polygonLocal = {
|
||||
QPointF(-120, 90), QPointF(120, 90), QPointF(120, -10), QPointF(0, -120), QPointF(-120, -10)};
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
// —— 对话气泡:工具资源;底边三角形位置在属性里用滑块自调 ——
|
||||
{
|
||||
LibraryResource r;
|
||||
r.resourceId = QStringLiteral("local/bubble");
|
||||
r.displayName = QStringLiteral("对话气泡");
|
||||
r.kind = LibraryResource::Kind::Tool;
|
||||
r.imageSize = QSize(260, 160);
|
||||
r.accent = QColor(120, 150, 255);
|
||||
r.imageOffsetFromOrigin = QPointF(-130, -140);
|
||||
r.toolTemplate.displayName = QStringLiteral("对话气泡");
|
||||
r.toolTemplate.visible = true;
|
||||
r.toolTemplate.type = core::Project::Tool::Type::Bubble;
|
||||
r.toolTemplate.text = QStringLiteral("……");
|
||||
r.toolTemplate.bubblePointerT01 = 0.5;
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
emit resourcesReady(out);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
59
client/core/library/ResourceLibraryProvider.h
Normal file
59
client/core/library/ResourceLibraryProvider.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "domain/Project.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
/// 资源库中的一个“可拖拽实体资源”。未来可来自在线服务器。
|
||||
struct LibraryResource {
|
||||
enum class Kind { Entity, Tool };
|
||||
QString resourceId;
|
||||
QString displayName;
|
||||
|
||||
Kind kind = Kind::Entity;
|
||||
|
||||
/// 拖到画布后用于创建实体的模板(id 可为空,主窗口会自动分配 entity-<n>)。
|
||||
core::Project::Entity entityTemplate;
|
||||
|
||||
/// 拖到画布后用于创建工具的模板(id 可为空,主窗口会自动分配 tool-<n>)。
|
||||
core::Project::Tool toolTemplate;
|
||||
|
||||
/// 资源预览/占位贴图生成参数(当前无真实来源时用)。
|
||||
QSize imageSize = QSize(256, 256);
|
||||
QColor accent = QColor(80, 160, 255);
|
||||
|
||||
/// 贴图相对 originWorld 的偏移(world 坐标;落到画布后会做 originWorld + offset)。
|
||||
QPointF imageOffsetFromOrigin = QPointF(-128, -128);
|
||||
};
|
||||
|
||||
/// 资源库提供者接口:可用假实现占位,后续接入在线服务时替换。
|
||||
class ResourceLibraryProvider : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ResourceLibraryProvider(QObject* parent = nullptr) : QObject(parent) {}
|
||||
~ResourceLibraryProvider() override = default;
|
||||
|
||||
virtual void fetchResourcesAsync() = 0;
|
||||
|
||||
signals:
|
||||
void resourcesReady(const QVector<core::library::LibraryResource>& resources);
|
||||
void resourcesFailed(const QString& error);
|
||||
};
|
||||
|
||||
/// 假资源提供者:返回内置的若干资源,预留接口用于未来在线获取。
|
||||
class FakeResourceLibraryProvider final : public ResourceLibraryProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FakeResourceLibraryProvider(QObject* parent = nullptr);
|
||||
void fetchResourcesAsync() override;
|
||||
};
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
113
client/core/library/ToolJson.cpp
Normal file
113
client/core/library/ToolJson.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "library/ToolJson.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
static QJsonArray pointToJson(const QPointF& p) {
|
||||
QJsonArray a;
|
||||
a.append(p.x());
|
||||
a.append(p.y());
|
||||
return a;
|
||||
}
|
||||
|
||||
static QPointF pointFromJson(const QJsonValue& v) {
|
||||
const QJsonArray a = v.toArray();
|
||||
if (a.size() >= 2) {
|
||||
return QPointF(a.at(0).toDouble(0.0), a.at(1).toDouble(0.0));
|
||||
}
|
||||
return QPointF();
|
||||
}
|
||||
|
||||
QJsonObject toolToJson(const core::Project::Tool& t) {
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("id"), t.id);
|
||||
o.insert(QStringLiteral("displayName"), t.displayName);
|
||||
o.insert(QStringLiteral("visible"), t.visible);
|
||||
o.insert(QStringLiteral("parentId"), t.parentId);
|
||||
o.insert(QStringLiteral("parentOffsetWorld"), pointToJson(t.parentOffsetWorld));
|
||||
o.insert(QStringLiteral("originWorld"), pointToJson(t.originWorld));
|
||||
|
||||
o.insert(QStringLiteral("type"), QStringLiteral("bubble"));
|
||||
o.insert(QStringLiteral("text"), t.text);
|
||||
o.insert(QStringLiteral("fontPx"), t.fontPx);
|
||||
QString align = QStringLiteral("center");
|
||||
if (t.align == core::Project::Tool::TextAlign::Left) align = QStringLiteral("left");
|
||||
if (t.align == core::Project::Tool::TextAlign::Right) align = QStringLiteral("right");
|
||||
o.insert(QStringLiteral("align"), align);
|
||||
o.insert(QStringLiteral("pointerT"), t.bubblePointerT01);
|
||||
|
||||
QJsonArray vis;
|
||||
for (const auto& k : t.visibilityKeys) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("value"), k.value);
|
||||
vis.append(ko);
|
||||
}
|
||||
o.insert(QStringLiteral("visibilityKeys"), vis);
|
||||
|
||||
QJsonArray loc;
|
||||
for (const auto& k : t.locationKeys) {
|
||||
QJsonObject ko;
|
||||
ko.insert(QStringLiteral("frame"), k.frame);
|
||||
ko.insert(QStringLiteral("x"), k.value.x());
|
||||
ko.insert(QStringLiteral("y"), k.value.y());
|
||||
loc.append(ko);
|
||||
}
|
||||
o.insert(QStringLiteral("locationKeys"), loc);
|
||||
return o;
|
||||
}
|
||||
|
||||
bool toolFromJson(const QJsonObject& o, core::Project::Tool& out) {
|
||||
core::Project::Tool t;
|
||||
t.id = o.value(QStringLiteral("id")).toString();
|
||||
t.displayName = o.value(QStringLiteral("displayName")).toString();
|
||||
t.visible = o.value(QStringLiteral("visible")).toBool(true);
|
||||
t.parentId = o.value(QStringLiteral("parentId")).toString();
|
||||
t.parentOffsetWorld = pointFromJson(o.value(QStringLiteral("parentOffsetWorld")));
|
||||
t.originWorld = pointFromJson(o.value(QStringLiteral("originWorld")));
|
||||
|
||||
const QString type = o.value(QStringLiteral("type")).toString(QStringLiteral("bubble"));
|
||||
if (type != QStringLiteral("bubble")) {
|
||||
return false;
|
||||
}
|
||||
t.type = core::Project::Tool::Type::Bubble;
|
||||
t.text = o.value(QStringLiteral("text")).toString();
|
||||
t.fontPx = std::clamp(o.value(QStringLiteral("fontPx")).toInt(18), 8, 120);
|
||||
const QString align = o.value(QStringLiteral("align")).toString(QStringLiteral("center"));
|
||||
if (align == QStringLiteral("left")) t.align = core::Project::Tool::TextAlign::Left;
|
||||
else if (align == QStringLiteral("right")) t.align = core::Project::Tool::TextAlign::Right;
|
||||
else t.align = core::Project::Tool::TextAlign::Center;
|
||||
if (o.contains(QStringLiteral("pointerT"))) {
|
||||
t.bubblePointerT01 = std::clamp(o.value(QStringLiteral("pointerT")).toDouble(0.5), 0.0, 1.0);
|
||||
} else {
|
||||
const QString ptr = o.value(QStringLiteral("pointer")).toString(QStringLiteral("mid"));
|
||||
if (ptr == QStringLiteral("left")) t.bubblePointerT01 = 0.12;
|
||||
else if (ptr == QStringLiteral("right")) t.bubblePointerT01 = 0.88;
|
||||
else t.bubblePointerT01 = 0.5;
|
||||
}
|
||||
|
||||
const QJsonArray vis = o.value(QStringLiteral("visibilityKeys")).toArray();
|
||||
for (const auto& kv : vis) {
|
||||
const QJsonObject ko = kv.toObject();
|
||||
core::Project::ToolKeyframeBool k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.value = ko.value(QStringLiteral("value")).toBool(true);
|
||||
t.visibilityKeys.push_back(k);
|
||||
}
|
||||
const QJsonArray loc = o.value(QStringLiteral("locationKeys")).toArray();
|
||||
for (const auto& kv : loc) {
|
||||
const QJsonObject ko = kv.toObject();
|
||||
core::Project::Entity::KeyframeVec2 k;
|
||||
k.frame = ko.value(QStringLiteral("frame")).toInt(0);
|
||||
k.value = QPointF(ko.value(QStringLiteral("x")).toDouble(0.0), ko.value(QStringLiteral("y")).toDouble(0.0));
|
||||
t.locationKeys.push_back(k);
|
||||
}
|
||||
out = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
13
client/core/library/ToolJson.h
Normal file
13
client/core/library/ToolJson.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "domain/Project.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace core::library {
|
||||
|
||||
QJsonObject toolToJson(const core::Project::Tool& t);
|
||||
bool toolFromJson(const QJsonObject& o, core::Project::Tool& out);
|
||||
|
||||
} // namespace core::library
|
||||
|
||||
Reference in New Issue
Block a user