1. 修改了模型热重载部分逻辑,实现了真正的异步模型热重载
This commit is contained in:
@@ -107,8 +107,15 @@ public:
|
|||||||
* 至于为什么要这样拆分,只是为了适应底层的模型加载函数<br>
|
* 至于为什么要这样拆分,只是为了适应底层的模型加载函数<br>
|
||||||
* 你可以选择再上层封装,将传入的路径拆分为路径和文件名,然后调用本函数即可<br>
|
* 你可以选择再上层封装,将传入的路径拆分为路径和文件名,然后调用本函数即可<br>
|
||||||
*/
|
*/
|
||||||
|
[[deprecated("如需使用,请在单线程中使用本函数")]]
|
||||||
void LoadModelFromPath(const std::string& modelPath, const std::string& fileName);
|
void LoadModelFromPath(const std::string& modelPath, const std::string& fileName);
|
||||||
|
|
||||||
|
// 仅在内存中加载模型,不干扰当前运行状态(供子线程调用)
|
||||||
|
LAppModel* LoadModelInstance(const std::string& modelPath, const std::string& fileName);
|
||||||
|
|
||||||
|
// 将已经加载好的模型应用到系统中(供主线程调用)
|
||||||
|
void MountLoadedModel(LAppModel* model);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 次のシーンに切り替える<br>
|
* @brief 次のシーンに切り替える<br>
|
||||||
* サンプルアプリケーションではモデルセットの切り替えを行う。
|
* サンプルアプリケーションではモデルセットの切り替えを行う。
|
||||||
|
|||||||
+69
-5
@@ -6,8 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "LAppLive2DManager.hpp"
|
#include "LAppLive2DManager.hpp"
|
||||||
#include <stdio.h>
|
#include <cstdio>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <io.h> // 保持 Windows 下继续用旧实现
|
#include <io.h> // 保持 Windows 下继续用旧实现
|
||||||
#include <direct.h>
|
#include <direct.h>
|
||||||
@@ -265,8 +265,8 @@ void LAppLive2DManager::OnUpdate() const
|
|||||||
#include <AppContext.h>
|
#include <AppContext.h>
|
||||||
void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const std::string& fileName)
|
void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const std::string& fileName)
|
||||||
{
|
{
|
||||||
csmString modelPathStr(modelPath.c_str());
|
const csmString modelPathStr(modelPath.c_str());
|
||||||
csmString modelJsonName(fileName.c_str());
|
const csmString modelJsonName(fileName.c_str());
|
||||||
|
|
||||||
ReleaseAllModel(); // 释放当前所有模型 有一个点要注意,在这里先释放然后再Push模型实例,以及加载模型实例
|
ReleaseAllModel(); // 释放当前所有模型 有一个点要注意,在这里先释放然后再Push模型实例,以及加载模型实例
|
||||||
_models.PushBack(new LAppModel()); // 这样在加载的时候都使用的models[0]这一个位置,自行实现模型选择器要注意注意
|
_models.PushBack(new LAppModel()); // 这样在加载的时候都使用的models[0]这一个位置,自行实现模型选择器要注意注意
|
||||||
@@ -275,7 +275,7 @@ void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const st
|
|||||||
// 加载完后根据模型大小来重新设置当前窗口大小
|
// 加载完后根据模型大小来重新设置当前窗口大小
|
||||||
const int width = static_cast<int>(_models[0]->GetModel()->GetCanvasWidthPixel() / 15.0);
|
const int width = static_cast<int>(_models[0]->GetModel()->GetCanvasWidthPixel() / 15.0);
|
||||||
const int height = static_cast<int>(_models[0]->GetModel()->GetCanvasHeightPixel() / 15.0);
|
const int height = static_cast<int>(_models[0]->GetModel()->GetCanvasHeightPixel() / 15.0);
|
||||||
AppContext::GetGLCore()->setWindowSize(width, height);
|
AppContext::GetGLCore()->setWindowSize(width, height); // 获取GLCore上下文
|
||||||
LAppPal::PrintLogLn("[APP]窗口尺寸重新设置为: W: %d H: %d", width, height);
|
LAppPal::PrintLogLn("[APP]窗口尺寸重新设置为: W: %d H: %d", width, height);
|
||||||
/*
|
/*
|
||||||
* 提供一个半透明表示模型的示例。
|
* 提供一个半透明表示模型的示例。
|
||||||
@@ -309,6 +309,70 @@ void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 纯加载函数 将在子线程运行
|
||||||
|
LAppModel* LAppLive2DManager::LoadModelInstance(const std::string& modelPath, const std::string& fileName)
|
||||||
|
{
|
||||||
|
const Csm::csmString modelPathStr(modelPath.c_str());
|
||||||
|
const Csm::csmString modelJsonName(fileName.c_str());
|
||||||
|
|
||||||
|
// 创建新实例
|
||||||
|
auto* instance = new LAppModel();
|
||||||
|
|
||||||
|
// 加载资源 (耗时巨头:IO + OpenGL上传)
|
||||||
|
// 注意:这里需要当前线程已经绑定了有效的 OpenGL Context
|
||||||
|
instance->LoadAssets(modelPathStr.GetRawString(), modelJsonName.GetRawString());
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
// 模型挂载函数 运行在主线程
|
||||||
|
void LAppLive2DManager::MountLoadedModel(LAppModel* model)
|
||||||
|
{
|
||||||
|
if (!model) return; // 模型为空
|
||||||
|
// 释放旧模型
|
||||||
|
ReleaseAllModel();
|
||||||
|
// 加入新模型
|
||||||
|
_models.PushBack(model);
|
||||||
|
|
||||||
|
// 加载完后根据模型大小来重新设置当前窗口大小
|
||||||
|
const int width = static_cast<int>(_models[0]->GetModel()->GetCanvasWidthPixel() / 15.0);
|
||||||
|
const int height = static_cast<int>(_models[0]->GetModel()->GetCanvasHeightPixel() / 15.0);
|
||||||
|
|
||||||
|
// 确保在主线程调用 UI 相关操作
|
||||||
|
if(AppContext::GetGLCore()) {
|
||||||
|
AppContext::GetGLCore()->setWindowSize(width, height);
|
||||||
|
}
|
||||||
|
LAppPal::PrintLogLn("[APP]窗口尺寸重新设置为: W: %d H: %d", width, height);
|
||||||
|
|
||||||
|
// 设置渲染目标等
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 提供一个半透明表示模型的示例。
|
||||||
|
* 如果定义了USE_RENDER_TARGET或USE_MODEL_RENDER_TARGET,
|
||||||
|
* 则将模型绘制到另一个渲染目标上,并将绘制结果作为纹理应用到另一个精灵上。
|
||||||
|
*/
|
||||||
|
#if defined(USE_RENDER_TARGET)
|
||||||
|
// 如果选择将绘制操作在LAppView持有的目标上进行,则选择此选项
|
||||||
|
LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ViewFrameBuffer;
|
||||||
|
#elif defined(USE_MODEL_RENDER_TARGET)
|
||||||
|
// 如果选择将绘制操作在各个LAppModel持有的目标上进行,则选择此选项
|
||||||
|
LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_ModelFrameBuffer;
|
||||||
|
#else
|
||||||
|
// 默认情况下,渲染到主帧缓冲区(通常是直接渲染到屏幕)
|
||||||
|
LAppView::SelectTarget useRenderTarget = LAppView::SelectTarget_None;
|
||||||
|
#endif
|
||||||
|
#if defined(USE_RENDER_TARGET) || defined(USE_MODEL_RENDER_TARGET)
|
||||||
|
// 作为给模型单独设置α(透明度)的示例,创建另一个模型实例并稍微移动其位置
|
||||||
|
_models.PushBack(new LAppModel());
|
||||||
|
_models[1]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());
|
||||||
|
_models[1]->GetModelMatrix()->TranslateX(0.2f);
|
||||||
|
#endif
|
||||||
|
LAppDelegate::GetInstance()->GetView()->SwitchRenderingTarget(useRenderTarget);
|
||||||
|
// 当选择其他渲染目标时的背景清除颜色
|
||||||
|
constexpr float clearColor[3] = { 1.0f, 1.0f, 1.0f };
|
||||||
|
LAppDelegate::GetInstance()->GetView()->SetRenderTargetClearColor(clearColor[0], clearColor[1], clearColor[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LAppLive2DManager::NextScene()
|
void LAppLive2DManager::NextScene()
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-1
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
project(Yosuga VERSION 1.0)
|
project(Yosuga VERSION 1.0)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
@@ -51,6 +51,7 @@ find_package(Qt6 COMPONENTS
|
|||||||
WebSockets
|
WebSockets
|
||||||
Multimedia
|
Multimedia
|
||||||
OpenGLWidgets
|
OpenGLWidgets
|
||||||
|
Concurrent
|
||||||
REQUIRED)
|
REQUIRED)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
@@ -172,6 +173,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
Qt::WebSockets
|
Qt::WebSockets
|
||||||
Qt::Multimedia
|
Qt::Multimedia
|
||||||
Qt::OpenGLWidgets
|
Qt::OpenGLWidgets
|
||||||
|
Qt::Concurrent
|
||||||
)
|
)
|
||||||
|
|
||||||
# 添加头文件
|
# 添加头文件
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // 允许多个窗口使用同一个OpenGL上下文(用于实现Live2D模型的异步加载)
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
eApp->init();
|
eApp->init();
|
||||||
// 设置云母效果图片
|
// 设置云母效果图片
|
||||||
|
|||||||
@@ -239,10 +239,8 @@ void GLCore::setWindowSize(const int w, const int h)
|
|||||||
if (this->width() == w && this->height() == h) {
|
if (this->width() == w && this->height() == h) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用 QWidget::resize 或 setFixedSize 来改变窗口的实际尺寸
|
// 调用 QWidget::resize 或 setFixedSize 来改变窗口的实际尺寸
|
||||||
setFixedSize(w, h);
|
setFixedSize(w, h);
|
||||||
|
|
||||||
// 调用 setFixedSize 会自动触发 QOpenGLWidget 的 resizeEvent,
|
// 调用 setFixedSize 会自动触发 QOpenGLWidget 的 resizeEvent,
|
||||||
// 进而调用 resizeGL(w, h),无需手动调用 resizeGL
|
// 进而调用 resizeGL(w, h),无需手动调用 resizeGL
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,7 @@
|
|||||||
* @brief 模型页面
|
* @brief 模型页面
|
||||||
* 暂时只做最简单功能切换模型
|
* 暂时只做最简单功能切换模型
|
||||||
*/
|
*/
|
||||||
|
#pragma once
|
||||||
#ifndef YOSUGA_MODELPAGE_H
|
|
||||||
#define YOSUGA_MODELPAGE_H
|
|
||||||
|
|
||||||
#include "BasePage.h"
|
#include "BasePage.h"
|
||||||
#include "ElaPushButton.h"
|
#include "ElaPushButton.h"
|
||||||
@@ -20,7 +18,7 @@
|
|||||||
|
|
||||||
class ElaLineEdit;
|
class ElaLineEdit;
|
||||||
class ElaPushButton;
|
class ElaPushButton;
|
||||||
class ModelPage : public BasePage
|
class ModelPage final : public BasePage
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -28,7 +26,7 @@ public:
|
|||||||
|
|
||||||
std::pair<QString, QString> splitPath(const QString& fullPath);
|
std::pair<QString, QString> splitPath(const QString& fullPath);
|
||||||
|
|
||||||
~ModelPage();
|
~ModelPage() override;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -39,12 +37,4 @@ private:
|
|||||||
QUrl modelFileUrl;
|
QUrl modelFileUrl;
|
||||||
QString modelFilePathFirst;
|
QString modelFilePathFirst;
|
||||||
QString modelFilePathSecond;
|
QString modelFilePathSecond;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //YOSUGA_MODELPAGE_H
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ HomePage::HomePage(QWidget* parent)
|
|||||||
urlCard1->setTitlePixelSize(17);
|
urlCard1->setTitlePixelSize(17);
|
||||||
urlCard1->setTitleSpacing(25);
|
urlCard1->setTitleSpacing(25);
|
||||||
urlCard1->setSubTitleSpacing(13);
|
urlCard1->setSubTitleSpacing(13);
|
||||||
urlCard1->setUrl("https://github.com/Misakiotoha/Yosuga");
|
urlCard1->setUrl("https://github.com/Misakityan/Yosuga");
|
||||||
urlCard1->setCardPixmap(QPixmap("Resources/Pic/Others/img.png"));
|
urlCard1->setCardPixmap(QPixmap("Resources/Pic/Others/img.png"));
|
||||||
urlCard1->setTitle("Yosuga Github");
|
urlCard1->setTitle("Yosuga Github");
|
||||||
urlCard1->setSubTitle("Star++!");
|
urlCard1->setSubTitle("Star++!");
|
||||||
@@ -110,10 +110,10 @@ HomePage::HomePage(QWidget* parent)
|
|||||||
Q_EMIT modelShopNavigation();
|
Q_EMIT modelShopNavigation();
|
||||||
});
|
});
|
||||||
ModeShopCard->setCardPixmap(QPixmap("Resources/Pic/Others/Live2D.png"));
|
ModeShopCard->setCardPixmap(QPixmap("Resources/Pic/Others/Live2D.png"));
|
||||||
ModeShopCard->setTitle("模型商店");
|
ModeShopCard->setTitle("模型设置");
|
||||||
ModeShopCard->setSubTitle("属于你的Live2D模型");
|
ModeShopCard->setSubTitle("属于你的Live2D模型");
|
||||||
ModeShopCard->setInteractiveTips("By Misaki");
|
ModeShopCard->setInteractiveTips("By Misaki");
|
||||||
ModeShopCard->setDetailedText("选择你喜欢的Live2D模型,模型来自多个作者,多个平台,有免费也有收费的");
|
ModeShopCard->setDetailedText("选择你喜欢的Live2D模型");
|
||||||
// 音频设置卡片
|
// 音频设置卡片
|
||||||
ElaPopularCard* AudioSettingCard = new ElaPopularCard(this);
|
ElaPopularCard* AudioSettingCard = new ElaPopularCard(this);
|
||||||
connect(AudioSettingCard, &ElaPopularCard::popularCardButtonClicked, this, [=, this]() {
|
connect(AudioSettingCard, &ElaPopularCard::popularCardButtonClicked, this, [=, this]() {
|
||||||
@@ -144,10 +144,6 @@ HomePage::HomePage(QWidget* parent)
|
|||||||
centerVLayout->addLayout(flowLayout);
|
centerVLayout->addLayout(flowLayout);
|
||||||
centerVLayout->addStretch();
|
centerVLayout->addStretch();
|
||||||
addCentralWidget(centralWidget);
|
addCentralWidget(centralWidget);
|
||||||
|
|
||||||
// 初始化提示
|
|
||||||
ElaMessageBar::success(ElaMessageBarType::BottomRight, "Success", "初始化成功!", 2000);
|
|
||||||
qDebug() << "初始化成功";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HomePage::~HomePage()
|
HomePage::~HomePage()
|
||||||
|
|||||||
@@ -3,21 +3,19 @@
|
|||||||
//
|
//
|
||||||
#include "ModelPage.h"
|
#include "ModelPage.h"
|
||||||
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QDebug>
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QtWidgets>
|
|
||||||
#include "ElaComboBox.h"
|
|
||||||
#include "ElaMessageBar.h"
|
#include "ElaMessageBar.h"
|
||||||
#include "ElaScrollPageArea.h"
|
#include "ElaScrollPageArea.h"
|
||||||
#include "ElaText.h"
|
#include "ElaText.h"
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QOffscreenSurface>
|
||||||
|
#include <QFutureWatcher>
|
||||||
#include "LAppLive2DManager.hpp"
|
#include "LAppLive2DManager.hpp"
|
||||||
|
|
||||||
ModelPage::ModelPage(QWidget *parent)
|
ModelPage::ModelPage(QWidget *parent)
|
||||||
: BasePage(parent)
|
: BasePage(parent) {
|
||||||
{
|
|
||||||
// 预览窗口标题
|
// 预览窗口标题
|
||||||
setWindowTitle("ModelPage");
|
setWindowTitle("ModelPage");
|
||||||
|
|
||||||
@@ -40,54 +38,116 @@ ModelPage::ModelPage(QWidget* parent)
|
|||||||
modelSetLayout->addWidget(modelUsePushButton);
|
modelSetLayout->addWidget(modelUsePushButton);
|
||||||
modelSetLayout->addSpacing(10);
|
modelSetLayout->addSpacing(10);
|
||||||
connect(modelChoosePushButton, &ElaPushButton::clicked, this, [this]() {
|
connect(modelChoosePushButton, &ElaPushButton::clicked, this, [this]() {
|
||||||
// 获取当前exe所在目录的本地路径
|
// 创建对话框对象(使用 heap 分配,由 Qt 对象树管理内存)
|
||||||
QString exeDir = QCoreApplication::applicationDirPath();
|
auto *fileDialog = new QFileDialog(this);
|
||||||
// 转换为QUrl格式(自动处理路径分隔符)
|
fileDialog->setWindowTitle("选择模型文件");
|
||||||
QUrl initialDir = QUrl::fromLocalFile(exeDir);
|
fileDialog->setNameFilter("Live2D Model (*.model3.json)");
|
||||||
|
|
||||||
// 打开文件选择对话框
|
// 设置初始目录
|
||||||
modelFileUrl = QFileDialog::getOpenFileUrl(
|
const QString exeDir = QCoreApplication::applicationDirPath(); // 获取当前exe所在目录的本地路径
|
||||||
this,
|
fileDialog->setDirectory(exeDir); // 设置初始目录为当前 exe 所在目录
|
||||||
"选择模型文件",
|
|
||||||
initialDir, // 初始目录为当前目录
|
|
||||||
"*.model3.json");
|
|
||||||
|
|
||||||
// 检查url是否有效
|
// 连接信号:当用户选中文件并点击打开时
|
||||||
|
connect(fileDialog, &QFileDialog::fileSelected, this, [this, fileDialog](const QString &file) {
|
||||||
|
modelFileUrl = QUrl::fromLocalFile(file);
|
||||||
if (!modelFileUrl.isEmpty()) {
|
if (!modelFileUrl.isEmpty()) {
|
||||||
QString t = modelFileUrl.toLocalFile();
|
const QString t = modelFileUrl.toLocalFile();
|
||||||
std::pair<QString, QString> path = this->splitPath(t);
|
const std::pair<QString, QString> path = this->splitPath(t);
|
||||||
this->modelFilePathFirst = path.first;
|
this->modelFilePathFirst = path.first;
|
||||||
this->modelFilePathSecond = path.second;
|
this->modelFilePathSecond = path.second;
|
||||||
this->modelUrlEdit->setText(t);
|
this->modelUrlEdit->setText(t);
|
||||||
|
ElaMessageBar::success(ElaMessageBarType::BottomRight, "模型设置", "模型选择成功", 2000, this);
|
||||||
}
|
}
|
||||||
else{
|
// 用完即弃,自动清理内存
|
||||||
ElaMessageBar::information(ElaMessageBarType::BottomRight, "模型设置", "似乎并没有选择模型", 800.0, this);
|
fileDialog->deleteLater();
|
||||||
}
|
});
|
||||||
|
// 处理取消的情况(防止内存泄漏)
|
||||||
|
connect(fileDialog, &QFileDialog::rejected, fileDialog, &QObject::deleteLater);
|
||||||
|
// 显示对话框(非阻塞,不会卡住主界面)
|
||||||
|
fileDialog->open();
|
||||||
});
|
});
|
||||||
connect(modelUsePushButton, &ElaPushButton::clicked, this, [this]() {
|
connect(modelUsePushButton, &ElaPushButton::clicked, this, [this]() {
|
||||||
if(!modelFileUrl.isEmpty()){
|
// 模型使用
|
||||||
LAppLive2DManager::GetInstance()->LoadModelFromPath(this->modelFilePathFirst.toStdString(), this->modelFilePathSecond.toStdString());
|
if (modelFileUrl.isEmpty()) {
|
||||||
}
|
|
||||||
else{
|
|
||||||
ElaMessageBar::information(ElaMessageBarType::BottomRight, "模型设置", "似乎并没有选择模型", 800.0, this);
|
ElaMessageBar::information(ElaMessageBarType::BottomRight, "模型设置", "似乎并没有选择模型", 800.0, this);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
// UI 状态设置为加载中
|
||||||
|
modelUsePushButton->setEnabled(false); // 禁用使用按钮
|
||||||
|
modelUsePushButton->setText("加载中"); // 修改按钮文本
|
||||||
|
modelChoosePushButton->setEnabled(false); // 禁用选择按钮
|
||||||
|
|
||||||
|
// 获取路径字符串 (必须按值传递给lambda)
|
||||||
|
std::string dir = this->modelFilePathFirst.toStdString();
|
||||||
|
std::string filename = this->modelFilePathSecond.toStdString();
|
||||||
|
|
||||||
|
// 启动异步任务
|
||||||
|
QFuture<LAppModel *> future = QtConcurrent::run([dir, filename]() -> LAppModel * {
|
||||||
|
// 以下代码在子线程执行
|
||||||
|
// 创建临时 OpenGL 上下文
|
||||||
|
auto *context = new QOpenGLContext();
|
||||||
|
// 关键点:设置与全局共享上下文共享 (这样主线程才能看到纹理)
|
||||||
|
context->setShareContext(QOpenGLContext::globalShareContext());
|
||||||
|
if (!context->create()) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// 创建离屏表面 (因为子线程没有窗口,需要一个假的绘制表面)
|
||||||
|
auto *surface = new QOffscreenSurface();
|
||||||
|
surface->setFormat(context->format());
|
||||||
|
surface->create();
|
||||||
|
// 绑定上下文
|
||||||
|
if (!context->makeCurrent(surface)) {
|
||||||
|
delete surface;
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// 执行真正的耗时加载
|
||||||
|
// 调用我们在 Manager 里新写的函数
|
||||||
|
LAppModel *model = LAppLive2DManager::GetInstance()->LoadModelInstance(dir, filename);
|
||||||
|
// 清理子线程资源
|
||||||
|
context->doneCurrent();
|
||||||
|
delete surface;
|
||||||
|
delete context;
|
||||||
|
|
||||||
|
return model;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监控任务结束
|
||||||
|
auto *watcher = new QFutureWatcher<LAppModel *>();
|
||||||
|
connect(watcher, &QFutureWatcher<LAppModel *>::finished, this, [this, watcher]() {
|
||||||
|
// 下面的代码在主线程中执行
|
||||||
|
LAppModel *newModel = watcher->result();
|
||||||
|
if (newModel) {
|
||||||
|
// 调用挂载函数,瞬间完成切换
|
||||||
|
LAppLive2DManager::GetInstance()->MountLoadedModel(newModel);
|
||||||
|
ElaMessageBar::success(ElaMessageBarType::BottomRight, "成功", "模型加载完成", 2000, this);
|
||||||
|
} else {
|
||||||
|
ElaMessageBar::error(ElaMessageBarType::BottomRight, "错误", "模型加载失败 (OpenGL环境异常)", 2000, this);
|
||||||
|
}
|
||||||
|
// 恢复 UI
|
||||||
|
modelUsePushButton->setEnabled(true);
|
||||||
|
modelUsePushButton->setText("使用模型");
|
||||||
|
modelChoosePushButton->setEnabled(true);
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
// 开始监控
|
||||||
|
watcher->setFuture(future);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
QWidget *centralWidget = new QWidget(this);
|
QWidget *centralWidget = new QWidget(this);
|
||||||
centralWidget->setWindowTitle("模型商店");
|
centralWidget->setWindowTitle("模型设置");
|
||||||
QVBoxLayout *centerLayout = new QVBoxLayout(centralWidget);
|
QVBoxLayout *centerLayout = new QVBoxLayout(centralWidget);
|
||||||
centerLayout->addWidget(modelSetArea);
|
centerLayout->addWidget(modelSetArea);
|
||||||
centerLayout->addStretch();
|
centerLayout->addStretch();
|
||||||
centerLayout->setContentsMargins(0, 0, 0, 0);
|
centerLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
addCentralWidget(centralWidget, true, true, 0);
|
addCentralWidget(centralWidget, true, true, 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回 pair<目录路径, 文件名>
|
// 返回 pair<目录路径, 文件名>
|
||||||
std::pair<QString, QString> ModelPage::splitPath(const QString& fullPath)
|
std::pair<QString, QString> ModelPage::splitPath(const QString &fullPath) {
|
||||||
{
|
const QFileInfo fileInfo(fullPath);
|
||||||
QFileInfo fileInfo(fullPath);
|
|
||||||
|
|
||||||
// 获取目录部分(自动处理末尾斜杠)
|
// 获取目录部分(自动处理末尾斜杠)
|
||||||
QString dirPath = fileInfo.dir().absolutePath() + "/";
|
QString dirPath = fileInfo.dir().absolutePath() + "/";
|
||||||
@@ -98,7 +158,5 @@ std::pair<QString, QString> ModelPage::splitPath(const QString& fullPath)
|
|||||||
return {dirPath, fileName};
|
return {dirPath, fileName};
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelPage::~ModelPage()
|
ModelPage::~ModelPage() {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ void Setting::initNavigationBar()
|
|||||||
addPageNode("主页", homePage, ElaIconType::House);
|
addPageNode("主页", homePage, ElaIconType::House);
|
||||||
|
|
||||||
// 添加模型商店节点
|
// 添加模型商店节点
|
||||||
addPageNode("模型商店", modelPage, ElaIconType::Shop);
|
addPageNode("模型设置", modelPage, ElaIconType::Shop);
|
||||||
|
|
||||||
// 添加网络连接设置节点
|
// 添加网络连接设置节点
|
||||||
addPageNode("连接设置", networkPage, ElaIconType::NetworkWired);
|
addPageNode("连接设置", networkPage, ElaIconType::NetworkWired);
|
||||||
|
|||||||
Reference in New Issue
Block a user