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
+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()));
}
}
}