diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index d5936dc7b..7b559b496 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -22,6 +22,8 @@ #include +#include "transition/transition.h" + Block::Block() : previous_(nullptr), next_(nullptr) @@ -159,8 +161,16 @@ rational Block::MediaToSequenceTime(const rational &media_time) const void Block::CopyParameters(const Block *source, Block *dest) { dest->set_block_name(source->block_name()); - dest->set_length(source->length()); dest->set_media_in(source->media_in()); + + if (source->type() == kTransition && dest->type() == kTransition) { + const TransitionBlock* src_t = static_cast(source); + TransitionBlock* dst_t = static_cast(dest); + + dst_t->set_in_and_out_offset(src_t->in_offset(), src_t->out_offset()); + } else { + dest->set_length(source->length()); + } } void Block::Link(Block *a, Block *b) diff --git a/app/node/block/transition/crossdissolve/crossdissolve.cpp b/app/node/block/transition/crossdissolve/crossdissolve.cpp index f933de317..1ea460724 100644 --- a/app/node/block/transition/crossdissolve/crossdissolve.cpp +++ b/app/node/block/transition/crossdissolve/crossdissolve.cpp @@ -7,7 +7,11 @@ CrossDissolveTransition::CrossDissolveTransition() Node *CrossDissolveTransition::copy() const { - return new CrossDissolveTransition(); + CrossDissolveTransition* c = new CrossDissolveTransition(); + + CopyParameters(this, c); + + return c; } QString CrossDissolveTransition::Name() const diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 451ebe1d4..143c0c0a5 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -51,14 +51,19 @@ void TransitionBlock::set_length_and_media_in(const rational &length) abort(); } -void TransitionBlock::set_in_offset(const rational &in_offset) +const rational &TransitionBlock::in_offset() const { - in_offset_ = in_offset; - RecalculateLength(); + return in_offset_; } -void TransitionBlock::set_out_offset(const rational &out_offset) +const rational &TransitionBlock::out_offset() const { + return out_offset_; +} + +void TransitionBlock::set_in_and_out_offset(const rational &in_offset, const rational &out_offset) +{ + in_offset_ = in_offset; out_offset_ = out_offset; RecalculateLength(); } diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index 14c6a3645..227844633 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -12,7 +12,6 @@ public: virtual QString Category() const override; NodeInput* out_block_input() const; - NodeInput* in_block_input() const; virtual void Retranslate() override; @@ -20,8 +19,9 @@ public: virtual void set_length(const rational &length) override; virtual void set_length_and_media_in(const rational &length) override; - void set_in_offset(const rational& in_offset); - void set_out_offset(const rational& out_offset); + const rational& in_offset() const; + const rational& out_offset() const; + void set_in_and_out_offset(const rational& in_offset, const rational& out_offset); private: void RecalculateLength(); diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 6129418f5..43a953736 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -1,7 +1,9 @@ #include "openglworker.h" +#include "common/clamp.h" #include "core.h" #include "functions.h" +#include "node/block/transition/transition.h" #include "node/node.h" #include "openglcolorprocessor.h" #include "render/colormanager.h" @@ -144,7 +146,7 @@ void OpenGLWorker::ParametersChangedEvent() } } -void OpenGLWorker::RunNodeAccelerated(const Node *node, const NodeValueDatabase *input_params, NodeValueTable *output_params) +void OpenGLWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase *input_params, NodeValueTable *output_params) { OpenGLShaderPtr shader = shader_cache_->Get(node->id()); @@ -206,6 +208,7 @@ void OpenGLWorker::RunNodeAccelerated(const Node *node, const NodeValueDatabase break; case NodeInput::kFootage: case NodeInput::kTexture: + case NodeInput::kBuffer: { OpenGLTextureCache::ReferencePtr texture = value.value(); @@ -248,7 +251,6 @@ void OpenGLWorker::RunNodeAccelerated(const Node *node, const NodeValueDatabase case NodeInput::kWholeNumber: case NodeInput::kNumber: case NodeInput::kString: - case NodeInput::kBuffer: case NodeInput::kVector: case NodeInput::kNone: case NodeInput::kAny: @@ -266,6 +268,29 @@ void OpenGLWorker::RunNodeAccelerated(const Node *node, const NodeValueDatabase static_cast(video_params().width()), static_cast(video_params().height())); + if (node->IsBlock()) { + const Block* block_node = static_cast(node); + + if (block_node->type() == Block::kTransition) { + const TransitionBlock* transition_node = static_cast(node); + + // Provide transition information + rational internal_time = range.in() - block_node->in(); + + // Provides total transition progress from 0.0 (start) - 1.0 (end) + shader->setUniformValue("ove_tprog_all", + static_cast(internal_time.toDouble() / block_node->length().toDouble())); + + // Provides progress of out section from 1.0 (start) - 0.0 (end) + shader->setUniformValue("ove_tprog_out", + static_cast(clamp(internal_time.toDouble() / transition_node->out_offset().toDouble(), 0.0, 1.0))); + + // Provides progress of in section from 0.0 (start) - 1.0 (end) + shader->setUniformValue("ove_tprog_in", + static_cast(clamp((internal_time - transition_node->out_offset()).toDouble() / transition_node->in_offset().toDouble(), 0.0, 1.0))); + } + } + // Blit this texture through this shader olive::gl::Blit(shader); diff --git a/app/render/backend/opengl/openglworker.h b/app/render/backend/opengl/openglworker.h index 84b1c2e1b..eb49f7349 100644 --- a/app/render/backend/opengl/openglworker.h +++ b/app/render/backend/opengl/openglworker.h @@ -48,7 +48,7 @@ protected: virtual void FrameToValue(StreamPtr stream, FramePtr frame, NodeValueTable* table) override; - virtual void RunNodeAccelerated(const Node *node, const NodeValueDatabase *input_params, NodeValueTable* output_params) override; + virtual void RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase *input_params, NodeValueTable* output_params) override; virtual void TextureToBuffer(const QVariant& texture, QByteArray& buffer) override; diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 5620fd154..f2a65a7fc 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -48,9 +48,10 @@ bool RenderWorker::OutputIsAccelerated(Node *output) return false; } -void RenderWorker::RunNodeAccelerated(const Node *node, const NodeValueDatabase *input_params, NodeValueTable* output_params) +void RenderWorker::RunNodeAccelerated(const Node *node, const TimeRange &range, const NodeValueDatabase *input_params, NodeValueTable* output_params) { Q_UNUSED(node) + Q_UNUSED(range) Q_UNUSED(input_params) Q_UNUSED(output_params) } @@ -136,7 +137,7 @@ NodeValueTable RenderWorker::ProcessNode(const NodeDependency& dep) NodeValueTable table = node->Value(database); // Check if we have a shader for this output - RunNodeAccelerated(node, &database, &table); + RunNodeAccelerated(node, dep.range(), &database, &table); return table; } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index e1bcbdba8..2668a4ce9 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -37,7 +37,7 @@ protected: virtual bool OutputIsAccelerated(Node *output); - virtual void RunNodeAccelerated(const Node *node, const NodeValueDatabase *input_params, NodeValueTable* output_params); + virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, const NodeValueDatabase *input_params, NodeValueTable* output_params); StreamPtr ResolveStreamFromInput(NodeInput* input); DecoderPtr ResolveDecoderFromInput(StreamPtr stream); diff --git a/app/shaders/crossdissolve.frag b/app/shaders/crossdissolve.frag index 0699bb0b9..0d7dda495 100644 --- a/app/shaders/crossdissolve.frag +++ b/app/shaders/crossdissolve.frag @@ -2,14 +2,27 @@ varying vec2 ove_texcoord; -uniform sampler2D base_in; -uniform sampler2D blend_in; +uniform sampler2D out_block_in; +uniform sampler2D in_block_in; +uniform bool out_block_in_enabled; +uniform bool in_block_in_enabled; + +uniform float ove_tprog_all; void main(void) { - vec4 base_col = texture2D(base_in, ove_texcoord); - vec4 blend_col = texture2D(blend_in, ove_texcoord); - base_col *= 1.0 - blend_col.a; - base_col += blend_col; + vec4 out_block_col = texture2D(out_block_in, ove_texcoord) * (1.0 - ove_tprog_all); + vec4 in_block_col = texture2D(in_block_in, ove_texcoord) * ove_tprog_all; + + vec4 composite = vec4(0.0); + + if (out_block_in_enabled) { + composite += out_block_col; + } + + if (in_block_in_enabled) { + composite *= 1.0 - in_block_col.a; + composite += in_block_col; + } - gl_FragColor = base_col; + gl_FragColor = composite; } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 89ab75267..673cf315c 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -60,7 +60,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : tools_.replace(olive::tool::kSlide, std::make_shared(this)); tools_.replace(olive::tool::kHand, std::make_shared(this)); tools_.replace(olive::tool::kZoom, std::make_shared(this)); - //tools_.replace(olive::tool::kTransition, new (this)); FIXME: Implement + tools_.replace(olive::tool::kTransition, std::make_shared(this)); //tools_.replace(olive::tool::kRecord, new PointerTool(this)); FIXME: Implement tools_.replace(olive::tool::kAdd, std::make_shared(this)); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 2d3506e49..43049679d 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -206,9 +206,9 @@ private: public: RazorTool(TimelineWidget* parent); - virtual void MousePress(TimelineViewMouseEvent *event); - virtual void MouseMove(TimelineViewMouseEvent *event); - virtual void MouseRelease(TimelineViewMouseEvent *event); + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; private: QVector split_tracks_; @@ -269,9 +269,9 @@ private: public: HandTool(TimelineWidget* parent); - virtual void MousePress(TimelineViewMouseEvent *event); - virtual void MouseMove(TimelineViewMouseEvent *event); - virtual void MouseRelease(TimelineViewMouseEvent *event); + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; private: QPoint screen_drag_start_; QPoint scrollbar_start_; @@ -282,19 +282,9 @@ private: public: ZoomTool(TimelineWidget* parent); - virtual void MousePress(TimelineViewMouseEvent *event); - virtual void MouseMove(TimelineViewMouseEvent *event); - virtual void MouseRelease(TimelineViewMouseEvent *event); - }; - - class TransitionTool : public Tool - { - public: - TransitionTool(TimelineWidget* parent); - - virtual void MousePress(TimelineViewMouseEvent *event); - virtual void MouseMove(TimelineViewMouseEvent *event); - virtual void MouseRelease(TimelineViewMouseEvent *event); + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; }; class AddTool : public Tool @@ -302,12 +292,27 @@ private: public: AddTool(TimelineWidget* parent); - virtual void MousePress(TimelineViewMouseEvent *event); - virtual void MouseMove(TimelineViewMouseEvent *event); - virtual void MouseRelease(TimelineViewMouseEvent *event); + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + + protected: + void MouseMoveInternal(const rational& cursor_frame, bool outwards); - private: TimelineViewGhostItem* ghost_; + + rational drag_start_point_; + }; + + class TransitionTool : public AddTool + { + public: + TransitionTool(TimelineWidget* parent); + + virtual void MousePress(TimelineViewMouseEvent *event) override; + virtual void MouseMove(TimelineViewMouseEvent *event) override; + virtual void MouseRelease(TimelineViewMouseEvent *event) override; + protected: }; void SetBlockLinksSelected(Block *block, bool selected); diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 169e1a58b..b9b9ef538 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -17,42 +17,25 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event) return; } + drag_start_point_ = event->GetCoordinates().GetFrame(); + ghost_ = new TimelineViewGhostItem(); - ghost_->SetIn(event->GetCoordinates().GetFrame()); - ghost_->SetOut(event->GetCoordinates().GetFrame()); + ghost_->SetIn(drag_start_point_); + ghost_->SetOut(drag_start_point_); ghost_->SetTrack(track); ghost_->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track)); parent()->AddGhost(ghost_); - snap_points_.append(event->GetCoordinates().GetFrame()); + snap_points_.append(drag_start_point_); } void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event) { - const rational& cursor_frame = event->GetCoordinates().GetFrame(); - - // Calculate movement - rational movement = cursor_frame - ghost_->Out(); - - // Snap movement - SnapPoint(snap_points_, &movement); - - // Validation: Ensure in point never goes below 0 - if (movement < -ghost_->In()) { - movement = -ghost_->In(); + if (!ghost_) { + return; } - // Make adjustment - if (!movement) { - ghost_->SetInAdjustment(0); - ghost_->SetOutAdjustment(0); - } else if (movement > 0) { - ghost_->SetInAdjustment(0); - ghost_->SetOutAdjustment(movement); - } else if (movement < 0) { - ghost_->SetInAdjustment(movement); - ghost_->SetOutAdjustment(0); - } + MouseMoveInternal(event->GetCoordinates().GetFrame(), event->GetModifiers() & Qt::AltModifier); } void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event) @@ -76,3 +59,37 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event) ghost_ = nullptr; } } + +void TimelineWidget::AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards) +{ + // Calculate movement + rational movement = cursor_frame - drag_start_point_; + + // Snap movement + bool snapped = SnapPoint(snap_points_, &movement); + + // If alt is held, our movement goes both ways (outwards) + if (!snapped && outwards) { + // Snap backwards too + movement = -movement; + SnapPoint(snap_points_, &movement); + // We don't need to un-neg here because outwards means all future processing will be done both pos and neg + } + + // Validation: Ensure in point never goes below 0 + if (movement < -ghost_->In() || (outwards && -movement < -ghost_->In())) { + movement = -ghost_->In(); + } + + // Make adjustment + if (!movement) { + ghost_->SetInAdjustment(0); + ghost_->SetOutAdjustment(0); + } else if (movement > 0) { + ghost_->SetInAdjustment(outwards ? -movement : 0); + ghost_->SetOutAdjustment(movement); + } else if (movement < 0) { + ghost_->SetInAdjustment(movement); + ghost_->SetOutAdjustment(outwards ? -movement : 0); + } +} diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index a89d71ba2..f12cac925 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -1,29 +1,104 @@ #include "widget/timelinewidget/timelinewidget.h" +#include "node/block/transition/crossdissolve/crossdissolve.h" +#include "widget/nodeview/nodeviewundo.h" + TimelineWidget::TransitionTool::TransitionTool(TimelineWidget *parent) : - Tool(parent) + AddTool(parent) { } void TimelineWidget::TransitionTool::MousePress(TimelineViewMouseEvent *event) { - TimelineViewBlockItem* item = GetItemAtScenePos(event->GetCoordinates()); + const TrackReference& track = event->GetCoordinates().GetTrack(); + TrackOutput* t = parent()->GetTrackFromReference(track); + rational cursor_frame = event->GetCoordinates().GetFrame(); - bool selectable_item = (item != nullptr - && item->flags() & QGraphicsItem::ItemIsSelectable - && !parent()->GetTrackFromReference(item->Track())->IsLocked()); - - if (!selectable_item) { + if (!t || t->IsLocked()) { return; } + + Block* block_at_time = t->BlockAtTime(event->GetCoordinates().GetFrame()); + if (!block_at_time || block_at_time->type() != Block::kClip) { + return; + } + + // Determine which side of the clip the transition belongs to + rational transition_start_point; + olive::timeline::MovementMode trim_mode; + rational halfway_point = block_at_time->in() + block_at_time->length() / 2; + if (cursor_frame < halfway_point) { + transition_start_point = block_at_time->in(); + trim_mode = olive::timeline::kTrimIn; + } else { + transition_start_point = block_at_time->out(); + trim_mode = olive::timeline::kTrimOut; + } + + // Create ghost + ghost_ = new TimelineViewGhostItem(); + ghost_->SetTrack(track); + ghost_->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track)); + ghost_->SetIn(transition_start_point); + ghost_->SetOut(transition_start_point); + ghost_->SetMode(trim_mode); + ghost_->setData(TimelineViewGhostItem::kAttachedBlock, Node::PtrToValue(block_at_time)); + parent()->AddGhost(ghost_); + + snap_points_.append(transition_start_point); + + // Set the drag start point + drag_start_point_ = cursor_frame; } void TimelineWidget::TransitionTool::MouseMove(TimelineViewMouseEvent *event) { - Q_UNUSED(event) + if (!ghost_) { + return; + } + + MouseMoveInternal(event->GetCoordinates().GetFrame(), false); } void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event) { - Q_UNUSED(event) + MouseMove(event); + + const TrackReference& track = ghost_->Track(); + + if (ghost_) { + if (!ghost_->AdjustedLength().isNull()) { + Block* block_to_transition = Node::ValueToPtr(ghost_->data(TimelineViewGhostItem::kAttachedBlock)); + CrossDissolveTransition* transition = new CrossDissolveTransition(); + NodeInput* transition_input_to_connect; + + if (ghost_->mode() == olive::timeline::kTrimIn) { + transition->set_in_and_out_offset(ghost_->AdjustedLength(), 0); + transition_input_to_connect = transition->in_block_input(); + } else { + transition->set_in_and_out_offset(0, ghost_->AdjustedLength()); + transition_input_to_connect = transition->out_block_input(); + } + + QUndoCommand* command = new QUndoCommand(); + + // Place transition in place + new TrackPlaceBlockCommand(parent()->timeline_node_->track_list(track.type()), + track.index(), + transition, + ghost_->GetAdjustedIn(), + command); + + // Connect block to transition + new NodeEdgeAddCommand(block_to_transition->output(), + transition_input_to_connect, + command); + + olive::undo_stack.push(command); + } + + parent()->ClearGhosts(); + snap_points_.clear(); + ghost_ = nullptr; + } } diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index d9ab38550..a6adfe3b2 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -28,6 +28,7 @@ #include #include "common/qtversionabstraction.h" +#include "node/block/transition/transition.h" TimelineViewBlockItem::TimelineViewBlockItem(QGraphicsItem* parent) : TimelineViewRect(parent), @@ -80,14 +81,14 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI switch (block_->type()) { case Block::kClip: - case Block::kTransition: // FIXME: Temporary, the transition should have its own UI representation at some point - /*QLinearGradient grad; + { + QLinearGradient grad; grad.setStart(0, rect().top()); grad.setFinalStop(0, rect().bottom()); grad.setColorAt(0.0, QColor(160, 160, 240)); grad.setColorAt(1.0, QColor(128, 128, 192)); - painter->fillRect(rect(), grad);*/ - painter->fillRect(rect(), QColor(128, 128, 192)); + painter->fillRect(rect(), grad); + //painter->fillRect(rect(), QColor(128, 128, 192)); if (option->state & QStyle::State_Selected) { painter->fillRect(rect(), QColor(0, 0, 0, 64)); @@ -114,12 +115,39 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI painter->drawLine(QPointF(rect().left(), rect().bottom() - 1), QPointF(rect().right(), rect().bottom() - 1)); painter->drawLine(QPointF(rect().right(), rect().bottom() - 1), QPointF(rect().right(), rect().top())); break; + } case Block::kGap: if (option->state & QStyle::State_Selected) { // FIXME: Make this palette or CSS painter->fillRect(rect(), Qt::white); } break; + case Block::kTransition: + { + painter->setBrush(QColor(128, 96, 192)); + painter->setPen(QColor(48, 32, 64)); + painter->drawRect(rect()); + + TransitionBlock* t = static_cast(block_); + if (t->out_block_input()->IsConnected()) { + // Transition fades something out, we'll draw a line + painter->drawLine(rect().topLeft(), rect().bottomRight()); + } + if (t->in_block_input()->IsConnected()) { + // Transition fades something in, we'll draw a line + painter->drawLine(rect().bottomLeft(), rect().topRight()); + } + if (t->out_block_input()->IsConnected() && t->in_block_input()->IsConnected()) { + // Draw line between out offset and in offset + qreal crossover_line = rect().left(); + crossover_line += TimeToScene(t->out_offset()); + painter->drawLine(qRound(crossover_line), + qRound(rect().top()), + qRound(crossover_line), + qRound(rect().bottom())); + } + break; + } case Block::kTrack: break; }