engine: keyframe properties dialog migrates; keyframeviewundo deleted
- new primitives: keyframes_set_time_many (conflict-safe batch time
move), keyframes_set_value_many (captured or explicit old values),
keyframes_set_bezier_many (double precision), and
keyframe_set_bezier_point (single handle with NaN-capture fallback)
- dialog and curveview drag finalization go through the facade; the
tests exercise the same global undo stack via oakengine_project_undo
- keyframeview/keyframeviewundo.{h,cpp} removed with zero remaining
references
This commit is contained in:
@@ -24,13 +24,55 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "widget/keyframeview/keyframeviewundo.h"
|
||||
#include "oakengine/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Selected keyframes grouped by owning input (node/id/element), with
|
||||
// times converted to the facade's frame timestamps. The dialog's
|
||||
// per-property writes go through the facade as ONE undoable command per
|
||||
// group (usually just one).
|
||||
struct KeyGroup {
|
||||
Node *node;
|
||||
QString input;
|
||||
int element;
|
||||
QVector<int64_t> times;
|
||||
QVector<int> tracks;
|
||||
};
|
||||
|
||||
QVector<KeyGroup> group_keys(const std::vector<NodeKeyframe *> &keys)
|
||||
{
|
||||
QVector<KeyGroup> groups;
|
||||
for (NodeKeyframe *item : keys) {
|
||||
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());
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KeyframePropertiesDialog::KeyframePropertiesDialog(
|
||||
const std::vector<NodeKeyframe *> &keys, const Rational &timebase,
|
||||
QWidget *parent)
|
||||
@@ -203,37 +245,57 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(
|
||||
|
||||
void KeyframePropertiesDialog::accept()
|
||||
{
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
const Rational new_time = time_slider_->get_value();
|
||||
const int new_type = type_select_->currentData().toInt();
|
||||
|
||||
Rational new_time = time_slider_->get_value();
|
||||
int new_type = type_select_->currentData().toInt();
|
||||
const QVector<KeyGroup> groups = group_keys(keys_);
|
||||
|
||||
foreach (NodeKeyframe *key, keys_) {
|
||||
if (time_slider_->isEnabled() && !time_slider_->is_tristate()) {
|
||||
command->add_child(
|
||||
new NodeParamSetKeyframeTimeCommand(key, new_time));
|
||||
if (new_type > -1) {
|
||||
// Engine type (linear 0 / hold 1 / bezier 2) to the facade's
|
||||
// easing order (linear 0 / bezier 1 / hold 2).
|
||||
int facade_type = 0;
|
||||
if (new_type == NodeKeyframe::k_bezier) {
|
||||
facade_type = 1;
|
||||
} else if (new_type == NodeKeyframe::k_hold) {
|
||||
facade_type = 2;
|
||||
}
|
||||
|
||||
if (new_type > -1) {
|
||||
command->add_child(new KeyframeSetTypeCommand(
|
||||
key, static_cast<NodeKeyframe::Type>(new_type)));
|
||||
}
|
||||
|
||||
if (bezier_group_->isEnabled()) {
|
||||
command->add_child(new KeyframeSetBezierControlPoint(
|
||||
key, NodeKeyframe::k_in_handle,
|
||||
QPointF(bezier_in_x_slider_->get_value(),
|
||||
bezier_in_y_slider_->get_value())));
|
||||
|
||||
command->add_child(new KeyframeSetBezierControlPoint(
|
||||
key, NodeKeyframe::k_out_handle,
|
||||
QPointF(bezier_out_x_slider_->get_value(),
|
||||
bezier_out_y_slider_->get_value())));
|
||||
foreach (const KeyGroup &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 Keyframe Properties"));
|
||||
if (bezier_group_->isEnabled()) {
|
||||
foreach (const KeyGroup &g, groups) {
|
||||
oakengine_node_keyframes_set_bezier_many(
|
||||
reinterpret_cast<OakEngineNode *>(g.node),
|
||||
g.input.toUtf8().constData(), g.element, g.times.constData(),
|
||||
g.tracks.data(), g.times.size(),
|
||||
bezier_in_x_slider_->get_value(),
|
||||
bezier_in_y_slider_->get_value(),
|
||||
bezier_out_x_slider_->get_value(),
|
||||
bezier_out_y_slider_->get_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Time moves go LAST: the facade addresses keyframes by time, so the
|
||||
// type/bezier writes above must happen while the keys still sit at
|
||||
// the times the groups were built from.
|
||||
if (time_slider_->isEnabled() && !time_slider_->is_tristate()) {
|
||||
foreach (const KeyGroup &g, groups) {
|
||||
OakEngineNode *handle =
|
||||
reinterpret_cast<OakEngineNode *>(g.node);
|
||||
int tbn = 0, tbd = 0;
|
||||
oakengine_node_frame_time_base(handle, &tbn, &tbd);
|
||||
oakengine_node_keyframes_set_time_many(
|
||||
handle, g.input.toUtf8().constData(), g.element,
|
||||
g.times.constData(), g.tracks.data(), g.times.size(),
|
||||
Timecode::time_to_timestamp(new_time, Rational(tbn, tbd),
|
||||
Timecode::k_round));
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
@@ -30,14 +30,67 @@
|
||||
|
||||
#include "common/decibel.h"
|
||||
#include "common/qtutils.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "widget/keyframeview/keyframeviewundo.h"
|
||||
#include "oakengine/node.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super KeyframeView
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Map a keyframe track's scalar QVariant into the facade POD for the
|
||||
// input's declared type (the curve view drags numeric tracks).
|
||||
void track_value_to_c(NodeValue::Type declared, const QVariant &value,
|
||||
oak_node_value *out)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
switch (declared) {
|
||||
case NodeValue::k_int:
|
||||
out->type = OAK_NODE_VALUE_INT;
|
||||
out->num = value.toLongLong();
|
||||
break;
|
||||
case NodeValue::k_combo:
|
||||
out->type = OAK_NODE_VALUE_COMBO;
|
||||
out->num = value.toLongLong();
|
||||
break;
|
||||
case NodeValue::k_boolean:
|
||||
out->type = OAK_NODE_VALUE_BOOL;
|
||||
out->num = value.toBool() ? 1 : 0;
|
||||
break;
|
||||
case NodeValue::k_rational: {
|
||||
const Rational r = value.value<Rational>();
|
||||
out->type = OAK_NODE_VALUE_RATIONAL;
|
||||
out->num = r.numerator();
|
||||
out->den = r.denominator();
|
||||
break;
|
||||
}
|
||||
case NodeValue::k_color:
|
||||
out->type = OAK_NODE_VALUE_COLOR;
|
||||
out->f[0] = value.toDouble();
|
||||
break;
|
||||
case NodeValue::k_vec2:
|
||||
out->type = OAK_NODE_VALUE_VEC2;
|
||||
out->f[0] = value.toDouble();
|
||||
break;
|
||||
case NodeValue::k_vec3:
|
||||
out->type = OAK_NODE_VALUE_VEC3;
|
||||
out->f[0] = value.toDouble();
|
||||
break;
|
||||
case NodeValue::k_vec4:
|
||||
out->type = OAK_NODE_VALUE_VEC4;
|
||||
out->f[0] = value.toDouble();
|
||||
break;
|
||||
default:
|
||||
out->type = OAK_NODE_VALUE_FLOAT;
|
||||
out->f[0] = value.toDouble();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CurveView::CurveView(QWidget *parent)
|
||||
: KeyframeView(parent)
|
||||
, dragging_bezier_pt_(nullptr)
|
||||
@@ -410,28 +463,40 @@ void CurveView::first_chance_mouse_move(QMouseEvent *event)
|
||||
|
||||
void CurveView::first_chance_mouse_release(QMouseEvent *event)
|
||||
{
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
// Create undo command with the current bezier point and the old one
|
||||
command->add_child(new KeyframeSetBezierControlPoint(
|
||||
dragging_bezier_pt_->keyframe, dragging_bezier_pt_->type,
|
||||
dragging_bezier_pt_->keyframe->bezier_control(dragging_bezier_pt_->type),
|
||||
dragging_bezier_point_start_));
|
||||
// Through the liboakengine C ABI facade with the drag-start point(s)
|
||||
// as the explicit old values (the drag already live-set the new
|
||||
// ones); one undoable command per handle, same as the old
|
||||
// KeyframeSetBezierControlPoint children.
|
||||
NodeKeyframe *key = dragging_bezier_pt_->keyframe;
|
||||
OakEngineNode *handle =
|
||||
reinterpret_cast<OakEngineNode *>(key->parent());
|
||||
int tbn = 0, tbd = 0;
|
||||
oakengine_node_frame_time_base(handle, &tbn, &tbd);
|
||||
const int64_t ts = Timecode::time_to_timestamp(
|
||||
key->time(), Rational(tbn, tbd), Timecode::k_round);
|
||||
const QPointF current =
|
||||
key->bezier_control(dragging_bezier_pt_->type);
|
||||
oakengine_node_keyframe_set_bezier_point(
|
||||
handle, key->input().toUtf8().constData(), key->element(), ts,
|
||||
key->track(),
|
||||
(dragging_bezier_pt_->type == NodeKeyframe::k_in_handle) ? 0 : 1,
|
||||
current.x(), current.y(), dragging_bezier_point_start_.x(),
|
||||
dragging_bezier_point_start_.y());
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
auto opposing_type =
|
||||
NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
|
||||
|
||||
command->add_child(new KeyframeSetBezierControlPoint(
|
||||
dragging_bezier_pt_->keyframe, opposing_type,
|
||||
dragging_bezier_pt_->keyframe->bezier_control(opposing_type),
|
||||
dragging_bezier_point_opposing_start_));
|
||||
const QPointF opposing_current = key->bezier_control(opposing_type);
|
||||
oakengine_node_keyframe_set_bezier_point(
|
||||
handle, key->input().toUtf8().constData(), key->element(), ts,
|
||||
key->track(),
|
||||
(opposing_type == NodeKeyframe::k_in_handle) ? 0 : 1,
|
||||
opposing_current.x(), opposing_current.y(),
|
||||
dragging_bezier_point_opposing_start_.x(),
|
||||
dragging_bezier_point_opposing_start_.y());
|
||||
}
|
||||
|
||||
dragging_bezier_pt_ = nullptr;
|
||||
|
||||
Core::instance()->undo_stack()->push(
|
||||
command, tr("Moved Keyframe Bezier Control Point"));
|
||||
}
|
||||
|
||||
void CurveView::keyframe_drag_start(QMouseEvent *event)
|
||||
@@ -517,13 +582,65 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
|
||||
void CurveView::keyframe_drag_release(QMouseEvent *event,
|
||||
MultiUndoCommand *command)
|
||||
{
|
||||
Q_UNUSED(command) // the facade pushes its own single command below
|
||||
|
||||
// Group the changed keys by owning input and push ONE undoable
|
||||
// command per group through the liboakengine C ABI facade, with the
|
||||
// drag-start values as the explicit undo values (the drag already
|
||||
// live-set the new ones).
|
||||
struct ValueGroup {
|
||||
Node *node;
|
||||
QString input;
|
||||
int element;
|
||||
QVector<int64_t> times;
|
||||
QVector<int> tracks;
|
||||
std::vector<oak_node_value> values;
|
||||
std::vector<oak_node_value> olds;
|
||||
};
|
||||
QVector<ValueGroup> groups;
|
||||
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
|
||||
NodeKeyframe *k = get_selected_keyframes().at(i);
|
||||
if (!qFuzzyCompare(k->value().toDouble(),
|
||||
drag_keyframe_values_.at(i).toDouble())) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(
|
||||
k, k->value(), drag_keyframe_values_.at(i)));
|
||||
if (qFuzzyCompare(k->value().toDouble(),
|
||||
drag_keyframe_values_.at(i).toDouble())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int g = 0;
|
||||
for (; g < groups.size(); g++) {
|
||||
if (groups.at(g).node == k->parent() &&
|
||||
groups.at(g).input == k->input() &&
|
||||
groups.at(g).element == k->element()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (g == groups.size()) {
|
||||
groups.append(
|
||||
{ k->parent(), k->input(), k->element(), {}, {}, {}, {} });
|
||||
}
|
||||
|
||||
OakEngineNode *handle =
|
||||
reinterpret_cast<OakEngineNode *>(k->parent());
|
||||
int tbn = 0, tbd = 0;
|
||||
oakengine_node_frame_time_base(handle, &tbn, &tbd);
|
||||
groups[g].times.append(Timecode::time_to_timestamp(
|
||||
k->time(), Rational(tbn, tbd), Timecode::k_round));
|
||||
groups[g].tracks.append(k->track());
|
||||
|
||||
const NodeValue::Type declared =
|
||||
k->parent()->get_input_data_type(k->input());
|
||||
oak_node_value new_v, old_v;
|
||||
track_value_to_c(declared, k->value(), &new_v);
|
||||
track_value_to_c(declared, drag_keyframe_values_.at(i), &old_v);
|
||||
groups[g].values.push_back(new_v);
|
||||
groups[g].olds.push_back(old_v);
|
||||
}
|
||||
|
||||
foreach (const ValueGroup &g, groups) {
|
||||
oakengine_node_keyframes_set_value_many(
|
||||
reinterpret_cast<OakEngineNode *>(g.node),
|
||||
g.input.toUtf8().constData(), g.element, g.times.constData(),
|
||||
g.tracks.data(), g.times.size(), g.values.data(),
|
||||
g.olds.data());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,5 @@ set(OLIVE_SOURCES
|
||||
widget/keyframeview/keyframeview.h
|
||||
widget/keyframeview/keyframeviewinputconnection.cpp
|
||||
widget/keyframeview/keyframeviewinputconnection.h
|
||||
widget/keyframeview/keyframeviewundo.cpp
|
||||
widget/keyframeview/keyframeviewundo.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "keyframeviewundo.h"
|
||||
|
||||
#include "node/node.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
KeyframeSetTypeCommand::KeyframeSetTypeCommand(NodeKeyframe *key,
|
||||
NodeKeyframe::Type type)
|
||||
: key_(key)
|
||||
, old_type_(key->type())
|
||||
, new_type_(type)
|
||||
{
|
||||
}
|
||||
|
||||
Project *KeyframeSetTypeCommand::get_relevant_project() const
|
||||
{
|
||||
return key_->parent()->project();
|
||||
}
|
||||
|
||||
void KeyframeSetTypeCommand::redo()
|
||||
{
|
||||
key_->set_type(new_type_);
|
||||
}
|
||||
|
||||
void KeyframeSetTypeCommand::undo()
|
||||
{
|
||||
key_->set_type(old_type_);
|
||||
}
|
||||
|
||||
KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(
|
||||
NodeKeyframe *key, NodeKeyframe::BezierType mode, const QPointF &point)
|
||||
: key_(key)
|
||||
, mode_(mode)
|
||||
, old_point_(key->bezier_control(mode_))
|
||||
, new_point_(point)
|
||||
{
|
||||
}
|
||||
|
||||
KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(
|
||||
NodeKeyframe *key, NodeKeyframe::BezierType mode, const QPointF &new_point,
|
||||
const QPointF &old_point)
|
||||
: key_(key)
|
||||
, mode_(mode)
|
||||
, old_point_(old_point)
|
||||
, new_point_(new_point)
|
||||
{
|
||||
}
|
||||
|
||||
Project *KeyframeSetBezierControlPoint::get_relevant_project() const
|
||||
{
|
||||
return key_->parent()->project();
|
||||
}
|
||||
|
||||
void KeyframeSetBezierControlPoint::redo()
|
||||
{
|
||||
key_->set_bezier_control(mode_, new_point_);
|
||||
}
|
||||
|
||||
void KeyframeSetBezierControlPoint::undo()
|
||||
{
|
||||
key_->set_bezier_control(mode_, old_point_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef OAK_KEYFRAMEVIEWUNDO_H
|
||||
#define OAK_KEYFRAMEVIEWUNDO_H
|
||||
|
||||
#include "node/keyframe.h"
|
||||
#include "undo/undocommand.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class KeyframeSetTypeCommand : public UndoCommand {
|
||||
public:
|
||||
KeyframeSetTypeCommand(NodeKeyframe *key, NodeKeyframe::Type type);
|
||||
|
||||
virtual Project *get_relevant_project() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeKeyframe *key_;
|
||||
|
||||
NodeKeyframe::Type old_type_;
|
||||
|
||||
NodeKeyframe::Type new_type_;
|
||||
};
|
||||
|
||||
class KeyframeSetBezierControlPoint : public UndoCommand {
|
||||
public:
|
||||
KeyframeSetBezierControlPoint(NodeKeyframe *key,
|
||||
NodeKeyframe::BezierType mode,
|
||||
const QPointF &point);
|
||||
KeyframeSetBezierControlPoint(NodeKeyframe *key,
|
||||
NodeKeyframe::BezierType mode,
|
||||
const QPointF &new_point,
|
||||
const QPointF &old_point);
|
||||
|
||||
virtual Project *get_relevant_project() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeKeyframe *key_;
|
||||
|
||||
NodeKeyframe::BezierType mode_;
|
||||
|
||||
QPointF old_point_;
|
||||
|
||||
QPointF new_point_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_KEYFRAMEVIEWUNDO_H
|
||||
@@ -437,6 +437,72 @@ 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 Move several keyframes of one input to `new_time_ts` (undoable,
|
||||
* ONE command; olive::NodeParamSetKeyframeTimeCommand per key, like the
|
||||
* application's keyframe properties dialog).
|
||||
*
|
||||
* Keyframes are addressed individually by (`old_times_ts`[i],
|
||||
* `tracks`[i]); `element` addresses the input's array element (-1 for
|
||||
* non-array). Every old address must name an existing keyframe
|
||||
* (OAKENGINE_E_NOT_FOUND otherwise), and no other keyframe may already
|
||||
* sit at `new_time_ts` on a target track (OAKENGINE_E_STATE; moving to
|
||||
* the key's own current time is allowed). On any failure nothing is
|
||||
* pushed. Returns the number of moved keyframes (>= 0) or a negative
|
||||
* code.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_node_keyframes_set_time_many(
|
||||
OakEngineNode *self, const char *input_id, int element,
|
||||
const int64_t *old_times_ts, const int *tracks, int count,
|
||||
int64_t new_time_ts);
|
||||
|
||||
/**
|
||||
* @brief Change the value of several keyframes of one input (undoable,
|
||||
* ONE command; olive::NodeParamSetKeyframeValueCommand per key).
|
||||
*
|
||||
* `values`[i] is the new per-track component (mapped like
|
||||
* oakengine_node_set_input_at_time(); the type must match the input's
|
||||
* declared type). When `old_values` is not NULL, `old_values`[i] is
|
||||
* recorded as the undo value -- for callers that already live-set the
|
||||
* new values (the curve view's drag release); when NULL, each key's
|
||||
* current value is captured at apply time. Every address must name an
|
||||
* existing keyframe (OAKENGINE_E_NOT_FOUND; nothing pushed on failure).
|
||||
* Returns the number of changed keyframes (>= 0) or a negative code.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_node_keyframes_set_value_many(
|
||||
OakEngineNode *self, const char *input_id, int element,
|
||||
const int64_t *times_ts, const int *tracks, int count,
|
||||
const oak_node_value *values, const oak_node_value *old_values);
|
||||
|
||||
/**
|
||||
* @brief Set both bezier control points of several keyframes of one
|
||||
* input (undoable, ONE command; two point commands per key, like the
|
||||
* keyframe properties dialog). The previous points are captured per
|
||||
* key. Every address must name an existing keyframe
|
||||
* (OAKENGINE_E_NOT_FOUND; nothing pushed on failure). Returns the
|
||||
* number of affected keyframes (>= 0) or a negative code.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_node_keyframes_set_bezier_many(
|
||||
OakEngineNode *self, const char *input_id, int element,
|
||||
const int64_t *times_ts, const int *tracks, int count, double in_x,
|
||||
double in_y, double out_x, double out_y);
|
||||
|
||||
/**
|
||||
* @brief Set one bezier control point of one keyframe (undoable;
|
||||
* the curve view's bezier-handle drag release).
|
||||
*
|
||||
* `point_index` is 0 for the in-handle and 1 for the out-handle. The
|
||||
* new point is (x, y); (`old_x`, `old_y`) is the undo point recorded
|
||||
* for callers that already live-set the new point during the drag --
|
||||
* pass NaN for either old component to capture the key's current point
|
||||
* at apply time instead. OAKENGINE_E_NOT_FOUND when no keyframe exists
|
||||
* at that time/track.
|
||||
*/
|
||||
OAKENGINE_API int oakengine_node_keyframe_set_bezier_point(
|
||||
OakEngineNode *self, const char *input_id, int element,
|
||||
int64_t time_ts, int track, int point_index, double x, double y,
|
||||
double old_x, double old_y);
|
||||
|
||||
/**
|
||||
* @brief Remove all keyframes from the input (undoable,
|
||||
* olive::NodeImmediateRemoveAllKeyframesCommand). A no-op (OAKENGINE_OK)
|
||||
|
||||
+260
-5
@@ -347,9 +347,9 @@ bool kf_value_to_c(olive::NodeValue::Type type, const QVariant &component,
|
||||
}
|
||||
|
||||
// Undo commands for easing changes. The engine's undo stack has no
|
||||
// keyframe type/bezier commands -- they live in the application layer
|
||||
// (app/widget/keyframeviewundo.h), so the facade carries minimal
|
||||
// equivalents with the same old/new semantics.
|
||||
// keyframe type/bezier commands (the application layer used to carry
|
||||
// them in app/widget/keyframeviewundo.h, since migrated here), so the
|
||||
// facade carries minimal equivalents with the same old/new semantics.
|
||||
class KeyframeSetTypeCommand : public olive::UndoCommand {
|
||||
public:
|
||||
KeyframeSetTypeCommand(olive::NodeKeyframe *key,
|
||||
@@ -393,6 +393,20 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// Explicit old point for callers that already live-set the new one
|
||||
// (mirrors the application class's second constructor).
|
||||
KeyframeSetBezierPointCommand(olive::NodeKeyframe *key,
|
||||
olive::NodeKeyframe::BezierType mode,
|
||||
const QPointF &new_point,
|
||||
const QPointF &old_point)
|
||||
: key_(key)
|
||||
, mode_(mode)
|
||||
, has_old_(true)
|
||||
, old_point_(old_point)
|
||||
, new_point_(new_point)
|
||||
{
|
||||
}
|
||||
|
||||
virtual olive::Project *get_relevant_project() const override
|
||||
{
|
||||
return key_->parent() ? key_->parent()->project() : nullptr;
|
||||
@@ -401,7 +415,10 @@ public:
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
old_point_ = key_->bezier_control(mode_);
|
||||
if (!has_old_) {
|
||||
old_point_ = key_->bezier_control(mode_);
|
||||
has_old_ = true;
|
||||
}
|
||||
key_->set_bezier_control(mode_, new_point_);
|
||||
}
|
||||
|
||||
@@ -413,6 +430,7 @@ protected:
|
||||
private:
|
||||
olive::NodeKeyframe *key_;
|
||||
olive::NodeKeyframe::BezierType mode_;
|
||||
bool has_old_ = false;
|
||||
QPointF old_point_;
|
||||
QPointF new_point_;
|
||||
};
|
||||
@@ -435,6 +453,27 @@ olive::NodeValue::Type checked_keyframe_input(const olive::Node *self,
|
||||
return type;
|
||||
}
|
||||
|
||||
// Time-exact keyframe lookup that does not depend on the input's
|
||||
// keyframing-enabled flag (Node::get_keyframe_at_time_on_track reports
|
||||
// nothing when keyframing is off, but the application's keyframe editing
|
||||
// operates on the keyframe objects regardless of the flag).
|
||||
olive::NodeKeyframe *find_keyframe(const olive::Node *node,
|
||||
const olive::NodeInput &input,
|
||||
const olive::Rational &time, int track)
|
||||
{
|
||||
const QVector<olive::NodeKeyframeTrack> &tracks =
|
||||
node->get_keyframe_tracks(input.input(), input.element());
|
||||
if (track < 0 || track >= tracks.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
for (olive::NodeKeyframe *key : tracks.at(track)) {
|
||||
if (key->time() == time) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C"
|
||||
@@ -1231,7 +1270,7 @@ int oakengine_node_keyframes_set_type_many(OakEngineNode *self,
|
||||
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]);
|
||||
find_keyframe(node, input, time, tracks[i]);
|
||||
if (!key) {
|
||||
set_error(QStringLiteral("no keyframe at time %1 track %2 on "
|
||||
"\"%3\"")
|
||||
@@ -1248,6 +1287,222 @@ int oakengine_node_keyframes_set_type_many(OakEngineNode *self,
|
||||
return count;
|
||||
}
|
||||
|
||||
int oakengine_node_keyframes_set_time_many(OakEngineNode *self,
|
||||
const char *input_id, int element,
|
||||
const int64_t *old_times_ts,
|
||||
const int *tracks, int count,
|
||||
int64_t new_time_ts)
|
||||
{
|
||||
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 (count < 0 || (count > 0 && (!old_times_ts || !tracks)) ||
|
||||
new_time_ts < 0) {
|
||||
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);
|
||||
const olive::Rational new_time =
|
||||
olive::core::Timecode::timestamp_to_time(new_time_ts, tb);
|
||||
|
||||
// Resolve and conflict-check every key first so a failure has no side
|
||||
// effects.
|
||||
olive::MultiUndoCommand *command = new olive::MultiUndoCommand();
|
||||
for (int i = 0; i < count; i++) {
|
||||
const olive::Rational old_time =
|
||||
olive::core::Timecode::timestamp_to_time(old_times_ts[i], tb);
|
||||
olive::NodeKeyframe *key =
|
||||
find_keyframe(node, input, old_time, tracks[i]);
|
||||
if (!key) {
|
||||
set_error(QStringLiteral("no keyframe at time %1 track %2 on "
|
||||
"\"%3\"")
|
||||
.arg(old_times_ts[i])
|
||||
.arg(tracks[i])
|
||||
.arg(id));
|
||||
delete command;
|
||||
return OAKENGINE_E_NOT_FOUND;
|
||||
}
|
||||
olive::NodeKeyframe *occupant =
|
||||
find_keyframe(node, input, new_time, tracks[i]);
|
||||
if (occupant && occupant != key) {
|
||||
set_error(QStringLiteral("a keyframe already exists at time %1 "
|
||||
"on track %2")
|
||||
.arg(new_time_ts)
|
||||
.arg(tracks[i]));
|
||||
delete command;
|
||||
return OAKENGINE_E_STATE;
|
||||
}
|
||||
command->add_child(new olive::NodeParamSetKeyframeTimeCommand(
|
||||
key, new_time, key->time()));
|
||||
}
|
||||
push_or_run(command, QStringLiteral("Set Keyframe Time"));
|
||||
return count;
|
||||
}
|
||||
|
||||
int oakengine_node_keyframes_set_value_many(OakEngineNode *self,
|
||||
const char *input_id, int element,
|
||||
const int64_t *times_ts,
|
||||
const int *tracks, int count,
|
||||
const oak_node_value *values,
|
||||
const oak_node_value *old_values)
|
||||
{
|
||||
set_error(QString());
|
||||
olive::Node *node = impl(self);
|
||||
const olive::NodeValue::Type declared =
|
||||
checked_keyframe_input(node, input_id);
|
||||
if (declared == olive::NodeValue::k_none) {
|
||||
return self && input_id ? OAKENGINE_E_NOT_FOUND : OAKENGINE_E_INVALID;
|
||||
}
|
||||
if (count < 0 || (count > 0 && (!times_ts || !tracks || !values))) {
|
||||
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);
|
||||
|
||||
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 =
|
||||
find_keyframe(node, 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;
|
||||
}
|
||||
QVariant new_value;
|
||||
if (!component_from_c(&values[i], declared, 0, &new_value)) {
|
||||
set_error(QStringLiteral(
|
||||
"value type does not match the declared input type"));
|
||||
delete command;
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
if (old_values) {
|
||||
QVariant old_value;
|
||||
if (!component_from_c(&old_values[i], declared, 0, &old_value)) {
|
||||
set_error(QStringLiteral(
|
||||
"old value type does not match the declared input type"));
|
||||
delete command;
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
command->add_child(new olive::NodeParamSetKeyframeValueCommand(
|
||||
key, new_value, old_value));
|
||||
} else {
|
||||
command->add_child(new olive::NodeParamSetKeyframeValueCommand(
|
||||
key, new_value, key->value()));
|
||||
}
|
||||
}
|
||||
push_or_run(command, QStringLiteral("Set Keyframe Value"));
|
||||
return count;
|
||||
}
|
||||
|
||||
int oakengine_node_keyframes_set_bezier_many(OakEngineNode *self,
|
||||
const char *input_id,
|
||||
int element,
|
||||
const int64_t *times_ts,
|
||||
const int *tracks, int count,
|
||||
double in_x, double in_y,
|
||||
double out_x, double out_y)
|
||||
{
|
||||
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 (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);
|
||||
|
||||
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 =
|
||||
find_keyframe(node, 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 KeyframeSetBezierPointCommand(
|
||||
key, olive::NodeKeyframe::k_in_handle, QPointF(in_x, in_y)));
|
||||
command->add_child(new KeyframeSetBezierPointCommand(
|
||||
key, olive::NodeKeyframe::k_out_handle, QPointF(out_x, out_y)));
|
||||
}
|
||||
push_or_run(command, QStringLiteral("Set Keyframe Bezier Points"));
|
||||
return count;
|
||||
}
|
||||
|
||||
int oakengine_node_keyframe_set_bezier_point(
|
||||
OakEngineNode *self, const char *input_id, int element, int64_t time_ts,
|
||||
int track, int point_index, double x, double y, double old_x,
|
||||
double old_y)
|
||||
{
|
||||
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 (point_index < 0 || point_index > 1) {
|
||||
set_error(QStringLiteral("invalid bezier point index %1")
|
||||
.arg(point_index));
|
||||
return OAKENGINE_E_INVALID;
|
||||
}
|
||||
const QString id = QString::fromUtf8(input_id);
|
||||
const olive::Rational tb = project_time_base(node);
|
||||
const olive::Rational time =
|
||||
olive::core::Timecode::timestamp_to_time(time_ts, tb);
|
||||
olive::NodeKeyframe *key = find_keyframe(
|
||||
node, olive::NodeInput(node, id, element), time, track);
|
||||
if (!key) {
|
||||
set_error(QStringLiteral("no keyframe at time %1 track %2 on \"%3\"")
|
||||
.arg(time_ts)
|
||||
.arg(track)
|
||||
.arg(id));
|
||||
return OAKENGINE_E_NOT_FOUND;
|
||||
}
|
||||
const olive::NodeKeyframe::BezierType mode =
|
||||
(point_index == 0) ? olive::NodeKeyframe::k_in_handle :
|
||||
olive::NodeKeyframe::k_out_handle;
|
||||
if (std::isnan(old_x) || std::isnan(old_y)) {
|
||||
push_or_run(new KeyframeSetBezierPointCommand(key, mode,
|
||||
QPointF(x, y)),
|
||||
QStringLiteral("Set Keyframe Bezier Point"));
|
||||
} else {
|
||||
push_or_run(new KeyframeSetBezierPointCommand(
|
||||
key, mode, QPointF(x, y), QPointF(old_x, old_y)),
|
||||
QStringLiteral("Set Keyframe Bezier Point"));
|
||||
}
|
||||
return OAKENGINE_OK;
|
||||
}
|
||||
|
||||
int oakengine_node_keyframes_clear(OakEngineNode *self, const char *input_id)
|
||||
{
|
||||
set_error(QString());
|
||||
|
||||
@@ -418,6 +418,150 @@ static void test_panel_paths(OakEngineProject *project,
|
||||
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 3);
|
||||
}
|
||||
|
||||
static void test_keyframe_properties(OakEngineProject *project,
|
||||
OakEngineNode *opacity)
|
||||
{
|
||||
// `opacity` has three keys at 0/15/30 (values 0.0/0.5/1.0) with
|
||||
// easings hold/bezier/linear from the earlier tests.
|
||||
oak_node_value out;
|
||||
int64_t ts = -1;
|
||||
const int tr1[1] = { 0 };
|
||||
|
||||
// set_time_many: move 15 -> 20 and undo.
|
||||
const int64_t olds[1] = { 15 };
|
||||
assert(oakengine_node_keyframes_set_time_many(opacity, "opacity_in", -1,
|
||||
olds, tr1, 1, 20) == 1);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 1, &ts, NULL) ==
|
||||
OAKENGINE_OK);
|
||||
assert(ts == 20);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 1, &ts, NULL) ==
|
||||
OAKENGINE_OK);
|
||||
assert(ts == 15);
|
||||
|
||||
// Moving onto an occupied time is a conflict; onto the key's own time
|
||||
// is allowed; a missing key is NOT_FOUND. Failures push nothing.
|
||||
assert(oakengine_node_keyframes_set_time_many(opacity, "opacity_in", -1,
|
||||
olds, tr1, 1,
|
||||
30) == OAKENGINE_E_STATE);
|
||||
assert(oakengine_node_keyframes_set_time_many(opacity, "opacity_in", -1,
|
||||
olds, tr1, 1, 15) == 1);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
const int64_t miss[1] = { 99 };
|
||||
assert(oakengine_node_keyframes_set_time_many(opacity, "opacity_in", -1,
|
||||
miss, tr1, 1,
|
||||
40) == OAKENGINE_E_NOT_FOUND);
|
||||
assert(oakengine_node_keyframes_set_time_many(NULL, "opacity_in", -1,
|
||||
olds, tr1, 1,
|
||||
40) == OAKENGINE_E_INVALID);
|
||||
assert(oakengine_node_keyframes_set_time_many(opacity, "opacity_in", -1,
|
||||
olds, tr1, 0, 40) == 0);
|
||||
assert(oakengine_node_keyframe_count(opacity, "opacity_in") == 3);
|
||||
|
||||
// set_value_many: captured old values are restored by undo.
|
||||
const int64_t times2[2] = { 0, 15 };
|
||||
const int tr2[2] = { 0, 0 };
|
||||
oak_node_value nv[2];
|
||||
nv[0] = float_value(0.7);
|
||||
nv[1] = float_value(0.6);
|
||||
assert(oakengine_node_keyframes_set_value_many(opacity, "opacity_in", -1,
|
||||
times2, tr2, 2, nv,
|
||||
NULL) == 2);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 0, &ts, &out) ==
|
||||
OAKENGINE_OK);
|
||||
assert(fabs(out.f[0] - 0.7) < 1e-9);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 1, &ts, &out) ==
|
||||
OAKENGINE_OK);
|
||||
assert(fabs(out.f[0] - 0.6) < 1e-9);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 0, &ts, &out) ==
|
||||
OAKENGINE_OK);
|
||||
assert(fabs(out.f[0] - 0.0) < 1e-9);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 1, &ts, &out) ==
|
||||
OAKENGINE_OK);
|
||||
assert(fabs(out.f[0] - 0.5) < 1e-9);
|
||||
|
||||
// Explicit old values (the live-set drag pattern): undo restores the
|
||||
// recorded old, redo the new.
|
||||
oak_node_value ov[2];
|
||||
ov[0] = float_value(9.9);
|
||||
ov[1] = float_value(9.8);
|
||||
assert(oakengine_node_keyframes_set_value_many(opacity, "opacity_in", -1,
|
||||
times2, tr2, 2, nv,
|
||||
ov) == 2);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_at(opacity, "opacity_in", 0, &ts, &out) ==
|
||||
OAKENGINE_OK);
|
||||
assert(fabs(out.f[0] - 9.9) < 1e-9);
|
||||
assert(oakengine_project_redo(project) == OAKENGINE_OK);
|
||||
oak_node_value orig[2];
|
||||
orig[0] = float_value(0.0);
|
||||
orig[1] = float_value(0.5);
|
||||
assert(oakengine_node_keyframes_set_value_many(opacity, "opacity_in", -1,
|
||||
times2, tr2, 2, orig,
|
||||
NULL) == 2);
|
||||
|
||||
// bezier_many: both control points on both keys, undo restores each
|
||||
// key's own previous points (key 15 had 0.1/0.2/0.3/0.4).
|
||||
assert(oakengine_node_keyframes_set_bezier_many(
|
||||
opacity, "opacity_in", -1, times2, tr2, 2, 0.11f, 0.22f, 0.33f,
|
||||
0.44f) == 2);
|
||||
float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
||||
int type = -1;
|
||||
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, &x1,
|
||||
&y1, &x2, &y2,
|
||||
&type) == OAKENGINE_OK);
|
||||
assert(fabsf(x1 - 0.11f) < 1e-6f && fabsf(y1 - 0.22f) < 1e-6f &&
|
||||
fabsf(x2 - 0.33f) < 1e-6f && fabsf(y2 - 0.44f) < 1e-6f);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, &x1,
|
||||
&y1, &x2, &y2,
|
||||
&type) == OAKENGINE_OK);
|
||||
assert(fabsf(x1 - 0.1f) < 1e-6f && fabsf(y1 - 0.2f) < 1e-6f);
|
||||
assert(oakengine_node_keyframes_set_bezier_many(
|
||||
opacity, "opacity_in", -1, miss, tr1, 1, 0.f, 0.f, 0.f,
|
||||
0.f) == OAKENGINE_E_NOT_FOUND);
|
||||
|
||||
// set_bezier_point: NaN old captures the current point.
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
opacity, "opacity_in", -1, 15, 0, 0, 0.5f, 0.6f, NAN,
|
||||
NAN) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, &x1,
|
||||
&y1, &x2, &y2,
|
||||
&type) == OAKENGINE_OK);
|
||||
assert(fabsf(x1 - 0.5f) < 1e-6f && fabsf(y1 - 0.6f) < 1e-6f);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, &x1,
|
||||
&y1, &x2, &y2,
|
||||
&type) == OAKENGINE_OK);
|
||||
assert(fabsf(x1 - 0.1f) < 1e-6f && fabsf(y1 - 0.2f) < 1e-6f);
|
||||
|
||||
// Explicit old restores the recorded point on undo.
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
opacity, "opacity_in", -1, 15, 0, 0, 0.7f, 0.8f, 9.0f,
|
||||
9.0f) == OAKENGINE_OK);
|
||||
assert(oakengine_project_undo(project) == OAKENGINE_OK);
|
||||
assert(oakengine_node_keyframe_get_easing(opacity, "opacity_in", 1, &x1,
|
||||
&y1, &x2, &y2,
|
||||
&type) == OAKENGINE_OK);
|
||||
assert(fabsf(x1 - 9.0f) < 1e-6f);
|
||||
// Put the point back so later state matches the earlier tests.
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
opacity, "opacity_in", -1, 15, 0, 0, 0.1f, 0.2f, NAN,
|
||||
NAN) == OAKENGINE_OK);
|
||||
|
||||
// Errors: bad point index, missing key, NULL node.
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
opacity, "opacity_in", -1, 15, 0, 2, 0.f, 0.f, 0.f,
|
||||
0.f) == OAKENGINE_E_INVALID);
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
opacity, "opacity_in", -1, 99, 0, 0, 0.f, 0.f, 0.f,
|
||||
0.f) == OAKENGINE_E_NOT_FOUND);
|
||||
assert(oakengine_node_keyframe_set_bezier_point(
|
||||
NULL, "opacity_in", -1, 15, 0, 0, 0.f, 0.f, 0.f,
|
||||
0.f) == OAKENGINE_E_INVALID);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
make_tmpdir();
|
||||
@@ -452,6 +596,7 @@ int main(void)
|
||||
test_easing_and_remove(project, opacity);
|
||||
test_rational_and_color(project, timeremap, solid);
|
||||
test_panel_paths(project, opacity, solid);
|
||||
test_keyframe_properties(project, opacity);
|
||||
|
||||
oakengine_project_free(project);
|
||||
assert(oakengine_shutdown() == OAKENGINE_OK);
|
||||
|
||||
@@ -251,6 +251,16 @@ TEST(DialogKeyframeProperties, SingleKeyAcceptWritesAllFields)
|
||||
ensure_app_singletons();
|
||||
auto project = create_project();
|
||||
|
||||
// A 24 fps sequence gives the facade's keyframe timestamps a
|
||||
// frame-aligned timebase (the dialog moves the key to 1/2 s = 12
|
||||
// frames; without a sequence the facade's default timebase would not
|
||||
// land on it exactly).
|
||||
auto *seq = new olive::Sequence();
|
||||
seq->setParent(project.get());
|
||||
seq->set_video_params(olive::VideoParams(
|
||||
1920, 1080, olive::Rational(1, 24), olive::PixelFormat::f32,
|
||||
olive::VideoParams::k_internal_channel_count));
|
||||
|
||||
auto *node = new olive::MathNode();
|
||||
node->setParent(project.get());
|
||||
auto *key = new olive::NodeKeyframe(olive::Rational(0), 1.0,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
#include <QSignalSpy>
|
||||
@@ -10,11 +11,12 @@
|
||||
#include "node/math/math/math.h"
|
||||
#include "node/nodeundo.h"
|
||||
#include "node/project.h"
|
||||
#include "oakengine/node.h"
|
||||
#include "oakengine/project.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "widget/curvewidget/curveview.h"
|
||||
#include "widget/curvewidget/curvewidget.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
#include "widget/keyframeview/keyframeviewundo.h"
|
||||
#include "widget/nodetreeview/nodetreeview.h"
|
||||
|
||||
using namespace olive;
|
||||
@@ -197,6 +199,7 @@ protected:
|
||||
void SetUp() override
|
||||
{
|
||||
ColorManager::set_up_default_config();
|
||||
ensure_app_singletons();
|
||||
|
||||
project_ = std::make_unique<Project>();
|
||||
project_->initialize();
|
||||
@@ -209,54 +212,73 @@ protected:
|
||||
MathNode *node_ = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(KeyframeViewUndoTest, SetTypeCommandSwitchesAndRestoresType)
|
||||
TEST_F(KeyframeViewUndoTest, SetTypeSwitchesAndRestoresType)
|
||||
{
|
||||
NodeKeyframe *key =
|
||||
insert_keyframe(node_, MathNode::k_param_a_in, Rational(0), 0.0);
|
||||
ASSERT_EQ(key->type(), NodeKeyframe::k_linear);
|
||||
|
||||
KeyframeSetTypeCommand command(key, NodeKeyframe::k_bezier);
|
||||
EXPECT_EQ(command.get_relevant_project(), project_.get());
|
||||
|
||||
command.redo_now();
|
||||
// The app-side command class was replaced by the facade batch call;
|
||||
// undo/redo ride the same global undo stack.
|
||||
const int64_t times[1] = { 0 };
|
||||
const int tracks[1] = { 0 };
|
||||
EXPECT_EQ(oakengine_node_keyframes_set_type_many(
|
||||
reinterpret_cast<OakEngineNode *>(node_),
|
||||
MathNode::k_param_a_in.toUtf8().constData(), -1, times,
|
||||
tracks, 1, 1),
|
||||
1);
|
||||
EXPECT_EQ(key->type(), NodeKeyframe::k_bezier);
|
||||
|
||||
command.undo_now();
|
||||
EXPECT_EQ(oakengine_project_undo(
|
||||
reinterpret_cast<OakEngineProject *>(project_.get())),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->type(), NodeKeyframe::k_linear);
|
||||
|
||||
EXPECT_EQ(oakengine_project_redo(
|
||||
reinterpret_cast<OakEngineProject *>(project_.get())),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->type(), NodeKeyframe::k_bezier);
|
||||
}
|
||||
|
||||
TEST_F(KeyframeViewUndoTest, SetBezierControlPointCapturesOldPointFromKeyframe)
|
||||
TEST_F(KeyframeViewUndoTest, SetBezierPointCapturesOldPointFromKeyframe)
|
||||
{
|
||||
NodeKeyframe *key =
|
||||
insert_keyframe(node_, MathNode::k_param_a_in, Rational(0), 0.0);
|
||||
key->set_type(NodeKeyframe::k_bezier);
|
||||
key->set_bezier_control_in(QPointF(0.1, 0.2));
|
||||
|
||||
KeyframeSetBezierControlPoint command(key, NodeKeyframe::k_in_handle,
|
||||
QPointF(0.5, 0.6));
|
||||
EXPECT_EQ(command.get_relevant_project(), project_.get());
|
||||
|
||||
command.redo_now();
|
||||
// NaN old components make the facade capture the current point.
|
||||
EXPECT_EQ(oakengine_node_keyframe_set_bezier_point(
|
||||
reinterpret_cast<OakEngineNode *>(node_),
|
||||
MathNode::k_param_a_in.toUtf8().constData(), -1, 0, 0, 0,
|
||||
0.5, 0.6, NAN, NAN),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->bezier_control_in(), QPointF(0.5, 0.6));
|
||||
|
||||
command.undo_now();
|
||||
EXPECT_EQ(oakengine_project_undo(
|
||||
reinterpret_cast<OakEngineProject *>(project_.get())),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->bezier_control_in(), QPointF(0.1, 0.2));
|
||||
}
|
||||
|
||||
TEST_F(KeyframeViewUndoTest, SetBezierControlPointWithExplicitOldPoint)
|
||||
TEST_F(KeyframeViewUndoTest, SetBezierPointWithExplicitOldPoint)
|
||||
{
|
||||
NodeKeyframe *key =
|
||||
insert_keyframe(node_, MathNode::k_param_a_in, Rational(0), 0.0);
|
||||
key->set_type(NodeKeyframe::k_bezier);
|
||||
|
||||
// The four-argument overload does not read the current control point
|
||||
KeyframeSetBezierControlPoint command(key, NodeKeyframe::k_out_handle,
|
||||
QPointF(0.7, 0.8), QPointF(0.3, 0.4));
|
||||
|
||||
command.redo_now();
|
||||
// Explicit old components are recorded for undo (the live-set drag
|
||||
// pattern; the current point is not read).
|
||||
EXPECT_EQ(oakengine_node_keyframe_set_bezier_point(
|
||||
reinterpret_cast<OakEngineNode *>(node_),
|
||||
MathNode::k_param_a_in.toUtf8().constData(), -1, 0, 0, 1,
|
||||
0.7, 0.8, 0.3, 0.4),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->bezier_control_out(), QPointF(0.7, 0.8));
|
||||
|
||||
command.undo_now();
|
||||
EXPECT_EQ(oakengine_project_undo(
|
||||
reinterpret_cast<OakEngineProject *>(project_.get())),
|
||||
OAKENGINE_OK);
|
||||
EXPECT_EQ(key->bezier_control_out(), QPointF(0.3, 0.4));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user