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
|
||||
Reference in New Issue
Block a user