update
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "dialogs/AboutWindow.h"
|
||||
#include "dialogs/CancelableTaskDialog.h"
|
||||
#include "dialogs/EntityFinalizeDialog.h"
|
||||
#include "dialogs/BlackholeResolveDialog.h"
|
||||
#include "editor/EditorCanvas.h"
|
||||
#include "editor/EntityCutoutUtils.h"
|
||||
#include "dialogs/ImageCropDialog.h"
|
||||
@@ -10,6 +11,7 @@
|
||||
#include "widgets/ToolOptionPopup.h"
|
||||
#include "params/ParamControls.h"
|
||||
#include "props/BackgroundPropertySection.h"
|
||||
#include "props/BlackholePropertySection.h"
|
||||
#include "props/EntityPropertySection.h"
|
||||
#include "props/ToolPropertySection.h"
|
||||
#include "timeline/TimelineWidget.h"
|
||||
@@ -87,9 +89,9 @@ constexpr int kRightDockMinimumWidth = 80;
|
||||
/// 列宽小于此值时自动隐藏右侧两 dock
|
||||
constexpr int kRightDockAutoHideBelow = 92;
|
||||
/// 右侧 dock 列最大宽度,避免过宽挤占画布
|
||||
constexpr int kRightDockMaximumWidth = 288;
|
||||
constexpr int kRightDockMaximumWidth = 252;
|
||||
/// 属性区表单内容最大宽度(dock 仍可略宽,两侧留白,避免 SpinBox 被拉得过开)
|
||||
constexpr int kPropertyPanelContentMaxWidth = 268;
|
||||
constexpr int kPropertyPanelContentMaxWidth = 232;
|
||||
/// 启动时垂直分割高度:项目树较矮、属性区较高
|
||||
constexpr int kProjectTreeDockStartupHeight = 148;
|
||||
constexpr int kPropertiesDockStartupHeight = 392;
|
||||
@@ -1185,6 +1187,14 @@ void MainWindow::createProjectTreeDock() {
|
||||
showBackgroundContextMenu(m_projectTree->viewport()->mapToGlobal(pos));
|
||||
return;
|
||||
}
|
||||
const QString kind = item->data(0, Qt::UserRole).toString();
|
||||
if (kind == QStringLiteral("blackhole")) {
|
||||
const QString id = item->data(0, Qt::UserRole + 1).toString();
|
||||
if (!id.isEmpty()) {
|
||||
showBlackholeContextMenu(m_projectTree->viewport()->mapToGlobal(pos), id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
connect(m_projectTree, &QTreeWidget::itemClicked, this, &MainWindow::onProjectTreeItemClicked);
|
||||
static_cast<ProjectTreeWidget*>(m_projectTree)->onNodeParentDropRequested =
|
||||
@@ -1280,15 +1290,17 @@ void MainWindow::createProjectTreeDock() {
|
||||
Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
|
||||
m_dockProperties->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable |
|
||||
QDockWidget::DockWidgetClosable);
|
||||
m_dockProperties->setMinimumWidth(236);
|
||||
m_dockProperties->setMinimumWidth(200);
|
||||
|
||||
m_bgPropertySection = new gui::BackgroundPropertySection();
|
||||
m_blackholePropertySection = new gui::BlackholePropertySection();
|
||||
m_entityPropertySection = new gui::EntityPropertySection();
|
||||
m_toolPropertySection = new gui::ToolPropertySection();
|
||||
m_propertyStack = new QStackedWidget();
|
||||
m_propertyStack->setContentsMargins(4, 4, 4, 4);
|
||||
m_propertyStack->setMaximumWidth(kPropertyPanelContentMaxWidth);
|
||||
m_propertyStack->addWidget(m_bgPropertySection);
|
||||
m_propertyStack->addWidget(m_blackholePropertySection);
|
||||
m_propertyStack->addWidget(m_entityPropertySection);
|
||||
m_propertyStack->addWidget(m_toolPropertySection);
|
||||
|
||||
@@ -1336,7 +1348,30 @@ void MainWindow::createProjectTreeDock() {
|
||||
if (m_selectedEntityId.isEmpty() || !m_editorCanvas) return;
|
||||
const double s = m_editorCanvas->selectedCombinedScale();
|
||||
if (s <= 1e-9) return;
|
||||
if (!m_workspace.reanchorEntityPivot(m_selectedEntityId, m_currentFrame, QPointF(x, y), s)) return;
|
||||
QPointF targetPivot(x, y);
|
||||
QString parentId;
|
||||
for (const auto& e : m_workspace.entities()) {
|
||||
if (e.id == m_selectedEntityId) {
|
||||
parentId = e.parentId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parentId.isEmpty()) {
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), m_currentFrame, 10);
|
||||
for (const auto& pe : rf.entities) {
|
||||
if (pe.entity.id == parentId) {
|
||||
targetPivot += pe.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& pt : rf.tools) {
|
||||
if (pt.tool.id == parentId) {
|
||||
targetPivot += pt.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!m_workspace.reanchorEntityPivot(m_selectedEntityId, m_currentFrame, targetPivot, s)) return;
|
||||
refreshEditorPage();
|
||||
refreshDopeSheet();
|
||||
});
|
||||
@@ -1344,8 +1379,31 @@ void MainWindow::createProjectTreeDock() {
|
||||
if (m_selectedEntityId.isEmpty() || !m_editorCanvas) return;
|
||||
const double s = m_editorCanvas->selectedCombinedScale();
|
||||
if (s <= 1e-9) return;
|
||||
QPointF targetCentroid(x, y);
|
||||
QString parentId;
|
||||
for (const auto& e : m_workspace.entities()) {
|
||||
if (e.id == m_selectedEntityId) {
|
||||
parentId = e.parentId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parentId.isEmpty()) {
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), m_currentFrame, 10);
|
||||
for (const auto& pe : rf.entities) {
|
||||
if (pe.entity.id == parentId) {
|
||||
targetCentroid += pe.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& pt : rf.tools) {
|
||||
if (pt.tool.id == parentId) {
|
||||
targetCentroid += pt.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool autoKey = m_chkAutoKeyframe && m_chkAutoKeyframe->isChecked();
|
||||
if (!m_workspace.moveEntityCentroidTo(m_selectedEntityId, m_currentFrame, QPointF(x, y), s, autoKey)) return;
|
||||
if (!m_workspace.moveEntityCentroidTo(m_selectedEntityId, m_currentFrame, targetCentroid, s, autoKey)) return;
|
||||
refreshEditorPage();
|
||||
refreshDopeSheet();
|
||||
});
|
||||
@@ -1410,6 +1468,46 @@ void MainWindow::createProjectTreeDock() {
|
||||
m_workspace.setToolAlign(m_selectedToolId, a);
|
||||
refreshEditorPage();
|
||||
});
|
||||
connect(m_toolPropertySection, &gui::ToolPropertySection::positionEdited, this, [this](double x, double y) {
|
||||
if (m_selectedToolId.isEmpty() || !m_workspace.isOpen()) return;
|
||||
const int f = std::clamp(m_currentFrame, 0, core::Project::kClipFixedFrames - 1);
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), f, 10);
|
||||
QPointF currentWorld;
|
||||
QPointF parentWorld;
|
||||
QString parentId;
|
||||
bool found = false;
|
||||
for (const auto& t : rf.tools) {
|
||||
if (t.tool.id == m_selectedToolId) {
|
||||
currentWorld = t.tool.originWorld;
|
||||
parentId = t.tool.parentId;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return;
|
||||
if (!parentId.isEmpty()) {
|
||||
for (const auto& e : rf.entities) {
|
||||
if (e.entity.id == parentId) {
|
||||
parentWorld = e.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (qFuzzyIsNull(parentWorld.x()) && qFuzzyIsNull(parentWorld.y())) {
|
||||
for (const auto& t : rf.tools) {
|
||||
if (t.tool.id == parentId) {
|
||||
parentWorld = t.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const QPointF targetWorld = parentId.isEmpty() ? QPointF(x, y) : (parentWorld + QPointF(x, y));
|
||||
const QPointF delta = targetWorld - currentWorld;
|
||||
if (qFuzzyIsNull(delta.x()) && qFuzzyIsNull(delta.y())) return;
|
||||
if (!m_workspace.moveToolBy(m_selectedToolId, delta, f, true)) return;
|
||||
refreshEditorPage();
|
||||
refreshDopeSheet();
|
||||
});
|
||||
connect(m_toolPropertySection, &gui::ToolPropertySection::visibleToggled, this, [this](bool on) {
|
||||
if (m_selectedToolId.isEmpty() || !m_workspace.isOpen()) return;
|
||||
const int f = std::clamp(m_currentFrame, 0, core::Project::kClipFixedFrames - 1);
|
||||
@@ -1468,7 +1566,8 @@ void MainWindow::createProjectTreeDock() {
|
||||
}
|
||||
|
||||
void MainWindow::refreshPropertyPanel() {
|
||||
if (!m_bgPropertySection || !m_entityPropertySection || !m_toolPropertySection || !m_propertyStack) {
|
||||
if (!m_bgPropertySection || !m_blackholePropertySection || !m_entityPropertySection ||
|
||||
!m_toolPropertySection || !m_propertyStack) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1556,6 +1655,15 @@ void MainWindow::refreshPropertyPanel() {
|
||||
gui::ToolPropertyUiState st;
|
||||
const int f = std::clamp(m_currentFrame, 0, core::Project::kClipFixedFrames - 1);
|
||||
const auto* clip = activeClipForUi();
|
||||
QString parentId;
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), m_currentFrame, 10);
|
||||
for (const auto& rt : rf.tools) {
|
||||
if (rt.tool.id == m_selectedToolId) {
|
||||
st.position = rt.tool.originWorld;
|
||||
parentId = rt.tool.parentId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& t : m_workspace.tools()) {
|
||||
if (t.id == m_selectedToolId) {
|
||||
st.displayName = t.displayName.isEmpty() ? t.id : t.displayName;
|
||||
@@ -1576,12 +1684,59 @@ void MainWindow::refreshPropertyPanel() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parentId.isEmpty()) {
|
||||
QPointF parentWorld;
|
||||
for (const auto& pe : rf.entities) {
|
||||
if (pe.entity.id == parentId) {
|
||||
parentWorld = pe.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (qFuzzyIsNull(parentWorld.x()) && qFuzzyIsNull(parentWorld.y())) {
|
||||
for (const auto& pt : rf.tools) {
|
||||
if (pt.tool.id == parentId) {
|
||||
parentWorld = pt.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
st.position -= parentWorld;
|
||||
st.parentRelativeMode = true;
|
||||
}
|
||||
m_toolPropertySection->applyState(st);
|
||||
m_propertyStack->setCurrentWidget(m_toolPropertySection);
|
||||
m_dockProperties->setWindowTitle(QStringLiteral("属性 — 工具"));
|
||||
return;
|
||||
}
|
||||
|
||||
const bool holeUi = m_workspace.isOpen() && !m_selectedBlackholeEntityId.isEmpty();
|
||||
if (holeUi) {
|
||||
gui::BlackholePropertyUiState st;
|
||||
for (const auto& e : m_workspace.entities()) {
|
||||
if (e.id != m_selectedBlackholeEntityId) {
|
||||
continue;
|
||||
}
|
||||
st.blackholeName = e.blackholeId.isEmpty() ? QStringLiteral("blackhole-%1").arg(e.id) : e.blackholeId;
|
||||
st.statusText = e.blackholeVisible ? QStringLiteral("否") : QStringLiteral("是");
|
||||
if (e.blackholeResolvedBy == QStringLiteral("copy_background")) {
|
||||
st.methodText = QStringLiteral("复制背景其他区域");
|
||||
} else if (e.blackholeResolvedBy == QStringLiteral("use_original_background")) {
|
||||
st.methodText = QStringLiteral("使用原始背景");
|
||||
} else if (e.blackholeResolvedBy == QStringLiteral("model_inpaint")) {
|
||||
st.methodText = QStringLiteral("模型补全");
|
||||
} else if (e.blackholeResolvedBy == QStringLiteral("pending")) {
|
||||
st.methodText = QStringLiteral("待选择");
|
||||
} else {
|
||||
st.methodText = QStringLiteral("未选择");
|
||||
}
|
||||
break;
|
||||
}
|
||||
m_blackholePropertySection->applyState(st);
|
||||
m_propertyStack->setCurrentWidget(m_blackholePropertySection);
|
||||
m_dockProperties->setWindowTitle(QStringLiteral("属性 — 黑洞"));
|
||||
return;
|
||||
}
|
||||
|
||||
const bool entUi = m_hasSelectedEntity && m_workspace.isOpen() && !m_selectedEntityId.isEmpty() && m_editorCanvas;
|
||||
if (!entUi) {
|
||||
m_entityPropertySection->clearDisconnected();
|
||||
@@ -1595,6 +1750,7 @@ void MainWindow::refreshPropertyPanel() {
|
||||
double userScale = 1.0;
|
||||
bool ignoreDist = false;
|
||||
bool entVisible = true;
|
||||
QString parentId;
|
||||
core::EntityIntroContent intro;
|
||||
const int f = std::clamp(m_currentFrame, 0, core::Project::kClipFixedFrames - 1);
|
||||
const auto* clip = activeClipForUi();
|
||||
@@ -1604,6 +1760,7 @@ void MainWindow::refreshPropertyPanel() {
|
||||
userScale = e.userScale;
|
||||
intro = e.intro;
|
||||
ignoreDist = e.ignoreDistanceScale;
|
||||
parentId = e.parentId;
|
||||
const QVector<core::Project::ToolKeyframeBool> keys =
|
||||
(clip && clip->entityVisibilityKeys.contains(e.id))
|
||||
? clip->entityVisibilityKeys.value(e.id)
|
||||
@@ -1621,6 +1778,27 @@ void MainWindow::refreshPropertyPanel() {
|
||||
QStringLiteral("%1(自动)").arg(m_editorCanvas->selectedDistanceScaleMultiplier(), 0, 'f', 3);
|
||||
st.pivot = m_editorCanvas->selectedAnimatedOriginWorld();
|
||||
st.centroid = m_editorCanvas->selectedEntityCentroidWorld();
|
||||
if (!parentId.isEmpty()) {
|
||||
QPointF parentWorld;
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), m_currentFrame, 10);
|
||||
for (const auto& pe : rf.entities) {
|
||||
if (pe.entity.id == parentId) {
|
||||
parentWorld = pe.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (qFuzzyIsNull(parentWorld.x()) && qFuzzyIsNull(parentWorld.y())) {
|
||||
for (const auto& pt : rf.tools) {
|
||||
if (pt.tool.id == parentId) {
|
||||
parentWorld = pt.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
st.pivot -= parentWorld;
|
||||
st.centroid -= parentWorld;
|
||||
st.parentRelativeMode = true;
|
||||
}
|
||||
st.userScale = userScale;
|
||||
st.ignoreDistanceScale = ignoreDist;
|
||||
st.visible = entVisible;
|
||||
@@ -1646,6 +1824,34 @@ void MainWindow::refreshEntityPropertyPanelFast() {
|
||||
QStringLiteral("%1(自动)").arg(m_editorCanvas->selectedDistanceScaleMultiplier(), 0, 'f', 3);
|
||||
st.pivot = m_editorCanvas->selectedAnimatedOriginWorld();
|
||||
st.centroid = m_editorCanvas->selectedEntityCentroidWorld();
|
||||
QString parentId;
|
||||
for (const auto& e : m_workspace.entities()) {
|
||||
if (e.id == m_selectedEntityId) {
|
||||
parentId = e.parentId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parentId.isEmpty()) {
|
||||
QPointF parentWorld;
|
||||
const auto rf = core::eval::evaluateAtFrame(m_workspace.project(), m_currentFrame, 10);
|
||||
for (const auto& pe : rf.entities) {
|
||||
if (pe.entity.id == parentId) {
|
||||
parentWorld = pe.entity.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (qFuzzyIsNull(parentWorld.x()) && qFuzzyIsNull(parentWorld.y())) {
|
||||
for (const auto& pt : rf.tools) {
|
||||
if (pt.tool.id == parentId) {
|
||||
parentWorld = pt.tool.originWorld;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
st.pivot -= parentWorld;
|
||||
st.centroid -= parentWorld;
|
||||
st.parentRelativeMode = true;
|
||||
}
|
||||
st.userScale = m_editorCanvas->selectedUserScale();
|
||||
// ignoreDistanceScale 在拖动中不变更,fast path 不必更新(避免再遍历 entities)
|
||||
m_entityPropertySection->applyState(st);
|
||||
@@ -1742,6 +1948,33 @@ void MainWindow::refreshProjectTree() {
|
||||
});
|
||||
}
|
||||
|
||||
// 黑洞节点:挂在“背景”下,和实体渲染解耦(黑洞可见性独立于实体可见性)
|
||||
QVector<const core::Project::Entity*> blackholeEnts;
|
||||
blackholeEnts.reserve(sortedEnts.size());
|
||||
for (const auto& e : sortedEnts) {
|
||||
if (!e.cutoutPolygonWorld.isEmpty()) {
|
||||
blackholeEnts.push_back(&e);
|
||||
}
|
||||
}
|
||||
std::stable_sort(blackholeEnts.begin(), blackholeEnts.end(),
|
||||
[](const core::Project::Entity* a, const core::Project::Entity* b) {
|
||||
const QString an = a->displayName.isEmpty() ? a->id : a->displayName;
|
||||
const QString bn = b->displayName.isEmpty() ? b->id : b->displayName;
|
||||
return an < bn;
|
||||
});
|
||||
for (const auto* e : blackholeEnts) {
|
||||
auto* it = new QTreeWidgetItem(m_itemBackground);
|
||||
const QString base = e->displayName.isEmpty() ? e->id : e->displayName;
|
||||
const QString holeName =
|
||||
e->blackholeId.isEmpty() ? QStringLiteral("blackhole-%1").arg(e->id) : e->blackholeId;
|
||||
it->setText(1, QStringLiteral("黑洞 · %1").arg(base));
|
||||
it->setToolTip(1, QStringLiteral("节点:%1").arg(holeName));
|
||||
it->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
|
||||
it->setData(0, Qt::UserRole, QStringLiteral("blackhole"));
|
||||
it->setData(0, Qt::UserRole + 1, e->id); // 绑定实体 id,便于定位 cutout 多边形
|
||||
it->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
struct NodeRef {
|
||||
QString kind; // "entity" / "tool"
|
||||
QString id;
|
||||
@@ -1845,7 +2078,9 @@ void MainWindow::syncProjectTreeFromCanvasSelection() {
|
||||
}
|
||||
m_syncingTreeSelection = true;
|
||||
m_projectTree->blockSignals(true);
|
||||
if ((!m_hasSelectedEntity || m_selectedEntityId.isEmpty()) && (!m_hasSelectedTool || m_selectedToolId.isEmpty())) {
|
||||
if ((!m_hasSelectedEntity || m_selectedEntityId.isEmpty()) &&
|
||||
(!m_hasSelectedTool || m_selectedToolId.isEmpty()) &&
|
||||
m_selectedBlackholeEntityId.isEmpty()) {
|
||||
m_projectTree->clearSelection();
|
||||
} else {
|
||||
QTreeWidgetItem* found = nullptr;
|
||||
@@ -1861,6 +2096,11 @@ void MainWindow::syncProjectTreeFromCanvasSelection() {
|
||||
found = node;
|
||||
break;
|
||||
}
|
||||
if (!m_selectedBlackholeEntityId.isEmpty() && kind == QStringLiteral("blackhole") &&
|
||||
id == m_selectedBlackholeEntityId) {
|
||||
found = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
m_projectTree->setCurrentItem(found);
|
||||
@@ -1882,6 +2122,10 @@ void MainWindow::onProjectTreeItemClicked(QTreeWidgetItem* item, int column) {
|
||||
if (kind == QStringLiteral("entity")) {
|
||||
const QString id = item->data(0, Qt::UserRole + 1).toString();
|
||||
if (!id.isEmpty()) {
|
||||
m_selectedBlackholeEntityId.clear();
|
||||
if (m_editorCanvas) {
|
||||
m_editorCanvas->clearBlackholeSelection();
|
||||
}
|
||||
m_hasSelectedTool = false;
|
||||
m_selectedToolId.clear();
|
||||
if (m_timeline) {
|
||||
@@ -1892,6 +2136,10 @@ void MainWindow::onProjectTreeItemClicked(QTreeWidgetItem* item, int column) {
|
||||
} else if (kind == QStringLiteral("tool")) {
|
||||
const QString id = item->data(0, Qt::UserRole + 1).toString();
|
||||
if (!id.isEmpty()) {
|
||||
m_selectedBlackholeEntityId.clear();
|
||||
if (m_editorCanvas) {
|
||||
m_editorCanvas->clearBlackholeSelection();
|
||||
}
|
||||
m_hasSelectedTool = true;
|
||||
m_selectedToolId = id;
|
||||
m_hasSelectedEntity = false;
|
||||
@@ -1904,10 +2152,28 @@ void MainWindow::onProjectTreeItemClicked(QTreeWidgetItem* item, int column) {
|
||||
}
|
||||
refreshPropertyPanel();
|
||||
}
|
||||
} else if (kind == QStringLiteral("blackhole")) {
|
||||
const QString entityId = item->data(0, Qt::UserRole + 1).toString();
|
||||
if (!entityId.isEmpty()) {
|
||||
m_selectedBlackholeEntityId = entityId;
|
||||
m_hasSelectedTool = false;
|
||||
m_selectedToolId.clear();
|
||||
m_hasSelectedEntity = false;
|
||||
m_selectedEntityId.clear();
|
||||
m_selectedEntityDisplayNameCache.clear();
|
||||
if (m_editorCanvas) {
|
||||
m_editorCanvas->clearEntitySelection();
|
||||
m_editorCanvas->selectBlackholeByEntityId(entityId);
|
||||
}
|
||||
updateTimelineTracks();
|
||||
refreshPropertyPanel();
|
||||
}
|
||||
} else if (kind == QStringLiteral("background")) {
|
||||
m_selectedBlackholeEntityId.clear();
|
||||
m_hasSelectedTool = false;
|
||||
m_selectedToolId.clear();
|
||||
m_editorCanvas->clearEntitySelection();
|
||||
m_editorCanvas->clearBlackholeSelection();
|
||||
updateTimelineTracks();
|
||||
}
|
||||
}
|
||||
@@ -2394,6 +2660,7 @@ void MainWindow::rebuildCentralPages() {
|
||||
connect(m_editorCanvas, &EditorCanvas::selectedEntityChanged, this, [this](bool hasSel, const QString& id, int depth, const QPointF& origin) {
|
||||
m_hasSelectedEntity = hasSel;
|
||||
m_selectedEntityId = id;
|
||||
m_selectedBlackholeEntityId.clear();
|
||||
m_selectedEntityDepth = depth;
|
||||
m_selectedEntityOrigin = origin;
|
||||
m_hasSelectedTool = false;
|
||||
@@ -2418,6 +2685,7 @@ void MainWindow::rebuildCentralPages() {
|
||||
Q_UNUSED(origin);
|
||||
m_hasSelectedTool = hasSel;
|
||||
m_selectedToolId = id;
|
||||
m_selectedBlackholeEntityId.clear();
|
||||
if (hasSel) {
|
||||
m_hasSelectedEntity = false;
|
||||
m_selectedEntityId.clear();
|
||||
@@ -2475,6 +2743,11 @@ void MainWindow::rebuildCentralPages() {
|
||||
}
|
||||
}
|
||||
}
|
||||
ent.blackholeVisible = true;
|
||||
if (ent.blackholeId.isEmpty() && !ent.id.isEmpty()) {
|
||||
ent.blackholeId = QStringLiteral("blackhole-%1").arg(ent.id);
|
||||
}
|
||||
ent.blackholeResolvedBy = QStringLiteral("pending");
|
||||
if (!m_workspace.addEntity(ent, img)) {
|
||||
QMessageBox::warning(this, QStringLiteral("实体"), QStringLiteral("保存实体失败。"));
|
||||
return;
|
||||
@@ -2768,6 +3041,11 @@ void MainWindow::rebuildCentralPages() {
|
||||
if (ent.displayName.isEmpty()) {
|
||||
// 允许空:界面会用 id 展示
|
||||
}
|
||||
ent.blackholeVisible = true;
|
||||
if (ent.blackholeId.isEmpty() && !ent.id.isEmpty()) {
|
||||
ent.blackholeId = QStringLiteral("blackhole-%1").arg(ent.id);
|
||||
}
|
||||
ent.blackholeResolvedBy = QStringLiteral("pending");
|
||||
|
||||
QImage bg(m_workspace.backgroundAbsolutePath());
|
||||
if (!bg.isNull() && bg.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
@@ -2812,6 +3090,26 @@ void MainWindow::rebuildCentralPages() {
|
||||
refreshProjectTree();
|
||||
updateUiEnabledState();
|
||||
});
|
||||
connect(m_editorCanvas, &EditorCanvas::requestResolveBlackholeCopy, this,
|
||||
[this](const QString& entityId, const QPoint& sourceOffsetPx) {
|
||||
if (!m_workspace.resolveBlackholeByCopyBackground(entityId, sourceOffsetPx, true)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
QStringLiteral("黑洞修复"),
|
||||
QStringLiteral("复制背景区域失败。请重新拖动取样框,确保采样区域在背景范围内。"));
|
||||
return;
|
||||
}
|
||||
statusBar()->showMessage(QStringLiteral("黑洞已通过背景复制修复"));
|
||||
refreshProjectTree();
|
||||
updateUiEnabledState();
|
||||
if (m_editorCanvas) {
|
||||
m_editorCanvas->notifyBackgroundContentChanged();
|
||||
}
|
||||
refreshEditorPage();
|
||||
if (m_previewRequested) {
|
||||
refreshPreviewPage();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_editorCanvas, &EditorCanvas::presentationEntityIntroRequested, this,
|
||||
[this](const QString& id, QPointF anchorView) {
|
||||
@@ -3194,7 +3492,8 @@ void MainWindow::refreshDopeSheet() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
addChannel(QStringLiteral("位置"), 0, hasLoc);
|
||||
const QString locLabel = e.parentId.isEmpty() ? QStringLiteral("位置") : QStringLiteral("相对位置");
|
||||
addChannel(locLabel, 0, hasLoc);
|
||||
addChannel(QStringLiteral("缩放"), 1, hasSc);
|
||||
addChannel(QStringLiteral("图像"), 2, hasIm);
|
||||
}
|
||||
@@ -3221,6 +3520,70 @@ void MainWindow::showBackgroundContextMenu(const QPoint& globalPos) {
|
||||
refreshPreviewPage();
|
||||
}
|
||||
|
||||
void MainWindow::showBlackholeContextMenu(const QPoint& globalPos, const QString& entityId) {
|
||||
if (entityId.isEmpty() || !m_workspace.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_selectedBlackholeEntityId = entityId;
|
||||
if (m_editorCanvas) {
|
||||
m_editorCanvas->selectBlackholeByEntityId(entityId);
|
||||
}
|
||||
syncProjectTreeFromCanvasSelection();
|
||||
|
||||
QString holeLabel = entityId;
|
||||
for (const auto& e : m_workspace.entities()) {
|
||||
if (e.id == entityId) {
|
||||
if (!e.blackholeId.isEmpty()) {
|
||||
holeLabel = e.blackholeId;
|
||||
} else {
|
||||
holeLabel = QStringLiteral("blackhole-%1").arg(entityId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QMenu menu(this);
|
||||
QAction* actResolve = menu.addAction(QStringLiteral("修复"));
|
||||
QAction* chosen = menu.exec(globalPos);
|
||||
if (!chosen || chosen != actResolve) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlackholeResolveDialog dlg(holeLabel, this);
|
||||
if (dlg.exec() != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
if (dlg.selectedAlgorithm() == BlackholeResolveDialog::Algorithm::CopyBackgroundRegion) {
|
||||
if (!m_editorCanvas || !m_editorCanvas->startBlackholeCopyResolve(entityId)) {
|
||||
QMessageBox::warning(
|
||||
this,
|
||||
QStringLiteral("黑洞修复"),
|
||||
QStringLiteral("无法进入画布拖动模式,请确认黑洞与背景数据有效。"));
|
||||
return;
|
||||
}
|
||||
statusBar()->showMessage(QStringLiteral("拖动画布中的青色取样框,松开鼠标即应用;Esc 取消"));
|
||||
return;
|
||||
} else {
|
||||
ok = m_workspace.resolveBlackholeByUseOriginalBackground(entityId);
|
||||
if (!ok) {
|
||||
QMessageBox::warning(this, QStringLiteral("黑洞修复"), QStringLiteral("应用“使用原始背景”失败。"));
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
statusBar()->showMessage(QStringLiteral("黑洞修复已应用"));
|
||||
refreshProjectTree();
|
||||
updateUiEnabledState();
|
||||
refreshEditorPage();
|
||||
if (m_previewRequested) {
|
||||
refreshPreviewPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::onNewProject() {
|
||||
if (m_workspace.isOpen()) {
|
||||
|
||||
@@ -34,6 +34,7 @@ class EditorCanvas;
|
||||
class TimelineWidget;
|
||||
namespace gui {
|
||||
class BackgroundPropertySection;
|
||||
class BlackholePropertySection;
|
||||
class EntityPropertySection;
|
||||
class ToolPropertySection;
|
||||
class EntityIntroPopup;
|
||||
@@ -100,6 +101,7 @@ private:
|
||||
|
||||
void showProjectRootContextMenu(const QPoint& globalPos);
|
||||
void showBackgroundContextMenu(const QPoint& globalPos);
|
||||
void showBlackholeContextMenu(const QPoint& globalPos, const QString& entityId);
|
||||
void rebuildCentralPages();
|
||||
void showWelcomePage();
|
||||
void showEditorPage();
|
||||
@@ -125,6 +127,7 @@ private:
|
||||
QComboBox* m_modeSelector = nullptr;
|
||||
QStackedWidget* m_propertyStack = nullptr;
|
||||
gui::BackgroundPropertySection* m_bgPropertySection = nullptr;
|
||||
gui::BlackholePropertySection* m_blackholePropertySection = nullptr;
|
||||
gui::EntityPropertySection* m_entityPropertySection = nullptr;
|
||||
gui::ToolPropertySection* m_toolPropertySection = nullptr;
|
||||
QToolButton* m_btnCreateEntity = nullptr;
|
||||
@@ -172,6 +175,7 @@ private:
|
||||
QPointF m_selectedEntityOrigin;
|
||||
QString m_selectedEntityId;
|
||||
QString m_selectedToolId;
|
||||
QString m_selectedBlackholeEntityId;
|
||||
QString m_selectedEntityDisplayNameCache;
|
||||
QString m_bgAbsCache;
|
||||
QString m_bgSizeTextCache;
|
||||
|
||||
Reference in New Issue
Block a user