When dragging a value in the UI, we use a "single frame update" because we want to prioritize the currently visible frame to give visual feedback as soon as possible. Previously, we generated a single frame InvalidateCache() signal from the widgets themselves, but this had the major downside of not necessarily emitting the time that the viewer was actually showing (due to either node time transformations or times differing between the effects panels and the viewer panels). Now, we send a different signal that viewers can handle themselves to update the time that they're currently showing. This means the fast updating will work no matter how many viewers are connected at whatever time each viewer is set to.
454 lines
13 KiB
C++
454 lines
13 KiB
C++
#include "nodeparamviewwidgetbridge.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QFontComboBox>
|
|
#include <QLineEdit>
|
|
#include <QVector2D>
|
|
#include <QVector3D>
|
|
#include <QVector4D>
|
|
|
|
#include "core.h"
|
|
#include "node/node.h"
|
|
#include "nodeparamviewundo.h"
|
|
#include "project/item/sequence/sequence.h"
|
|
#include "undo/undostack.h"
|
|
#include "widget/footagecombobox/footagecombobox.h"
|
|
#include "widget/slider/floatslider.h"
|
|
#include "widget/slider/integerslider.h"
|
|
|
|
NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput *input, QObject *parent) :
|
|
QObject(parent),
|
|
input_(input),
|
|
dragging_(false),
|
|
drag_created_keyframe_(false)
|
|
{
|
|
CreateWidgets();
|
|
|
|
connect(input_, &NodeInput::ValueChanged, this, &NodeParamViewWidgetBridge::InputValueChanged);
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::SetTime(const rational &time)
|
|
{
|
|
time_ = time;
|
|
|
|
if (!input_) {
|
|
return;
|
|
}
|
|
|
|
// We assume the first data type is the "primary" type
|
|
switch (input_->data_type()) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeParam::kNone:
|
|
case NodeParam::kAny:
|
|
case NodeParam::kTexture:
|
|
case NodeParam::kMatrix:
|
|
case NodeParam::kRational:
|
|
case NodeParam::kSamples:
|
|
case NodeParam::kDecimal:
|
|
case NodeParam::kWholeNumber:
|
|
case NodeParam::kNumber:
|
|
case NodeParam::kString:
|
|
case NodeParam::kBuffer:
|
|
case NodeParam::kVector:
|
|
break;
|
|
case NodeParam::kInt:
|
|
static_cast<IntegerSlider*>(widgets_.first())->SetValue(input_->get_value_at_time(time).toLongLong());
|
|
break;
|
|
case NodeParam::kFloat:
|
|
static_cast<FloatSlider*>(widgets_.first())->SetValue(input_->get_value_at_time(time).toDouble());
|
|
break;
|
|
case NodeParam::kVec2:
|
|
{
|
|
QVector2D vec2 = input_->get_value_at_time(time).value<QVector2D>();
|
|
|
|
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec2.x()));
|
|
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec2.y()));
|
|
break;
|
|
}
|
|
case NodeParam::kVec3:
|
|
{
|
|
QVector3D vec3 = input_->get_value_at_time(time).value<QVector3D>();
|
|
|
|
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec3.x()));
|
|
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec3.y()));
|
|
static_cast<FloatSlider*>(widgets_.at(2))->SetValue(static_cast<double>(vec3.z()));
|
|
break;
|
|
}
|
|
case NodeParam::kVec4:
|
|
{
|
|
QVector4D vec4 = input_->get_value_at_time(time).value<QVector4D>();
|
|
|
|
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec4.x()));
|
|
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec4.y()));
|
|
static_cast<FloatSlider*>(widgets_.at(2))->SetValue(static_cast<double>(vec4.z()));
|
|
static_cast<FloatSlider*>(widgets_.at(3))->SetValue(static_cast<double>(vec4.w()));
|
|
break;
|
|
}
|
|
case NodeParam::kFile:
|
|
// FIXME: File selector
|
|
break;
|
|
case NodeParam::kColor:
|
|
// FIXME: Color selector
|
|
break;
|
|
case NodeParam::kText:
|
|
{
|
|
static_cast<QLineEdit*>(widgets_.first())->setText(input_->get_value_at_time(time).toString());
|
|
break;
|
|
}
|
|
case NodeParam::kBoolean:
|
|
static_cast<QCheckBox*>(widgets_.first())->setChecked(input_->get_value_at_time(time).toBool());
|
|
break;
|
|
case NodeParam::kFont:
|
|
{
|
|
// FIXME: Implement this
|
|
break;
|
|
}
|
|
case NodeParam::kFootage:
|
|
static_cast<FootageComboBox*>(widgets_.first())->SetFootage(input_->get_value_at_time(time).value<StreamPtr>());
|
|
break;
|
|
}
|
|
}
|
|
|
|
const QList<QWidget *> &NodeParamViewWidgetBridge::widgets()
|
|
{
|
|
return widgets_;
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::CreateWidgets()
|
|
{
|
|
// We assume the first data type is the "primary" type
|
|
switch (input_->data_type()) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeParam::kNone:
|
|
case NodeParam::kAny:
|
|
case NodeParam::kTexture:
|
|
case NodeParam::kMatrix:
|
|
case NodeParam::kRational:
|
|
case NodeParam::kSamples:
|
|
case NodeParam::kDecimal:
|
|
case NodeParam::kWholeNumber:
|
|
case NodeParam::kNumber:
|
|
case NodeParam::kString:
|
|
case NodeParam::kBuffer:
|
|
case NodeParam::kVector:
|
|
break;
|
|
case NodeParam::kInt:
|
|
{
|
|
IntegerSlider* slider = new IntegerSlider();
|
|
|
|
if (input_->has_minimum()) {
|
|
slider->SetMinimum(input_->minimum().toLongLong());
|
|
}
|
|
|
|
if (input_->has_maximum()) {
|
|
slider->SetMaximum(input_->maximum().toLongLong());
|
|
}
|
|
|
|
connect(slider, &IntegerSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
widgets_.append(slider);
|
|
break;
|
|
}
|
|
case NodeParam::kFloat:
|
|
{
|
|
FloatSlider* slider = new FloatSlider();
|
|
|
|
if (input_->has_minimum()) {
|
|
slider->SetMinimum(input_->minimum().toDouble());
|
|
}
|
|
|
|
if (input_->has_maximum()) {
|
|
slider->SetMaximum(input_->maximum().toDouble());
|
|
}
|
|
|
|
connect(slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
widgets_.append(slider);
|
|
break;
|
|
}
|
|
case NodeParam::kVec2:
|
|
{
|
|
FloatSlider* x_slider = new FloatSlider();
|
|
widgets_.append(x_slider);
|
|
connect(x_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* y_slider = new FloatSlider();
|
|
widgets_.append(y_slider);
|
|
connect(y_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
break;
|
|
}
|
|
case NodeParam::kVec3:
|
|
{
|
|
FloatSlider* x_slider = new FloatSlider();
|
|
widgets_.append(x_slider);
|
|
connect(x_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* y_slider = new FloatSlider();
|
|
widgets_.append(y_slider);
|
|
connect(y_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* z_slider = new FloatSlider();
|
|
widgets_.append(z_slider);
|
|
connect(z_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
break;
|
|
}
|
|
case NodeParam::kVec4:
|
|
{
|
|
FloatSlider* x_slider = new FloatSlider();
|
|
widgets_.append(x_slider);
|
|
connect(x_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* y_slider = new FloatSlider();
|
|
widgets_.append(y_slider);
|
|
connect(y_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* z_slider = new FloatSlider();
|
|
widgets_.append(z_slider);
|
|
connect(z_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
FloatSlider* w_slider = new FloatSlider();
|
|
widgets_.append(w_slider);
|
|
connect(w_slider, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
break;
|
|
}
|
|
case NodeParam::kFile:
|
|
// FIXME: File selector
|
|
break;
|
|
case NodeParam::kColor:
|
|
// FIXME: Color selector
|
|
break;
|
|
case NodeParam::kText:
|
|
{
|
|
QLineEdit* line_edit = new QLineEdit();
|
|
widgets_.append(line_edit);
|
|
connect(line_edit, &QLineEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
break;
|
|
}
|
|
case NodeParam::kBoolean:
|
|
{
|
|
QCheckBox* check_box = new QCheckBox();
|
|
widgets_.append(check_box);
|
|
connect(check_box, &QCheckBox::toggled, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
break;
|
|
}
|
|
case NodeParam::kFont:
|
|
{
|
|
QFontComboBox* font_combobox = new QFontComboBox();
|
|
widgets_.append(font_combobox);
|
|
break;
|
|
}
|
|
case NodeParam::kFootage:
|
|
{
|
|
FootageComboBox* footage_combobox = new FootageComboBox();
|
|
footage_combobox->SetRoot(static_cast<Sequence*>(input_->parentNode()->parent())->project()->root());
|
|
|
|
connect(footage_combobox, &FootageComboBox::FootageChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
|
|
|
widgets_.append(footage_combobox);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track)
|
|
{
|
|
QUndoCommand* command = new QUndoCommand();
|
|
|
|
if (input_->is_keyframing()) {
|
|
NodeKeyframePtr existing_key = input_->get_keyframe_at_time_on_track(time_, track);
|
|
|
|
if (existing_key) {
|
|
new NodeParamSetKeyframeValueCommand(existing_key, value, command);
|
|
} else {
|
|
// No existing key, create a new one
|
|
NodeKeyframePtr new_key = NodeKeyframe::Create(time_,
|
|
value,
|
|
input_->get_best_keyframe_type_for_time(time_, track),
|
|
track);
|
|
|
|
new NodeParamInsertKeyframeCommand(input_, new_key, command);
|
|
}
|
|
} else {
|
|
new NodeParamSetStandardValueCommand(input_, track, value, command);
|
|
}
|
|
|
|
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::ProcessSlider(SliderBase *slider, const QVariant &value)
|
|
{
|
|
int slider_track = widgets_.indexOf(slider);
|
|
|
|
if (slider->IsDragging()) {
|
|
|
|
// While we're dragging, we block the input's normal signalling and create our own
|
|
input_->blockSignals(true);
|
|
|
|
if (!dragging_) {
|
|
// Set up new drag
|
|
dragging_ = true;
|
|
|
|
// Cache current value
|
|
drag_old_value_ = input_->get_value_at_time_for_track(time_, slider_track);
|
|
|
|
// Determine whether we are creating a keyframe or not
|
|
if (input_->is_keyframing()) {
|
|
dragging_keyframe_ = input_->get_keyframe_at_time_on_track(time_, slider_track);
|
|
drag_created_keyframe_ = !dragging_keyframe_;
|
|
|
|
if (drag_created_keyframe_) {
|
|
dragging_keyframe_ = NodeKeyframe::Create(time_,
|
|
value,
|
|
input_->get_best_keyframe_type_for_time(time_, slider_track),
|
|
slider_track);
|
|
|
|
input_->insert_keyframe(dragging_keyframe_);
|
|
|
|
// We re-enable signals temporarily to emit the keyframe added signal
|
|
input_->blockSignals(false);
|
|
emit input_->KeyframeAdded(dragging_keyframe_);
|
|
input_->blockSignals(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (input_->is_keyframing()) {
|
|
dragging_keyframe_->set_value(value);
|
|
} else {
|
|
input_->set_standard_value(value, slider_track);
|
|
}
|
|
|
|
input_->blockSignals(false);
|
|
|
|
input_->parentNode()->InvalidateVisible(input_);
|
|
|
|
} else {
|
|
if (dragging_) {
|
|
// We were dragging and just stopped
|
|
dragging_ = false;
|
|
|
|
QUndoCommand* command = new QUndoCommand();
|
|
|
|
if (input_->is_keyframing()) {
|
|
if (drag_created_keyframe_) {
|
|
// We created a keyframe in this process
|
|
new NodeParamInsertKeyframeCommand(input_, dragging_keyframe_, true, command);
|
|
}
|
|
|
|
// We just set a keyframe's value
|
|
// We do this even when inserting a keyframe because we don't actually perform an insert in this undo command
|
|
// so this will ensure the ValueChanged() signal is sent correctly
|
|
new NodeParamSetKeyframeValueCommand(dragging_keyframe_, value, drag_old_value_, command);
|
|
} else {
|
|
// We just set the standard value
|
|
new NodeParamSetStandardValueCommand(input_, slider_track, value, drag_old_value_, command);
|
|
}
|
|
|
|
Core::instance()->undo_stack()->push(command);
|
|
|
|
} else {
|
|
// No drag was involved, we can just push the value
|
|
SetInputValue(value, slider_track);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::WidgetCallback()
|
|
{
|
|
switch (input_->data_type()) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeParam::kNone:
|
|
case NodeParam::kAny:
|
|
case NodeParam::kTexture:
|
|
case NodeParam::kMatrix:
|
|
case NodeParam::kSamples:
|
|
case NodeParam::kRational:
|
|
case NodeParam::kDecimal:
|
|
case NodeParam::kWholeNumber:
|
|
case NodeParam::kNumber:
|
|
case NodeParam::kString:
|
|
case NodeParam::kVector:
|
|
case NodeParam::kBuffer:
|
|
break;
|
|
case NodeParam::kInt:
|
|
{
|
|
// Widget is a IntegerSlider
|
|
IntegerSlider* slider = static_cast<IntegerSlider*>(sender());
|
|
|
|
ProcessSlider(slider, slider->GetValue());
|
|
break;
|
|
}
|
|
case NodeParam::kFloat:
|
|
{
|
|
// Widget is a FloatSlider
|
|
FloatSlider* slider = static_cast<FloatSlider*>(sender());
|
|
|
|
ProcessSlider(slider, slider->GetValue());
|
|
break;
|
|
}
|
|
case NodeParam::kVec2:
|
|
{
|
|
// Widget is a FloatSlider
|
|
FloatSlider* slider = static_cast<FloatSlider*>(sender());
|
|
|
|
ProcessSlider(slider, slider->GetValue());
|
|
break;
|
|
}
|
|
case NodeParam::kVec3:
|
|
{
|
|
// Widget is a FloatSlider
|
|
FloatSlider* slider = static_cast<FloatSlider*>(sender());
|
|
|
|
ProcessSlider(slider, slider->GetValue());
|
|
break;
|
|
}
|
|
case NodeParam::kVec4:
|
|
{
|
|
// Widget is a FloatSlider
|
|
FloatSlider* slider = static_cast<FloatSlider*>(sender());
|
|
|
|
ProcessSlider(slider, slider->GetValue());
|
|
break;
|
|
}
|
|
case NodeParam::kFile:
|
|
// FIXME: File selector
|
|
break;
|
|
case NodeParam::kColor:
|
|
// FIXME: Color selector
|
|
break;
|
|
case NodeParam::kText:
|
|
{
|
|
// Sender is a QLineEdit
|
|
SetInputValue(static_cast<QLineEdit*>(sender())->text(), 0);
|
|
break;
|
|
}
|
|
case NodeParam::kBoolean:
|
|
{
|
|
// Widget is a QCheckBox
|
|
SetInputValue(static_cast<QCheckBox*>(sender())->isChecked(), 0);
|
|
break;
|
|
}
|
|
case NodeParam::kFont:
|
|
{
|
|
// Widget is a QFontComboBox
|
|
SetInputValue(static_cast<QFontComboBox*>(sender())->currentFont(), 0);
|
|
break;
|
|
}
|
|
case NodeParam::kFootage:
|
|
{
|
|
// Widget is a FootageComboBox
|
|
SetInputValue(QVariant::fromValue(static_cast<FootageComboBox*>(sender())->SelectedFootage()), 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::InputValueChanged(const rational &start, const rational &end)
|
|
{
|
|
if (!dragging_ && start <= time_ && end >= time_) {
|
|
// We'll need to update the widgets because the values have changed on our current time
|
|
SetTime(time_);
|
|
}
|
|
}
|