engine: node parameter panel migrates to the facade

- new facade API: set_input_at_time (element addressing, track=-1 for
  all components at once), set_input_string_at_time, frame_time_base,
  array_insert_at/remove_at, disconnect_ex (element-aware), and
  keyframes_set_type_many (first cross-track keyframe op, addressed by
  (time,track) pairs)
- the widget bridge's commit funnel, color path, array ops, label
  disconnect, and keyframe set-type actions in keyframeview/curvewidget
  now go through the facade; keyframeviewundo.h loses two consumers
- deliberate leftovers with rationale: keyframecontrol's multi-track
  composite ops (documented track-0-only limitation of the keyframe
  family), keyframeproperties dialog (needs a set_time primitive),
  curveview's drag UX, and NodeInputDragger (already engine-side)
This commit is contained in:
2026-07-20 14:42:00 +08:00
parent 0fe37dba3c
commit 8311128f4c
9 changed files with 747 additions and 57 deletions
+43 -7
View File
@@ -30,7 +30,7 @@
#include "core.h"
#include "common/qtutils.h"
#include "node/node.h"
#include "widget/keyframeview/keyframeviewundo.h"
#include "oakengine/node.h"
#include "widget/timeruler/timeruler.h"
namespace olive
@@ -341,14 +341,50 @@ void CurveWidget::keyframe_type_button_triggered(bool checked)
// Ensure only the appropriate button is checked
set_keyframe_button_checked_from_type(new_type);
MultiUndoCommand *command = new MultiUndoCommand();
// Through the liboakengine C ABI facade: one undoable command per
// distinct input (usually just one), with the same batch semantics as
// the old per-keyframe commands.
struct TypeGroup {
Node *node;
QString input;
int element;
QVector<int64_t> times;
QVector<int> tracks;
};
QVector<TypeGroup> groups;
foreach (NodeKeyframe *item, selected) {
command->add_child(new KeyframeSetTypeCommand(item, new_type));
int g = 0;
for (; g < groups.size(); g++) {
if (groups.at(g).node == item->parent() &&
groups.at(g).input == item->input() &&
groups.at(g).element == item->element()) {
break;
}
}
if (g == groups.size()) {
groups.append({ item->parent(), item->input(), item->element(),
{}, {} });
}
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(item->parent());
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
groups[g].times.append(Timecode::time_to_timestamp(
item->time(), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(item->track());
}
int facade_type = 0;
if (new_type == NodeKeyframe::k_bezier) {
facade_type = 1;
} else if (new_type == NodeKeyframe::k_hold) {
facade_type = 2;
}
foreach (const TypeGroup &g, groups) {
oakengine_node_keyframes_set_type_many(
reinterpret_cast<OakEngineNode *>(g.node),
g.input.toUtf8().constData(), g.element, g.times.constData(),
g.tracks.data(), g.times.size(), facade_type);
}
Core::instance()->undo_stack()->push(
command, tr("Changed Type of %1 Keyframe(s) to %2"));
}
void CurveWidget::input_selection_changed(const NodeKeyframeTrackReference &ref)
+44 -6
View File
@@ -27,11 +27,11 @@
#include "common/qtutils.h"
#include "dialog/keyframeproperties/keyframeproperties.h"
#include "keyframeviewundo.h"
#include "node/group/group.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "node/project/serializer/serializer.h"
#include "oakengine/node.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
@@ -645,13 +645,51 @@ void KeyframeView::show_context_menu()
new_type = NodeKeyframe::k_linear;
}
MultiUndoCommand *command = new MultiUndoCommand();
// Through the liboakengine C ABI facade: one undoable command
// per distinct input (usually just one), with the same batch
// semantics as the old per-keyframe commands.
struct TypeGroup {
Node *node;
QString input;
int element;
QVector<int64_t> times;
QVector<int> tracks;
};
QVector<TypeGroup> groups;
foreach (NodeKeyframe *item, get_selected_keyframes()) {
command->add_child(new KeyframeSetTypeCommand(item, new_type));
int g = 0;
for (; g < groups.size(); g++) {
if (groups.at(g).node == item->parent() &&
groups.at(g).input == item->input() &&
groups.at(g).element == item->element()) {
break;
}
}
if (g == groups.size()) {
groups.append({ item->parent(), item->input(),
item->element(), {}, {} });
}
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(item->parent());
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
groups[g].times.append(Timecode::time_to_timestamp(
item->time(), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(item->track());
}
int facade_type = 0;
if (new_type == NodeKeyframe::k_bezier) {
facade_type = 1;
} else if (new_type == NodeKeyframe::k_hold) {
facade_type = 2;
}
foreach (const TypeGroup &g, groups) {
oakengine_node_keyframes_set_type_many(
reinterpret_cast<OakEngineNode *>(g.node),
g.input.toUtf8().constData(), g.element,
g.times.constData(), g.tracks.data(), g.times.size(),
facade_type);
}
Core::instance()->undo_stack()->push(
command, tr("Set Type of %1 Keyframe(s)")
.arg(get_selected_keyframes().size()));
}
}
}
@@ -26,7 +26,7 @@
#include "common/qtutils.h"
#include "core.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "oakengine/node.h"
#include "widget/collapsebutton/collapsebutton.h"
#include "widget/menu/menu.h"
@@ -150,9 +150,11 @@ void NodeParamViewConnectedLabel::show_label_context_menu()
QAction *disconnect_action = m.addAction(tr("Disconnect"));
connect(disconnect_action, &QAction::triggered, this, [this]() {
Core::instance()->undo_stack()->push(
new NodeEdgeRemoveCommand(connected_node_, input_),
Node::get_disconnect_command_string(connected_node_, input_));
// Through the liboakengine C ABI facade (one undoable command,
// array element included, same as the old NodeEdgeRemoveCommand).
oakengine_node_disconnect_ex(
reinterpret_cast<OakEngineNode *>(input_.node()),
input_.input().toUtf8().constData(), input_.element());
});
m.exec(QCursor::pos());
+15 -17
View File
@@ -25,11 +25,10 @@
#include <QDebug>
#include "common/qtutils.h"
#include "core.h"
#include "dialog/speedduration/speeddurationdialog.h"
#include "node/group/group.h"
#include "node/nodeundo.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "pluginSupport/oliveplugininstance.h"
namespace olive
@@ -630,13 +629,12 @@ void NodeParamViewItemBody::array_append_clicked()
if (it.value().append_btn == sender()) {
NodeInput real_input = NodeGroup::resolve_input(
NodeInput(it.key().node, it.key().input));
Core::instance()->undo_stack()->push(
new NodeArrayInsertCommand(real_input.node(),
real_input.input(),
real_input.get_array_size()),
tr("Appended Array Element In %1 - %2")
.arg(real_input.node()->get_label_and_name(),
real_input.get_input_name()));
// Through the liboakengine C ABI facade (one undoable command,
// same as the old NodeArrayInsertCommand push).
oakengine_node_array_insert_at(
reinterpret_cast<OakEngineNode *>(real_input.node()),
real_input.input().toUtf8().constData(),
real_input.get_array_size());
break;
}
}
@@ -648,10 +646,10 @@ void NodeParamViewItemBody::array_insert_clicked()
if (it.value().array_insert_btn == sender()) {
// Found our input and element
NodeInput ic = NodeGroup::resolve_input(it.key());
Core::instance()->undo_stack()->push(
new NodeArrayInsertCommand(ic.node(), ic.input(), ic.element()),
tr("Inserted Array Element In %1 - %2")
.arg(ic.node()->get_label_and_name(), ic.get_input_name()));
// Through the liboakengine C ABI facade (one undoable command).
oakengine_node_array_insert_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
ic.input().toUtf8().constData(), ic.element());
break;
}
}
@@ -663,10 +661,10 @@ void NodeParamViewItemBody::array_remove_clicked()
if (it.value().array_remove_btn == sender()) {
// Found our input and element
NodeInput ic = NodeGroup::resolve_input(it.key());
Core::instance()->undo_stack()->push(
new NodeArrayRemoveCommand(ic.node(), ic.input(), ic.element()),
tr("Removed Array Element In %1 - %2")
.arg(ic.node()->get_label_and_name(), ic.get_input_name()));
// Through the liboakengine C ABI facade (one undoable command).
oakengine_node_array_remove_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
ic.input().toUtf8().constData(), ic.element());
break;
}
}
@@ -37,6 +37,7 @@
#include "node/project/sequence/sequence.h"
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewtextedit.h"
#include "oakengine/node.h"
#include "render/lutlibrary.h"
#include "undo/undostack.h"
#include "widget/bezier/bezierwidget.h"
@@ -75,6 +76,74 @@ int get_slider_count(NodeValue::Type type)
return NodeValue::get_number_of_keyframe_tracks(type);
}
namespace
{
// Map a panel widget's per-track scalar QVariant into the facade POD.
// Returns false for types that have no facade mapping (the caller keeps
// the legacy path for those).
bool variant_to_c_value(NodeValue::Type type, const QVariant &value,
oak_node_value *out)
{
memset(out, 0, sizeof(*out));
switch (type) {
case NodeValue::k_int:
out->type = OAK_NODE_VALUE_INT;
out->num = value.toLongLong();
return true;
case NodeValue::k_combo:
out->type = OAK_NODE_VALUE_COMBO;
out->num = value.toLongLong();
return true;
case NodeValue::k_float:
case NodeValue::k_bezier:
out->type = OAK_NODE_VALUE_FLOAT;
out->f[0] = value.toDouble();
return true;
case NodeValue::k_boolean:
out->type = OAK_NODE_VALUE_BOOL;
out->num = value.toBool() ? 1 : 0;
return true;
case NodeValue::k_rational: {
const Rational r = value.value<Rational>();
out->type = OAK_NODE_VALUE_RATIONAL;
out->num = r.numerator();
out->den = r.denominator();
return true;
}
case NodeValue::k_color:
out->type = OAK_NODE_VALUE_COLOR;
out->f[0] = value.toDouble();
return true;
case NodeValue::k_vec2:
out->type = OAK_NODE_VALUE_VEC2;
out->f[0] = value.toDouble();
return true;
case NodeValue::k_vec3:
out->type = OAK_NODE_VALUE_VEC3;
out->f[0] = value.toDouble();
return true;
case NodeValue::k_vec4:
out->type = OAK_NODE_VALUE_VEC4;
out->f[0] = value.toDouble();
return true;
default:
return false;
}
}
// Convert rational node time to the facade's frame timestamps using the
// facade's own timebase (so the round trip is exact).
int64_t node_time_to_ts(OakEngineNode *node, const Rational &time)
{
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(node, &tbn, &tbd);
return Timecode::time_to_timestamp(time, Rational(tbn, tbd),
Timecode::k_round);
}
} // namespace
void NodeParamViewWidgetBridge::create_widgets()
{
QWidget *parent = dynamic_cast<QWidget *>(this->parent());
@@ -236,11 +305,37 @@ void NodeParamViewWidgetBridge::create_widgets()
void NodeParamViewWidgetBridge::set_input_value(const QVariant &value, int track)
{
MultiUndoCommand *command = new MultiUndoCommand();
// POD values go through the liboakengine C ABI facade (one undoable
// command with the same set-value-at-time semantics as the old
// app-side assembly); types without a facade mapping keep the legacy
// undo assembly below.
const NodeInput &input = get_inner_input();
oak_node_value c_value;
if (!variant_to_c_value(get_data_type(), value, &c_value)) {
MultiUndoCommand *command = new MultiUndoCommand();
set_input_value_internal(value, track, command, true);
Core::instance()->undo_stack()->push(command, get_command_name());
return;
}
set_input_value_internal(value, track, command, true);
oakengine_node_set_input_at_time(
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(),
node_time_to_ts(reinterpret_cast<OakEngineNode *>(input.node()),
get_current_time_as_node_time()),
track, &c_value, 1);
}
Core::instance()->undo_stack()->push(command, get_command_name());
void NodeParamViewWidgetBridge::set_string_value(const QString &value)
{
// String-family inputs (file/text/font/str_combo) through the facade.
const NodeInput &input = get_inner_input();
oakengine_node_set_input_string_at_time(
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(),
node_time_to_ts(reinterpret_cast<OakEngineNode *>(input.node()),
get_current_time_as_node_time()),
value.toUtf8().constData());
}
void NodeParamViewWidgetBridge::set_input_value_internal(
@@ -336,7 +431,7 @@ void NodeParamViewWidgetBridge::widget_callback()
break;
}
case NodeValue::k_file: {
set_input_value(static_cast<FileField *>(sender())->get_filename(), 0);
set_string_value(static_cast<FileField *>(sender())->get_filename());
break;
}
case NodeValue::k_color: {
@@ -345,15 +440,27 @@ void NodeParamViewWidgetBridge::widget_callback()
FloatSlider *slider = static_cast<FloatSlider *>(sender());
process_slider(slider, slider->get_value());
} else {
// Sender is a ColorButton
// Sender is a ColorButton: all four components go through the
// facade in one undoable command (track -1). The
// color-management input properties are not undoable in the
// engine and stay direct (same as the old code).
ManagedColor c = static_cast<ColorButton *>(sender())->get_color();
MultiUndoCommand *command = new MultiUndoCommand();
set_input_value_internal(c.red(), 0, command, false);
set_input_value_internal(c.green(), 1, command, false);
set_input_value_internal(c.blue(), 2, command, false);
set_input_value_internal(c.alpha(), 3, command, false);
const NodeInput &input = get_inner_input();
oak_node_value c_value;
memset(&c_value, 0, sizeof(c_value));
c_value.type = OAK_NODE_VALUE_COLOR;
c_value.f[0] = c.red();
c_value.f[1] = c.green();
c_value.f[2] = c.blue();
c_value.f[3] = c.alpha();
oakengine_node_set_input_at_time(
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(),
node_time_to_ts(
reinterpret_cast<OakEngineNode *>(input.node()),
get_current_time_as_node_time()),
-1, &c_value, 0);
Node *n = get_inner_input().node();
n->blockSignals(true);
@@ -369,15 +476,12 @@ void NodeParamViewWidgetBridge::widget_callback()
QStringLiteral("col_look"),
c.color_output().look());
n->blockSignals(false);
Core::instance()->undo_stack()->push(command, get_command_name());
}
break;
}
case NodeValue::k_text: {
// Sender is a NodeParamViewRichText
set_input_value(static_cast<NodeParamViewTextEdit *>(sender())->text(),
0);
set_string_value(static_cast<NodeParamViewTextEdit *>(sender())->text());
break;
}
case NodeValue::k_binary: {
@@ -397,8 +501,8 @@ void NodeParamViewWidgetBridge::widget_callback()
}
case NodeValue::k_font: {
// Widget is a QFontComboBox
set_input_value(
static_cast<QFontComboBox *>(sender())->currentFont().family(), 0);
set_string_value(
static_cast<QFontComboBox *>(sender())->currentFont().family());
break;
}
case NodeValue::k_combo: {
@@ -421,9 +525,9 @@ void NodeParamViewWidgetBridge::widget_callback()
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
const QVariant data = cb->currentData();
if (data.isValid()) {
set_input_value(data.toString(), 0);
set_string_value(data.toString());
} else {
set_input_value(cb->currentText(), 0);
set_string_value(cb->currentText());
}
break;
}
@@ -66,6 +66,8 @@ private:
void set_input_value(const QVariant &value, int track);
void set_string_value(const QString &value);
void set_input_value_internal(const QVariant &value, int track,
MultiUndoCommand *command,
bool insert_on_all_tracks_if_no_key);