From 1b29bd147eb27d3e549b88f869afd0a26de66f88 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 4 Jan 2026 22:53:26 +0800 Subject: [PATCH] Add missing param instance types (String, Double3D/Integer3D, Group/Page, Custom/Bytes) and mapping to node inputs. `app/pluginSupport/OlivePluginInstance.cpp`, `app/node/plugins/Plugin.cpp` --- TODO-zh.md | 5 + app/node/plugins/Plugin.cpp | 53 +++- app/pluginSupport/OlivePluginInstance.cpp | 13 + app/pluginSupport/paraminstance.h | 261 ++++++++++++++++++ .../nodeparamview/nodeparamviewitem.cpp | 23 ++ .../nodeparamviewwidgetbridge.cpp | 27 +- 6 files changed, 373 insertions(+), 9 deletions(-) diff --git a/TODO-zh.md b/TODO-zh.md index ee834e19c..c863f46bc 100644 --- a/TODO-zh.md +++ b/TODO-zh.md @@ -32,6 +32,11 @@ - 为什么需要:插件在编辑参数、显示进度、根据时间线上下文渲染时依赖这些回调。 - 可能改动:实现 editBegin/editEnd 通知;progressStart/Update/End 与 UI 进度条连接;timelineGetTime/GotoTime/Bounds 与工程时间轴连接。 +7) 持久消息展示与清理机制需要完善 +- 现状:我们已在 Host/Instance 里保存消息并能弹窗,但 UI 面板还需要稳定地展示、更新和清理。 +- 为什么需要:插件经常用 persistent message 提示错误或警告,需要可追踪、可清除。 +- 可能改动:统一消息存储(Host/Instance),提供 UI 列表、清除按钮、计数徽标,并保证信号更新。 + 8) Project Extent / Fielding 行为待确认 - 现状:`OlivePluginInstance::getProjectExtent()` 有 “TODO” 注释。 - 为什么需要:OFX 插件会根据项目尺寸、扫描线场信息做渲染决策。 diff --git a/app/node/plugins/Plugin.cpp b/app/node/plugins/Plugin.cpp index d20b57984..cd28fa5ba 100644 --- a/app/node/plugins/Plugin.cpp +++ b/app/node/plugins/Plugin.cpp @@ -49,8 +49,39 @@ olive::plugin::PluginNode::PluginNode( { plugin_instance_=plugin; bool has_texture_input = false; + QHash group_labels; + QHash page_labels; + QHash page_for_param; auto params=plugin_instance_->getParams(); + for (auto param: params) { + const std::string &ofxType = param.second->getType(); + if (ofxType == kOfxParamTypeGroup) { + const QString name = QString::fromStdString(param.first); + const QString label = + QString::fromStdString(param.second->getLabel()); + group_labels.insert(name, label.isEmpty() ? name : label); + } else if (ofxType == kOfxParamTypePage) { + const QString name = QString::fromStdString(param.first); + const QString label = + QString::fromStdString(param.second->getLabel()); + page_labels.insert(name, label.isEmpty() ? name : label); + + const auto &props = param.second->getProperties(); + int count = props.getDimension(kOfxParamPropPageChild); + for (int i = 0; i < count; ++i) { + const std::string &child = + props.getStringProperty(kOfxParamPropPageChild, i); + if (child == kOfxParamPageSkipRow || + child == kOfxParamPageSkipColumn) { + continue; + } + page_for_param.insert(QString::fromStdString(child), + page_labels.value(name)); + } + } + } + for (auto param: params) { NodeValue::Type type = NodeValue::kNone; @@ -82,17 +113,25 @@ olive::plugin::PluginNode::PluginNode( type = NodeValue::kBinary; } else if (ofxType == kOfxParamTypePushButton) { type = NodeValue::kPushButton; - } else if (ofxType == kOfxParamTypeGroup) { - // TODO - } else if (ofxType == kOfxParamTypePage) { - // TODO + } else if (ofxType == kOfxParamTypeGroup || + ofxType == kOfxParamTypePage) { + continue; }else { type = NodeValue::kNone; } - - - AddInput(param.second->getName().data(), type); + const QString input_id = QString::fromStdString(param.second->getName()); + AddInput(input_id, type); + const QString parent = + QString::fromStdString(param.second->getParentName()); + if (!parent.isEmpty()) { + SetInputProperty(input_id, QStringLiteral("ui_group"), + group_labels.value(parent, parent)); + } + if (page_for_param.contains(input_id)) { + SetInputProperty(input_id, QStringLiteral("ui_page"), + page_for_param.value(input_id)); + } } const auto &clips = plugin_instance_->getDescriptor().getClips(); diff --git a/app/pluginSupport/OlivePluginInstance.cpp b/app/pluginSupport/OlivePluginInstance.cpp index a72d391ee..f2d3d02c8 100644 --- a/app/pluginSupport/OlivePluginInstance.cpp +++ b/app/pluginSupport/OlivePluginInstance.cpp @@ -171,6 +171,8 @@ OlivePluginInstance::newParam(const std::string &name, return new BooleanInstance(node_, name, desc); } else if (type == kOfxParamTypeChoice) { return new ChoiceInstance(node_, name, desc); + } else if (type == kOfxParamTypeString) { + return new StringInstance(node_, name, desc); } else if (type == kOfxParamTypeRGBA) { return new RGBAInstance(node_, name, desc); } else if (type == kOfxParamTypeRGB) { @@ -179,6 +181,17 @@ OlivePluginInstance::newParam(const std::string &name, return new Double2DInstance(node_, name, desc); } else if (type == kOfxParamTypeInteger2D) { return new Integer2DInstance(node_, name, desc); + } else if (type == kOfxParamTypeDouble3D) { + return new Double3DInstance(node_, name, desc); + } else if (type == kOfxParamTypeInteger3D) { + return new Integer3DInstance(node_, name, desc); + } else if (type == kOfxParamTypeCustom || + type == kOfxParamTypeBytes) { + return new CustomInstance(node_, name, desc); + } else if (type == kOfxParamTypeGroup) { + return new GroupInstance(desc); + } else if (type == kOfxParamTypePage) { + return new PageInstance(desc); } else if (type == kOfxParamTypePushButton) { return new PushbuttonInstance(node_, name, desc); } diff --git a/app/pluginSupport/paraminstance.h b/app/pluginSupport/paraminstance.h index 87537f2b5..a7ea59ac6 100644 --- a/app/pluginSupport/paraminstance.h +++ b/app/pluginSupport/paraminstance.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "ofxhParam.h" #include "node/nodeundo.h" @@ -528,6 +529,266 @@ public: return kOfxStatOK; } }; + +class Double3DInstance : public OFX::Host::Param::Double3DInstance { +protected: + std::shared_ptr node; + OFX::Host::Param::Descriptor& _descriptor; +public: + Double3DInstance(std::shared_ptr effect, const std::string& name, + OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::Double3DInstance(descriptor) + , node(effect) + , _descriptor(descriptor) + { + (void)name; + } + OfxStatus get(double& x,double& y,double& z) + { + QVector3D vec = + node->GetStandardValue(_descriptor.getName().c_str()) + .value(); + x = static_cast(vec.x()); + y = static_cast(vec.y()); + z = static_cast(vec.z()); + return kOfxStatOK; + } + OfxStatus get(OfxTime time,double& x,double& y,double& z) + { + QVector3D vec = + node->GetValueAtTime(_descriptor.getName().c_str(), + rational::fromDouble(time)) + .value(); + x = static_cast(vec.x()); + y = static_cast(vec.y()); + z = static_cast(vec.z()); + return kOfxStatOK; + } + OfxStatus set(double x,double y,double z) + { + SplitValue split = NodeValue::split_normal_value_into_track_values( + NodeValue::kVec3, QVector3D(x, y, z)); + auto command = new NodeParamSetSplitStandardValueCommand( + NodeInput(node.get(), _descriptor.getName().c_str()), split); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } + OfxStatus set(OfxTime time,double x,double y,double z) + { + auto command = new MultiUndoCommand(); + const QString name = _descriptor.getName().c_str(); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), x, 0, command, true); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), y, 1, command, true); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), z, 2, command, true); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } +}; + +class Integer3DInstance : public OFX::Host::Param::Integer3DInstance { +protected: + std::shared_ptr node; + OFX::Host::Param::Descriptor& _descriptor; +public: + Integer3DInstance(std::shared_ptr effect, const std::string& name, + OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::Integer3DInstance(descriptor) + , node(effect) + , _descriptor(descriptor) + { + (void)name; + } + OfxStatus get(int& x,int& y,int& z) + { + QVector3D vec = + node->GetStandardValue(_descriptor.getName().c_str()) + .value(); + x = static_cast(vec.x()); + y = static_cast(vec.y()); + z = static_cast(vec.z()); + return kOfxStatOK; + } + OfxStatus get(OfxTime time,int& x,int& y,int& z) + { + QVector3D vec = + node->GetValueAtTime(_descriptor.getName().c_str(), + rational::fromDouble(time)) + .value(); + x = static_cast(vec.x()); + y = static_cast(vec.y()); + z = static_cast(vec.z()); + return kOfxStatOK; + } + OfxStatus set(int x,int y,int z) + { + SplitValue split = NodeValue::split_normal_value_into_track_values( + NodeValue::kVec3, QVector3D(x, y, z)); + auto command = new NodeParamSetSplitStandardValueCommand( + NodeInput(node.get(), _descriptor.getName().c_str()), split); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } + OfxStatus set(OfxTime time,int x,int y,int z) + { + auto command = new MultiUndoCommand(); + const QString name = _descriptor.getName().c_str(); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), x, 0, command, true); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), y, 1, command, true); + Node::SetValueAtTime(NodeInput(node.get(), name), + rational::fromDouble(time), z, 2, command, true); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } +}; + +class StringInstance : public OFX::Host::Param::StringInstance { +protected: + std::shared_ptr node; + OFX::Host::Param::Descriptor& _descriptor; +public: + StringInstance(std::shared_ptr effect, const std::string& name, + OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::StringInstance(descriptor) + , node(effect) + , _descriptor(descriptor) + { + (void)name; + } + OfxStatus get(std::string &data) + { + QVariant variant = node->GetStandardValue(_descriptor.getName().c_str()); + if (variant.canConvert()) { + data = variant.toString().toStdString(); + return kOfxStatOK; + } + data.clear(); + return kOfxStatErrValue; + } + OfxStatus get(OfxTime time, std::string &data) + { + QVariant variant = + node->GetValueAtTime(_descriptor.getName().c_str(), + rational::fromDouble(time)); + if (variant.canConvert()) { + data = variant.toString().toStdString(); + return kOfxStatOK; + } + data.clear(); + return kOfxStatErrValue; + } + OfxStatus set(const char *data) + { + QString v = QString::fromUtf8(data); + SplitValue split = NodeValue::split_normal_value_into_track_values( + NodeValue::kText, v); + auto command = new NodeParamSetSplitStandardValueCommand( + NodeInput(node.get(), _descriptor.getName().c_str()), split); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } + OfxStatus set(OfxTime time, const char *data) + { + auto command = new MultiUndoCommand(); + Node::SetValueAtTime( + NodeInput(node.get(), _descriptor.getName().c_str()), + rational::fromDouble(time), QString::fromUtf8(data), 0, command, true); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } +}; + +class CustomInstance : public OFX::Host::Param::CustomInstance { +protected: + std::shared_ptr node; + OFX::Host::Param::Descriptor& _descriptor; +public: + CustomInstance(std::shared_ptr effect, const std::string& name, + OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::CustomInstance(descriptor) + , node(effect) + , _descriptor(descriptor) + { + (void)name; + } + OfxStatus get(std::string &data) + { + QVariant variant = node->GetStandardValue(_descriptor.getName().c_str()); + if (variant.canConvert()) { + data = variant.toByteArray().toStdString(); + return kOfxStatOK; + } + if (variant.canConvert()) { + data = variant.toString().toStdString(); + return kOfxStatOK; + } + data.clear(); + return kOfxStatErrValue; + } + OfxStatus get(OfxTime time, std::string &data) + { + QVariant variant = + node->GetValueAtTime(_descriptor.getName().c_str(), + rational::fromDouble(time)); + if (variant.canConvert()) { + data = variant.toByteArray().toStdString(); + return kOfxStatOK; + } + if (variant.canConvert()) { + data = variant.toString().toStdString(); + return kOfxStatOK; + } + data.clear(); + return kOfxStatErrValue; + } + OfxStatus set(const char *data) + { + QByteArray v = QByteArray(data); + SplitValue split = NodeValue::split_normal_value_into_track_values( + NodeValue::kBinary, v); + auto command = new NodeParamSetSplitStandardValueCommand( + NodeInput(node.get(), _descriptor.getName().c_str()), split); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } + OfxStatus set(OfxTime time, const char *data) + { + auto command = new MultiUndoCommand(); + Node::SetValueAtTime( + NodeInput(node.get(), _descriptor.getName().c_str()), + rational::fromDouble(time), QByteArray(data), 0, command, true); + Core::instance()->undo_stack()->push(command, + ParamChangeLabel(_descriptor)); + return kOfxStatOK; + } +}; + +class GroupInstance : public OFX::Host::Param::GroupInstance { +public: + GroupInstance(OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::GroupInstance(descriptor) + { + } +}; + +class PageInstance : public OFX::Host::Param::PageInstance { +public: + PageInstance(OFX::Host::Param::Descriptor& descriptor) + : OFX::Host::Param::PageInstance(descriptor) + { + } +}; } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 4d67b5b4c..696d39a1b 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -196,6 +196,8 @@ NodeParamViewItemBody::NodeParamViewItemBody( QGridLayout *root_layout = new QGridLayout(this); int insert_row = 0; + QString current_page; + QString current_group; QVector connected_signals; @@ -219,6 +221,27 @@ NodeParamViewItemBody::NodeParamViewItemBody( { n, input }); if (!(n->GetInputFlags(input) & kInputFlagHidden)) { + QString page_label = n->GetInputProperty(input, QStringLiteral("ui_page")).toString(); + QString group_label = n->GetInputProperty(input, QStringLiteral("ui_group")).toString(); + if (!page_label.isEmpty() && page_label != current_page) { + QLabel *page_title = new QLabel(page_label, this); + QFont f = page_title->font(); + f.setBold(true); + page_title->setFont(f); + root_layout->addWidget(page_title, insert_row, 0, 1, 10); + insert_row++; + current_page = page_label; + current_group.clear(); + } + if (!group_label.isEmpty() && group_label != current_group) { + QLabel *group_title = new QLabel(group_label, this); + QFont f = group_title->font(); + f.setBold(true); + group_title->setFont(f); + root_layout->addWidget(group_title, insert_row, 0, 1, 10); + insert_row++; + current_group = group_label; + } CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 9f8eb4399..23138693d 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -261,9 +261,16 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: - case NodeValue::kBinary: case NodeValue::kDataTypeCount: break; + case NodeValue::kBinary: { + NodeParamViewTextEdit *line_edit = + new NodeParamViewTextEdit(parent); + widgets_.append(line_edit); + connect(line_edit, &NodeParamViewTextEdit::textEdited, this, + &NodeParamViewWidgetBridge::WidgetCallback); + break; + } case NodeValue::kInt: { // Widget is a IntegerSlider IntegerSlider *slider = static_cast(sender()); @@ -342,6 +349,16 @@ void NodeParamViewWidgetBridge::WidgetCallback() 0); break; } + case NodeValue::kBinary: { + QString text = static_cast(sender())->text(); + QByteArray raw = text.toUtf8(); + QByteArray decoded = QByteArray::fromBase64(raw); + if (decoded.isEmpty() && !raw.isEmpty()) { + decoded = raw; + } + SetInputValue(decoded, 0); + break; + } case NodeValue::kBoolean: { // Widget is a QCheckBox SetInputValue(static_cast(sender())->isChecked(), 0); @@ -440,9 +457,15 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kVideoParams: case NodeValue::kAudioParams: case NodeValue::kSubtitleParams: - case NodeValue::kBinary: case NodeValue::kDataTypeCount: break; + case NodeValue::kBinary: { + NodeParamViewTextEdit *e = + static_cast(widgets_.first()); + QByteArray bytes = GetInnerInput().GetValueAtTime(node_time).toByteArray(); + e->setTextPreservingCursor(QString::fromUtf8(bytes.toBase64())); + break; + } case NodeValue::kInt: { static_cast(widgets_.first()) ->SetValue(GetInnerInput().GetValueAtTime(node_time).toLongLong());