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:
@@ -30,7 +30,7 @@
|
|||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "node/node.h"
|
#include "node/node.h"
|
||||||
#include "widget/keyframeview/keyframeviewundo.h"
|
#include "oakengine/node.h"
|
||||||
#include "widget/timeruler/timeruler.h"
|
#include "widget/timeruler/timeruler.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
@@ -341,14 +341,50 @@ void CurveWidget::keyframe_type_button_triggered(bool checked)
|
|||||||
// Ensure only the appropriate button is checked
|
// Ensure only the appropriate button is checked
|
||||||
set_keyframe_button_checked_from_type(new_type);
|
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) {
|
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)
|
void CurveWidget::input_selection_changed(const NodeKeyframeTrackReference &ref)
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
|
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "dialog/keyframeproperties/keyframeproperties.h"
|
#include "dialog/keyframeproperties/keyframeproperties.h"
|
||||||
#include "keyframeviewundo.h"
|
|
||||||
#include "node/group/group.h"
|
#include "node/group/group.h"
|
||||||
#include "node/node.h"
|
#include "node/node.h"
|
||||||
#include "node/nodeundo.h"
|
#include "node/nodeundo.h"
|
||||||
#include "node/project/serializer/serializer.h"
|
#include "node/project/serializer/serializer.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
#include "widget/menu/menushared.h"
|
#include "widget/menu/menushared.h"
|
||||||
|
|
||||||
@@ -645,13 +645,51 @@ void KeyframeView::show_context_menu()
|
|||||||
new_type = NodeKeyframe::k_linear;
|
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()) {
|
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 "common/qtutils.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "node/node.h"
|
#include "node/node.h"
|
||||||
#include "node/nodeundo.h"
|
#include "oakengine/node.h"
|
||||||
#include "widget/collapsebutton/collapsebutton.h"
|
#include "widget/collapsebutton/collapsebutton.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
|
|
||||||
@@ -150,9 +150,11 @@ void NodeParamViewConnectedLabel::show_label_context_menu()
|
|||||||
|
|
||||||
QAction *disconnect_action = m.addAction(tr("Disconnect"));
|
QAction *disconnect_action = m.addAction(tr("Disconnect"));
|
||||||
connect(disconnect_action, &QAction::triggered, this, [this]() {
|
connect(disconnect_action, &QAction::triggered, this, [this]() {
|
||||||
Core::instance()->undo_stack()->push(
|
// Through the liboakengine C ABI facade (one undoable command,
|
||||||
new NodeEdgeRemoveCommand(connected_node_, input_),
|
// array element included, same as the old NodeEdgeRemoveCommand).
|
||||||
Node::get_disconnect_command_string(connected_node_, input_));
|
oakengine_node_disconnect_ex(
|
||||||
|
reinterpret_cast<OakEngineNode *>(input_.node()),
|
||||||
|
input_.input().toUtf8().constData(), input_.element());
|
||||||
});
|
});
|
||||||
|
|
||||||
m.exec(QCursor::pos());
|
m.exec(QCursor::pos());
|
||||||
|
|||||||
@@ -25,11 +25,10 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "core.h"
|
|
||||||
#include "dialog/speedduration/speeddurationdialog.h"
|
#include "dialog/speedduration/speeddurationdialog.h"
|
||||||
#include "node/group/group.h"
|
#include "node/group/group.h"
|
||||||
#include "node/nodeundo.h"
|
|
||||||
#include "node/project/sequence/sequence.h"
|
#include "node/project/sequence/sequence.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
#include "pluginSupport/oliveplugininstance.h"
|
#include "pluginSupport/oliveplugininstance.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
@@ -630,13 +629,12 @@ void NodeParamViewItemBody::array_append_clicked()
|
|||||||
if (it.value().append_btn == sender()) {
|
if (it.value().append_btn == sender()) {
|
||||||
NodeInput real_input = NodeGroup::resolve_input(
|
NodeInput real_input = NodeGroup::resolve_input(
|
||||||
NodeInput(it.key().node, it.key().input));
|
NodeInput(it.key().node, it.key().input));
|
||||||
Core::instance()->undo_stack()->push(
|
// Through the liboakengine C ABI facade (one undoable command,
|
||||||
new NodeArrayInsertCommand(real_input.node(),
|
// same as the old NodeArrayInsertCommand push).
|
||||||
real_input.input(),
|
oakengine_node_array_insert_at(
|
||||||
real_input.get_array_size()),
|
reinterpret_cast<OakEngineNode *>(real_input.node()),
|
||||||
tr("Appended Array Element In %1 - %2")
|
real_input.input().toUtf8().constData(),
|
||||||
.arg(real_input.node()->get_label_and_name(),
|
real_input.get_array_size());
|
||||||
real_input.get_input_name()));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,10 +646,10 @@ void NodeParamViewItemBody::array_insert_clicked()
|
|||||||
if (it.value().array_insert_btn == sender()) {
|
if (it.value().array_insert_btn == sender()) {
|
||||||
// Found our input and element
|
// Found our input and element
|
||||||
NodeInput ic = NodeGroup::resolve_input(it.key());
|
NodeInput ic = NodeGroup::resolve_input(it.key());
|
||||||
Core::instance()->undo_stack()->push(
|
// Through the liboakengine C ABI facade (one undoable command).
|
||||||
new NodeArrayInsertCommand(ic.node(), ic.input(), ic.element()),
|
oakengine_node_array_insert_at(
|
||||||
tr("Inserted Array Element In %1 - %2")
|
reinterpret_cast<OakEngineNode *>(ic.node()),
|
||||||
.arg(ic.node()->get_label_and_name(), ic.get_input_name()));
|
ic.input().toUtf8().constData(), ic.element());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,10 +661,10 @@ void NodeParamViewItemBody::array_remove_clicked()
|
|||||||
if (it.value().array_remove_btn == sender()) {
|
if (it.value().array_remove_btn == sender()) {
|
||||||
// Found our input and element
|
// Found our input and element
|
||||||
NodeInput ic = NodeGroup::resolve_input(it.key());
|
NodeInput ic = NodeGroup::resolve_input(it.key());
|
||||||
Core::instance()->undo_stack()->push(
|
// Through the liboakengine C ABI facade (one undoable command).
|
||||||
new NodeArrayRemoveCommand(ic.node(), ic.input(), ic.element()),
|
oakengine_node_array_remove_at(
|
||||||
tr("Removed Array Element In %1 - %2")
|
reinterpret_cast<OakEngineNode *>(ic.node()),
|
||||||
.arg(ic.node()->get_label_and_name(), ic.get_input_name()));
|
ic.input().toUtf8().constData(), ic.element());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "node/project/sequence/sequence.h"
|
#include "node/project/sequence/sequence.h"
|
||||||
#include "nodeparamviewarraywidget.h"
|
#include "nodeparamviewarraywidget.h"
|
||||||
#include "nodeparamviewtextedit.h"
|
#include "nodeparamviewtextedit.h"
|
||||||
|
#include "oakengine/node.h"
|
||||||
#include "render/lutlibrary.h"
|
#include "render/lutlibrary.h"
|
||||||
#include "undo/undostack.h"
|
#include "undo/undostack.h"
|
||||||
#include "widget/bezier/bezierwidget.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);
|
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()
|
void NodeParamViewWidgetBridge::create_widgets()
|
||||||
{
|
{
|
||||||
QWidget *parent = dynamic_cast<QWidget *>(this->parent());
|
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)
|
void NodeParamViewWidgetBridge::set_input_value(const QVariant &value, int track)
|
||||||
{
|
{
|
||||||
|
// 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();
|
MultiUndoCommand *command = new MultiUndoCommand();
|
||||||
|
|
||||||
set_input_value_internal(value, track, command, true);
|
set_input_value_internal(value, track, command, true);
|
||||||
|
|
||||||
Core::instance()->undo_stack()->push(command, get_command_name());
|
Core::instance()->undo_stack()->push(command, get_command_name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
void NodeParamViewWidgetBridge::set_input_value_internal(
|
||||||
@@ -336,7 +431,7 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::k_file: {
|
case NodeValue::k_file: {
|
||||||
set_input_value(static_cast<FileField *>(sender())->get_filename(), 0);
|
set_string_value(static_cast<FileField *>(sender())->get_filename());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::k_color: {
|
case NodeValue::k_color: {
|
||||||
@@ -345,15 +440,27 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
||||||
process_slider(slider, slider->get_value());
|
process_slider(slider, slider->get_value());
|
||||||
} else {
|
} 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();
|
ManagedColor c = static_cast<ColorButton *>(sender())->get_color();
|
||||||
|
|
||||||
MultiUndoCommand *command = new MultiUndoCommand();
|
const NodeInput &input = get_inner_input();
|
||||||
|
oak_node_value c_value;
|
||||||
set_input_value_internal(c.red(), 0, command, false);
|
memset(&c_value, 0, sizeof(c_value));
|
||||||
set_input_value_internal(c.green(), 1, command, false);
|
c_value.type = OAK_NODE_VALUE_COLOR;
|
||||||
set_input_value_internal(c.blue(), 2, command, false);
|
c_value.f[0] = c.red();
|
||||||
set_input_value_internal(c.alpha(), 3, command, false);
|
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();
|
Node *n = get_inner_input().node();
|
||||||
n->blockSignals(true);
|
n->blockSignals(true);
|
||||||
@@ -369,15 +476,12 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
QStringLiteral("col_look"),
|
QStringLiteral("col_look"),
|
||||||
c.color_output().look());
|
c.color_output().look());
|
||||||
n->blockSignals(false);
|
n->blockSignals(false);
|
||||||
|
|
||||||
Core::instance()->undo_stack()->push(command, get_command_name());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::k_text: {
|
case NodeValue::k_text: {
|
||||||
// Sender is a NodeParamViewRichText
|
// Sender is a NodeParamViewRichText
|
||||||
set_input_value(static_cast<NodeParamViewTextEdit *>(sender())->text(),
|
set_string_value(static_cast<NodeParamViewTextEdit *>(sender())->text());
|
||||||
0);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::k_binary: {
|
case NodeValue::k_binary: {
|
||||||
@@ -397,8 +501,8 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
}
|
}
|
||||||
case NodeValue::k_font: {
|
case NodeValue::k_font: {
|
||||||
// Widget is a QFontComboBox
|
// Widget is a QFontComboBox
|
||||||
set_input_value(
|
set_string_value(
|
||||||
static_cast<QFontComboBox *>(sender())->currentFont().family(), 0);
|
static_cast<QFontComboBox *>(sender())->currentFont().family());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeValue::k_combo: {
|
case NodeValue::k_combo: {
|
||||||
@@ -421,9 +525,9 @@ void NodeParamViewWidgetBridge::widget_callback()
|
|||||||
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
||||||
const QVariant data = cb->currentData();
|
const QVariant data = cb->currentData();
|
||||||
if (data.isValid()) {
|
if (data.isValid()) {
|
||||||
set_input_value(data.toString(), 0);
|
set_string_value(data.toString());
|
||||||
} else {
|
} else {
|
||||||
set_input_value(cb->currentText(), 0);
|
set_string_value(cb->currentText());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ private:
|
|||||||
|
|
||||||
void set_input_value(const QVariant &value, int track);
|
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,
|
void set_input_value_internal(const QVariant &value, int track,
|
||||||
MultiUndoCommand *command,
|
MultiUndoCommand *command,
|
||||||
bool insert_on_all_tracks_if_no_key);
|
bool insert_on_all_tracks_if_no_key);
|
||||||
|
|||||||
@@ -224,6 +224,66 @@ OAKENGINE_API int oakengine_node_set_input_string(OakEngineNode *self,
|
|||||||
const char *input_id,
|
const char *input_id,
|
||||||
const char *s);
|
const char *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The frame timebase used for keyframe/parameter frame timestamps
|
||||||
|
* (seconds per frame: the frame rate of the project's first sequence
|
||||||
|
* flipped, or the engine default 1001/30000). Any pointer may be NULL.
|
||||||
|
* Use it to convert rational seconds to the timestamps this family
|
||||||
|
* takes, exactly like the facade does internally.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_frame_time_base(
|
||||||
|
const OakEngineNode *self, int *num, int *den);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write an input's value at a time (undoable, ONE command;
|
||||||
|
* olive::Node::set_value_at_time() -- the application's parameter
|
||||||
|
* panel commit path).
|
||||||
|
*
|
||||||
|
* When the input is keyframed this inserts or updates the keyframe at
|
||||||
|
* `time_ts` (the engine's set_value_at_time semantics); otherwise it
|
||||||
|
* sets the standard value on `track`. `element` is the array element
|
||||||
|
* (-1 for non-array inputs). `track` is the keyframe track/component;
|
||||||
|
* pass -1 to write ALL components of a split-track type (COLOR/VEC2/3/4)
|
||||||
|
* from `v->f[]` in the same command. `v->type` must match the input's
|
||||||
|
* declared type; a k_bezier input takes an OAK_NODE_VALUE_FLOAT
|
||||||
|
* component per track. String-family inputs (k_file/k_text/k_font/
|
||||||
|
* k_str_combo) are rejected -- use oakengine_node_set_input_string_at_
|
||||||
|
* time(). `insert_on_all_tracks` mirrors set_value_at_time's
|
||||||
|
* insert_on_all_tracks_if_no_key (ignored for `track` -1).
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_set_input_at_time(
|
||||||
|
OakEngineNode *self, const char *input_id, int element, int64_t time_ts,
|
||||||
|
int track, const oak_node_value *v, int insert_on_all_tracks);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a string-family input's value at a time (undoable, ONE
|
||||||
|
* command; k_file/k_text/k_font/k_str_combo on track 0, with
|
||||||
|
* insert_on_all_tracks_if_no_key semantics like the panel).
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_set_input_string_at_time(
|
||||||
|
OakEngineNode *self, const char *input_id, int element, int64_t time_ts,
|
||||||
|
const char *value);
|
||||||
|
|
||||||
|
/* ---- Array inputs --------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert an element into an array input at `index` (undoable,
|
||||||
|
* olive::NodeArrayInsertCommand; the panel's array append/insert
|
||||||
|
* buttons). `index` must be >= 0.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_array_insert_at(OakEngineNode *self,
|
||||||
|
const char *input_id,
|
||||||
|
int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the array element at `index` (undoable,
|
||||||
|
* olive::NodeArrayRemoveCommand). OAKENGINE_E_NOT_FOUND for an
|
||||||
|
* out-of-range index.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_array_remove_at(OakEngineNode *self,
|
||||||
|
const char *input_id,
|
||||||
|
int index);
|
||||||
|
|
||||||
/* ---- Graph editing ------------------------------------------------------------- */
|
/* ---- Graph editing ------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,6 +322,16 @@ OAKENGINE_API int oakengine_node_connect(OakEngineNode *output_node,
|
|||||||
OAKENGINE_API int oakengine_node_disconnect(OakEngineNode *input_node,
|
OAKENGINE_API int oakengine_node_disconnect(OakEngineNode *input_node,
|
||||||
const char *input_id);
|
const char *input_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the edge feeding `input_node`'s `input_id` at `element`
|
||||||
|
* (undoable). Same as oakengine_node_disconnect() (which passes element
|
||||||
|
* -1) but addresses an array element, like the panel's connected-label
|
||||||
|
* disconnect for array inputs.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_disconnect_ex(OakEngineNode *input_node,
|
||||||
|
const char *input_id,
|
||||||
|
int element);
|
||||||
|
|
||||||
/* ---- Parameter animation (keyframes) --------------------------------------
|
/* ---- Parameter animation (keyframes) --------------------------------------
|
||||||
*
|
*
|
||||||
* Keyframes live on an input's keyframe tracks (olive::NodeKeyframe). All
|
* Keyframes live on an input's keyframe tracks (olive::NodeKeyframe). All
|
||||||
@@ -350,6 +420,23 @@ OAKENGINE_API int oakengine_node_keyframe_set_easing(
|
|||||||
OakEngineNode *self, const char *input_id, int64_t time_ts, int type,
|
OakEngineNode *self, const char *input_id, int64_t time_ts, int type,
|
||||||
float x1, float y1, float x2, float y2);
|
float x1, float y1, float x2, float y2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Change only the easing TYPE of several keyframes of one input
|
||||||
|
* (undoable, ONE command; the application's keyframe view
|
||||||
|
* KeyframeSetTypeCommand, batched like its context-menu action).
|
||||||
|
*
|
||||||
|
* Unlike the rest of this family (track 0 only), keyframes are addressed
|
||||||
|
* individually by (`times_ts`[i], `tracks`[i]) because the view's
|
||||||
|
* selection may span tracks; `element` addresses the input's array
|
||||||
|
* element (-1 for non-array). Bezier control points are left untouched.
|
||||||
|
* Every address must name an existing keyframe or the whole call fails
|
||||||
|
* with OAKENGINE_E_NOT_FOUND and nothing is pushed. Returns the number
|
||||||
|
* of affected keyframes (>= 0) or a negative code.
|
||||||
|
*/
|
||||||
|
OAKENGINE_API int oakengine_node_keyframes_set_type_many(
|
||||||
|
OakEngineNode *self, const char *input_id, int element,
|
||||||
|
const int64_t *times_ts, const int *tracks, int count, int type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove all keyframes from the input (undoable,
|
* @brief Remove all keyframes from the input (undoable,
|
||||||
* olive::NodeImmediateRemoveAllKeyframesCommand). A no-op (OAKENGINE_OK)
|
* olive::NodeImmediateRemoveAllKeyframesCommand). A no-op (OAKENGINE_OK)
|
||||||
|
|||||||
+270
-4
@@ -668,6 +668,221 @@ int oakengine_node_set_input_string(OakEngineNode *self,
|
|||||||
return OAKENGINE_OK;
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int oakengine_node_frame_time_base(const OakEngineNode *self, int *num,
|
||||||
|
int *den)
|
||||||
|
{
|
||||||
|
if (!self) {
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const olive::Rational tb = project_time_base(impl(self));
|
||||||
|
if (num) {
|
||||||
|
*num = tb.numerator();
|
||||||
|
}
|
||||||
|
if (den) {
|
||||||
|
*den = tb.denominator();
|
||||||
|
}
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component QVariant of a per-track POD for set_value_at_time: the
|
||||||
|
// panel's sliders carry one scalar per track (int64/double/Rational/
|
||||||
|
// bool). Returns false on a type that has no scalar component here.
|
||||||
|
static bool component_from_c(const oak_node_value *v,
|
||||||
|
olive::NodeValue::Type declared, int component,
|
||||||
|
QVariant *out)
|
||||||
|
{
|
||||||
|
switch (declared) {
|
||||||
|
case olive::NodeValue::k_int:
|
||||||
|
case olive::NodeValue::k_combo:
|
||||||
|
if (v->type != OAK_NODE_VALUE_INT &&
|
||||||
|
v->type != OAK_NODE_VALUE_COMBO) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = QVariant::fromValue<qlonglong>(v->num);
|
||||||
|
return true;
|
||||||
|
case olive::NodeValue::k_float:
|
||||||
|
case olive::NodeValue::k_bezier:
|
||||||
|
if (v->type != OAK_NODE_VALUE_FLOAT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = QVariant::fromValue(v->f[0]);
|
||||||
|
return true;
|
||||||
|
case olive::NodeValue::k_boolean:
|
||||||
|
if (v->type != OAK_NODE_VALUE_BOOL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = QVariant::fromValue(v->num != 0);
|
||||||
|
return true;
|
||||||
|
case olive::NodeValue::k_rational:
|
||||||
|
if (v->type != OAK_NODE_VALUE_RATIONAL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = QVariant::fromValue(
|
||||||
|
olive::Rational(int(v->num), int(v->den)));
|
||||||
|
return true;
|
||||||
|
case olive::NodeValue::k_color:
|
||||||
|
case olive::NodeValue::k_vec2:
|
||||||
|
case olive::NodeValue::k_vec3:
|
||||||
|
case olive::NodeValue::k_vec4:
|
||||||
|
if (int(v->type) != int(to_c_type(declared))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = QVariant::fromValue(v->f[component]);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_node_set_input_at_time(OakEngineNode *self,
|
||||||
|
const char *input_id, int element,
|
||||||
|
int64_t time_ts, int track,
|
||||||
|
const oak_node_value *v,
|
||||||
|
int insert_on_all_tracks)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Node *node = impl(self);
|
||||||
|
const olive::NodeValue::Type declared = checked_input(node, input_id);
|
||||||
|
if (declared == olive::NodeValue::k_none) {
|
||||||
|
set_error(self && input_id ?
|
||||||
|
QStringLiteral("unknown input id \"%1\"")
|
||||||
|
.arg(QString::fromUtf8(input_id)) :
|
||||||
|
QStringLiteral("invalid arguments"));
|
||||||
|
return self && input_id ? OAKENGINE_E_NOT_FOUND : OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (declared == olive::NodeValue::k_file ||
|
||||||
|
declared == olive::NodeValue::k_text ||
|
||||||
|
declared == olive::NodeValue::k_font ||
|
||||||
|
declared == olive::NodeValue::k_str_combo) {
|
||||||
|
set_error(QStringLiteral(
|
||||||
|
"string inputs use oakengine_node_set_input_string_at_time"));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const int nb_tracks =
|
||||||
|
olive::NodeValue::get_number_of_keyframe_tracks(declared);
|
||||||
|
if (!v || track < -1 || track >= nb_tracks || nb_tracks == 0) {
|
||||||
|
set_error(QStringLiteral("invalid arguments"));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString id = QString::fromUtf8(input_id);
|
||||||
|
const olive::Rational time = olive::core::Timecode::timestamp_to_time(
|
||||||
|
time_ts, project_time_base(node));
|
||||||
|
const olive::NodeInput input(node, id, element);
|
||||||
|
|
||||||
|
// The panel's commit path (Node::set_value_at_time), one undoable
|
||||||
|
// command: keyframed inputs insert/update the keyframe at the time,
|
||||||
|
// others set the standard value on the track.
|
||||||
|
olive::MultiUndoCommand *command = new olive::MultiUndoCommand();
|
||||||
|
if (track == -1) {
|
||||||
|
for (int i = 0; i < nb_tracks; i++) {
|
||||||
|
QVariant component;
|
||||||
|
if (!component_from_c(v, declared, i, &component)) {
|
||||||
|
set_error(QStringLiteral(
|
||||||
|
"value type does not match the declared input type"));
|
||||||
|
delete command;
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::Node::set_value_at_time(input, time, component, i,
|
||||||
|
command, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QVariant component;
|
||||||
|
if (!component_from_c(v, declared, 0, &component)) {
|
||||||
|
set_error(QStringLiteral(
|
||||||
|
"value type does not match the declared input type"));
|
||||||
|
delete command;
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
olive::Node::set_value_at_time(input, time, component, track,
|
||||||
|
command, insert_on_all_tracks != 0);
|
||||||
|
}
|
||||||
|
push_or_run(command, QStringLiteral("Set Input Value"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_node_set_input_string_at_time(OakEngineNode *self,
|
||||||
|
const char *input_id, int element,
|
||||||
|
int64_t time_ts, const char *value)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Node *node = impl(self);
|
||||||
|
const olive::NodeValue::Type declared = checked_input(node, input_id);
|
||||||
|
if (declared == olive::NodeValue::k_none) {
|
||||||
|
set_error(self && input_id ?
|
||||||
|
QStringLiteral("unknown input id \"%1\"")
|
||||||
|
.arg(QString::fromUtf8(input_id)) :
|
||||||
|
QStringLiteral("invalid arguments"));
|
||||||
|
return self && input_id ? OAKENGINE_E_NOT_FOUND : OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (declared != olive::NodeValue::k_file &&
|
||||||
|
declared != olive::NodeValue::k_text &&
|
||||||
|
declared != olive::NodeValue::k_font &&
|
||||||
|
declared != olive::NodeValue::k_str_combo) {
|
||||||
|
set_error(QStringLiteral("\"%1\" is not a string input")
|
||||||
|
.arg(QString::fromUtf8(input_id)));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const olive::Rational time = olive::core::Timecode::timestamp_to_time(
|
||||||
|
time_ts, project_time_base(node));
|
||||||
|
const QVariant v =
|
||||||
|
QVariant::fromValue(QString::fromUtf8(value ? value : ""));
|
||||||
|
olive::MultiUndoCommand *command = new olive::MultiUndoCommand();
|
||||||
|
olive::Node::set_value_at_time(
|
||||||
|
olive::NodeInput(node, QString::fromUtf8(input_id), element), time, v,
|
||||||
|
0, command, true);
|
||||||
|
push_or_run(command, QStringLiteral("Set Input Value"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_node_array_insert_at(OakEngineNode *self, const char *input_id,
|
||||||
|
int index)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Node *node = impl(self);
|
||||||
|
if (checked_input(node, input_id) == olive::NodeValue::k_none ||
|
||||||
|
index < 0) {
|
||||||
|
set_error(self && input_id && index >= 0 ?
|
||||||
|
QStringLiteral("unknown input id \"%1\"")
|
||||||
|
.arg(QString::fromUtf8(input_id)) :
|
||||||
|
QStringLiteral("invalid arguments"));
|
||||||
|
return self && input_id && index >= 0 ? OAKENGINE_E_NOT_FOUND :
|
||||||
|
OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
push_or_run(new olive::NodeArrayInsertCommand(
|
||||||
|
node, QString::fromUtf8(input_id), index),
|
||||||
|
QStringLiteral("Insert Array Element"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_node_array_remove_at(OakEngineNode *self, const char *input_id,
|
||||||
|
int index)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Node *node = impl(self);
|
||||||
|
if (checked_input(node, input_id) == olive::NodeValue::k_none ||
|
||||||
|
index < 0) {
|
||||||
|
set_error(self && input_id && index >= 0 ?
|
||||||
|
QStringLiteral("unknown input id \"%1\"")
|
||||||
|
.arg(QString::fromUtf8(input_id)) :
|
||||||
|
QStringLiteral("invalid arguments"));
|
||||||
|
return self && input_id && index >= 0 ? OAKENGINE_E_NOT_FOUND :
|
||||||
|
OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
const QString id = QString::fromUtf8(input_id);
|
||||||
|
const int size = olive::NodeInput(node, id).get_array_size();
|
||||||
|
if (index >= size) {
|
||||||
|
set_error(QStringLiteral("array index %1 out of range (size %2)")
|
||||||
|
.arg(index)
|
||||||
|
.arg(size));
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
push_or_run(new olive::NodeArrayRemoveCommand(node, id, index),
|
||||||
|
QStringLiteral("Remove Array Element"));
|
||||||
|
return OAKENGINE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
OakEngineNode *oakengine_project_add_node(OakEngineProject *project,
|
OakEngineNode *oakengine_project_add_node(OakEngineProject *project,
|
||||||
const char *type_id)
|
const char *type_id)
|
||||||
{
|
{
|
||||||
@@ -739,6 +954,12 @@ int oakengine_node_connect(OakEngineNode *output_node,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int oakengine_node_disconnect(OakEngineNode *input_node, const char *input_id)
|
int oakengine_node_disconnect(OakEngineNode *input_node, const char *input_id)
|
||||||
|
{
|
||||||
|
return oakengine_node_disconnect_ex(input_node, input_id, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_node_disconnect_ex(OakEngineNode *input_node,
|
||||||
|
const char *input_id, int element)
|
||||||
{
|
{
|
||||||
set_error(QString());
|
set_error(QString());
|
||||||
olive::Node *in_node = impl(input_node);
|
olive::Node *in_node = impl(input_node);
|
||||||
@@ -751,14 +972,13 @@ int oakengine_node_disconnect(OakEngineNode *input_node, const char *input_id)
|
|||||||
set_error(QStringLiteral("unknown input id \"%1\"").arg(id));
|
set_error(QStringLiteral("unknown input id \"%1\"").arg(id));
|
||||||
return OAKENGINE_E_NOT_FOUND;
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
}
|
}
|
||||||
olive::Node *connected =
|
const olive::NodeInput input(in_node, id, element);
|
||||||
in_node->get_connected_output(olive::NodeInput(in_node, id));
|
olive::Node *connected = in_node->get_connected_output(input);
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
set_error(QStringLiteral("input \"%1\" is not connected").arg(id));
|
set_error(QStringLiteral("input \"%1\" is not connected").arg(id));
|
||||||
return OAKENGINE_E_NOT_FOUND;
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
}
|
}
|
||||||
push_or_run(new olive::NodeEdgeRemoveCommand(
|
push_or_run(new olive::NodeEdgeRemoveCommand(connected, input),
|
||||||
connected, olive::NodeInput(in_node, id)),
|
|
||||||
QStringLiteral("Disconnect Nodes"));
|
QStringLiteral("Disconnect Nodes"));
|
||||||
return OAKENGINE_OK;
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
@@ -982,6 +1202,52 @@ int oakengine_node_keyframe_set_easing(OakEngineNode *self,
|
|||||||
return OAKENGINE_OK;
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int oakengine_node_keyframes_set_type_many(OakEngineNode *self,
|
||||||
|
const char *input_id, int element,
|
||||||
|
const int64_t *times_ts,
|
||||||
|
const int *tracks, int count,
|
||||||
|
int type)
|
||||||
|
{
|
||||||
|
set_error(QString());
|
||||||
|
olive::Node *node = impl(self);
|
||||||
|
if (checked_keyframe_input(node, input_id) == olive::NodeValue::k_none) {
|
||||||
|
return self && input_id ? OAKENGINE_E_NOT_FOUND : OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (type < 0 || type > 2 || count < 0 || (count > 0 && (!times_ts || !tracks))) {
|
||||||
|
set_error(QStringLiteral("invalid arguments"));
|
||||||
|
return OAKENGINE_E_INVALID;
|
||||||
|
}
|
||||||
|
if (count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const QString id = QString::fromUtf8(input_id);
|
||||||
|
const olive::Rational tb = project_time_base(node);
|
||||||
|
const olive::NodeInput input(node, id, element);
|
||||||
|
|
||||||
|
// Resolve every keyframe first so a bad address fails without side
|
||||||
|
// effects (same batch semantics as the view's context-menu action).
|
||||||
|
olive::MultiUndoCommand *command = new olive::MultiUndoCommand();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const olive::Rational time =
|
||||||
|
olive::core::Timecode::timestamp_to_time(times_ts[i], tb);
|
||||||
|
olive::NodeKeyframe *key =
|
||||||
|
node->get_keyframe_at_time_on_track(input, time, tracks[i]);
|
||||||
|
if (!key) {
|
||||||
|
set_error(QStringLiteral("no keyframe at time %1 track %2 on "
|
||||||
|
"\"%3\"")
|
||||||
|
.arg(times_ts[i])
|
||||||
|
.arg(tracks[i])
|
||||||
|
.arg(id));
|
||||||
|
delete command;
|
||||||
|
return OAKENGINE_E_NOT_FOUND;
|
||||||
|
}
|
||||||
|
command->add_child(
|
||||||
|
new KeyframeSetTypeCommand(key, to_engine_easing(type)));
|
||||||
|
}
|
||||||
|
push_or_run(command, QStringLiteral("Set Keyframe Type"));
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
int oakengine_node_keyframes_clear(OakEngineNode *self, const char *input_id)
|
int oakengine_node_keyframes_clear(OakEngineNode *self, const char *input_id)
|
||||||
{
|
{
|
||||||
set_error(QString());
|
set_error(QString());
|
||||||
|
|||||||
@@ -262,6 +262,162 @@ static void test_rational_and_color(OakEngineProject *project,
|
|||||||
assert(oakengine_node_keyframes_clear(solid, "color_in") == OAKENGINE_OK);
|
assert(oakengine_node_keyframes_clear(solid, "color_in") == OAKENGINE_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_panel_paths(OakEngineProject *project,
|
||||||
|
OakEngineNode *opacity, OakEngineNode *solid)
|
||||||
|
{
|
||||||
|
oak_node_value out;
|
||||||
|
|
||||||
|
// Frame time base (1001/30000 with the default sequence).
|
||||||
|
int tbn = 0, tbd = 0;
|
||||||
|
assert(oakengine_node_frame_time_base(opacity, &tbn, &tbd) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(tbn > 0 && tbd > 0);
|
||||||
|
assert(oakengine_node_frame_time_base(NULL, &tbn, &tbd) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
|
||||||
|
// set_input_at_time on a NON-keyframed input (fresh node) sets the
|
||||||
|
// standard value; undo restores it.
|
||||||
|
OakEngineNode *op2 = oakengine_project_add_node(
|
||||||
|
project, "org.olivevideoeditor.Olive.opacity");
|
||||||
|
assert(op2 != NULL);
|
||||||
|
oak_node_value v = float_value(0.75);
|
||||||
|
assert(oakengine_node_set_input_at_time(op2, "opacity_in", -1, 10, 0, &v,
|
||||||
|
1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_get_input(op2, "opacity_in", &out) == OAKENGINE_OK);
|
||||||
|
assert(fabs(out.f[0] - 0.75) < 1e-9);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_get_input(op2, "opacity_in", &out) == OAKENGINE_OK);
|
||||||
|
assert(fabs(out.f[0] - 0.75) > 1e-9);
|
||||||
|
|
||||||
|
// Component type must match the declared type; bad track/id rejected.
|
||||||
|
oak_node_value wrong;
|
||||||
|
memset(&wrong, 0, sizeof(wrong));
|
||||||
|
wrong.type = OAK_NODE_VALUE_INT;
|
||||||
|
assert(oakengine_node_set_input_at_time(op2, "opacity_in", -1, 0, 0,
|
||||||
|
&wrong,
|
||||||
|
1) == OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_node_set_input_at_time(op2, "opacity_in", -1, 0, 1, &v,
|
||||||
|
1) == OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_node_set_input_at_time(op2, "nope", -1, 0, 0, &v,
|
||||||
|
1) == OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_node_set_input_at_time(NULL, "opacity_in", -1, 0, 0, &v,
|
||||||
|
1) == OAKENGINE_E_INVALID);
|
||||||
|
|
||||||
|
// `opacity` is keyframed at this point (keys at 0/15/30 left by the
|
||||||
|
// earlier tests): the call writes a KEYFRAME at the time
|
||||||
|
// (set_value_at_time semantics) -- insert first, then update in place.
|
||||||
|
v = float_value(0.9);
|
||||||
|
assert(oakengine_node_set_input_at_time(opacity, "opacity_in", -1, 20, 0,
|
||||||
|
&v, 1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 4);
|
||||||
|
int64_t ts = -1;
|
||||||
|
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 2, &ts, &out) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(ts == 20 && fabs(out.f[0] - 0.9) < 1e-9);
|
||||||
|
v = float_value(0.8);
|
||||||
|
assert(oakengine_node_set_input_at_time(opacity, "opacity_in", -1, 20, 0,
|
||||||
|
&v, 1) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 4);
|
||||||
|
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 2, &ts, &out) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(fabs(out.f[0] - 0.8) < 1e-9);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 3);
|
||||||
|
|
||||||
|
// track -1 writes all color components in one command (fresh node, so
|
||||||
|
// the standard value itself is written and readable back).
|
||||||
|
OakEngineNode *solid2 = oakengine_project_add_node(
|
||||||
|
project, "org.olivevideoeditor.Olive.solidgenerator");
|
||||||
|
assert(solid2 != NULL);
|
||||||
|
oak_node_value c;
|
||||||
|
memset(&c, 0, sizeof(c));
|
||||||
|
c.type = OAK_NODE_VALUE_COLOR;
|
||||||
|
c.f[0] = 0.1;
|
||||||
|
c.f[1] = 0.2;
|
||||||
|
c.f[2] = 0.3;
|
||||||
|
c.f[3] = 0.4;
|
||||||
|
assert(oakengine_node_set_input_at_time(solid2, "color_in", -1, 5, -1, &c,
|
||||||
|
0) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_get_input(solid2, "color_in", &out) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(fabs(out.f[0] - 0.1) < 1e-6 && fabs(out.f[1] - 0.2) < 1e-6 &&
|
||||||
|
fabs(out.f[2] - 0.3) < 1e-6 && fabs(out.f[3] - 0.4) < 1e-6);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
|
||||||
|
// String-at-time on the text node; the POD path rejects strings and
|
||||||
|
// vice versa.
|
||||||
|
OakEngineNode *text = oakengine_project_add_node(
|
||||||
|
project, "org.olivevideoeditor.Olive.text3");
|
||||||
|
assert(text != NULL);
|
||||||
|
assert(oakengine_node_set_input_string_at_time(text, "text_in", -1, 0,
|
||||||
|
"hello") == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_set_input_string_at_time(text, "nope", -1, 0,
|
||||||
|
"x") == OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_node_set_input_string_at_time(op2, "opacity_in", -1, 0,
|
||||||
|
"x") == OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_node_set_input_at_time(text, "text_in", -1, 0, 0, &v,
|
||||||
|
1) == OAKENGINE_E_INVALID);
|
||||||
|
|
||||||
|
// Array insert/remove on the text node's args_in array.
|
||||||
|
assert(oakengine_node_array_insert_at(text, "args_in", 0) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_array_insert_at(text, "args_in", 1) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_array_remove_at(text, "args_in", 1) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_array_remove_at(text, "args_in", 5) ==
|
||||||
|
OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_node_array_remove_at(text, "args_in", 0) ==
|
||||||
|
OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_array_insert_at(text, "args_in", -1) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_node_array_remove_at(NULL, "args_in", 0) ==
|
||||||
|
OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
|
||||||
|
// keyframes_set_type_many: keys at 0 and 15 to hold in one command.
|
||||||
|
const int64_t times[2] = { 0, 15 };
|
||||||
|
const int tracks[2] = { 0, 0 };
|
||||||
|
assert(oakengine_node_keyframes_set_type_many(opacity, "opacity_in", -1,
|
||||||
|
times, tracks, 2, 2) == 2);
|
||||||
|
int type = -1;
|
||||||
|
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 0, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
&type) == OAKENGINE_OK);
|
||||||
|
assert(type == 2);
|
||||||
|
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
&type) == OAKENGINE_OK);
|
||||||
|
assert(type == 2);
|
||||||
|
// One undo restores each key's own previous easing (hold at 0, bezier
|
||||||
|
// at 15 -- left over from the earlier tests).
|
||||||
|
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||||
|
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 0, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
&type) == OAKENGINE_OK);
|
||||||
|
assert(type == 2);
|
||||||
|
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
&type) == OAKENGINE_OK);
|
||||||
|
assert(type == 1);
|
||||||
|
|
||||||
|
// A bad address fails without side effects; zero count is a no-op.
|
||||||
|
const int64_t bad[1] = { 99 };
|
||||||
|
assert(oakengine_node_keyframes_set_type_many(opacity, "opacity_in", -1,
|
||||||
|
bad, tracks, 1,
|
||||||
|
1) == OAKENGINE_E_NOT_FOUND);
|
||||||
|
assert(oakengine_node_keyframes_set_type_many(opacity, "opacity_in", -1,
|
||||||
|
times, tracks, 0, 1) == 0);
|
||||||
|
assert(oakengine_node_keyframes_set_type_many(NULL, "opacity_in", -1,
|
||||||
|
times, tracks, 1,
|
||||||
|
1) == OAKENGINE_E_INVALID);
|
||||||
|
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 3);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
make_tmpdir();
|
make_tmpdir();
|
||||||
@@ -295,6 +451,7 @@ int main(void)
|
|||||||
test_float_lifecycle(project, opacity);
|
test_float_lifecycle(project, opacity);
|
||||||
test_easing_and_remove(project, opacity);
|
test_easing_and_remove(project, opacity);
|
||||||
test_rational_and_color(project, timeremap, solid);
|
test_rational_and_color(project, timeremap, solid);
|
||||||
|
test_panel_paths(project, opacity, solid);
|
||||||
|
|
||||||
oakengine_project_free(project);
|
oakengine_project_free(project);
|
||||||
assert(oakengine_shutdown() == OAKENGINE_OK);
|
assert(oakengine_shutdown() == OAKENGINE_OK);
|
||||||
|
|||||||
Reference in New Issue
Block a user