diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index b6673b1e2..2e72721cb 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -52,8 +52,8 @@ rational TransitionBlock::in_offset() const return length(); } - // Assume both are connected, in which case the in offset will be <= length - return length() / 2 + media_in(); + // Assume both are connected + return length() + media_in(); } rational TransitionBlock::out_offset() const @@ -68,8 +68,8 @@ rational TransitionBlock::out_offset() const return length(); } - // Assume both are connected, in which case the in offset will be <= length - return length() / 2 - media_in(); + // Assume both are connected + return -media_in(); } Block *TransitionBlock::connected_out_block() const diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp index 413b1d6fc..719874732 100644 --- a/app/node/inputarray.cpp +++ b/app/node/inputarray.cpp @@ -68,6 +68,11 @@ void NodeInputArray::SetSize(int size, bool lock) emit SizeChanged(size); } +bool NodeInputArray::ContainsSubParameter(NodeInput *input) const +{ + return sub_params_.contains(input); +} + int NodeInputArray::IndexOfSubParameter(NodeInput *input) const { return sub_params_.indexOf(input); diff --git a/app/node/inputarray.h b/app/node/inputarray.h index 02f0eb125..ebc33df86 100644 --- a/app/node/inputarray.h +++ b/app/node/inputarray.h @@ -20,6 +20,7 @@ public: void RemoveAt(int index); void SetSize(int size, bool lock = true); + bool ContainsSubParameter(NodeInput* input) const; int IndexOfSubParameter(NodeInput* input) const; NodeInput* First() const; diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 96aac6a17..512f894ab 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -91,10 +91,9 @@ TrackOutput* TrackList::AddTrack() track_input_->Append(); - NodeInput* assoc_input = track_input_->At(track_input_->GetSize() - 1); - // Connect this track directly to this output - NodeParam::ConnectEdge(track->output(), assoc_input); + NodeParam::ConnectEdge(track->output(), + track_input_->At(track_input_->GetSize() - 1)); // FIXME: Test code only if (track_input_->GetSize() > 1) { @@ -111,7 +110,7 @@ TrackOutput* TrackList::AddTrack() if (last_track && last_track->output()->IsConnected()) { foreach (NodeEdgePtr edge, last_track->output()->edges()) { - if (edge->input()->parentNode() != track_input_->parentNode()) { + if (!track_input_->ContainsSubParameter(edge->input())) { Node* blend = NodeFactory::CreateFromID("org.olivevideoeditor.Olive.alphaoverblend"); GetParentGraph()->AddNode(blend); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 1164b4ebd..7bafafc00 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -7,6 +7,7 @@ #include "core.h" #include "common/timecodefunctions.h" #include "dialog/speedduration/speedduration.h" +#include "node/block/transition/transition.h" #include "tool/tool.h" #include "trackview/trackview.h" #include "widget/menu/menu.h" @@ -394,23 +395,46 @@ void TimelineWidget::SplitAtPlayhead() } } -void TimelineWidget::DeleteSelectedInternal(const QList blocks, bool remove_from_graph, QUndoCommand *command) +void TimelineWidget::DeleteSelectedInternal(const QList blocks, bool transition_aware, bool remove_from_graph, QUndoCommand *command) { foreach (Block* b, blocks) { TrackOutput* original_track = TrackOutput::TrackFromBlock(b); - // Make new gap and replace old Block with it for now - GapBlock* gap = new GapBlock(); - gap->set_length_and_media_out(b->length()); + if (transition_aware && b->type() == Block::kTransition) { + // Deleting transitions restores their in/out offsets to their attached blocks + TransitionBlock* transition = static_cast(b); - new NodeAddCommand(static_cast(b->parent()), - gap, - command); + // Ripple remove transition + new TrackRippleRemoveBlockCommand(original_track, + transition, + command); - new TrackReplaceBlockCommand(original_track, - b, - gap, - command); + // Resize attached blocks to make up length + if (transition->connected_in_block()) { + new BlockResizeWithMediaInCommand(transition->connected_in_block(), + transition->connected_in_block()->length() + transition->in_offset(), + command); + } + + if (transition->connected_out_block()) { + new BlockResizeCommand(transition->connected_out_block(), + transition->connected_out_block()->length() + transition->out_offset(), + command); + } + } else { + // Make new gap and replace old Block with it for now + GapBlock* gap = new GapBlock(); + gap->set_length_and_media_out(b->length()); + + new NodeAddCommand(static_cast(b->parent()), + gap, + command); + + new TrackReplaceBlockCommand(original_track, + b, + gap, + command); + } if (remove_from_graph) { new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command); @@ -442,7 +466,7 @@ void TimelineWidget::DeleteSelected() QUndoCommand* command = new QUndoCommand(); // Replace blocks with gaps (effectively deleting them) - DeleteSelectedInternal(blocks_to_delete, true, command); + DeleteSelectedInternal(blocks_to_delete, true, true, command); // Clean each track foreach (const TrackReference& track, tracks_affected) { diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 8003e60e0..d20b69783 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -331,7 +331,7 @@ private: bool dual_transition_; }; - void DeleteSelectedInternal(const QList blocks, bool remove_from_graph, QUndoCommand* command); + void DeleteSelectedInternal(const QList blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command); void SetBlockLinksSelected(Block *block, bool selected); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index c4a31d9c6..a4ccac25f 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -31,6 +31,7 @@ #include "config/config.h" #include "core.h" #include "node/block/gap/gap.h" +#include "node/block/transition/transition.h" #include "widget/nodeview/nodeviewundo.h" TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) : @@ -216,7 +217,7 @@ void TimelineWidget::PointerTool::MouseReleaseInternal(TimelineViewMouseEvent *e } // If there are any blocks to remove, remove them - parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, command); + parent()->DeleteSelectedInternal(blocks_to_temp_remove, false, false, command); // Now we place the clips back in the timeline where the user moved them. It's legal for them to overwrite parts or // all of the gaps we inserted earlier @@ -459,8 +460,19 @@ void TimelineWidget::PointerTool::InitiateGhosts(TimelineViewBlockItem* clicked_ block_mode = FlipTrimMode(trim_mode); } - if (block != nullptr) { + if (block) { AddGhostFromBlock(block, clip_item->Track(), block_mode); + + if (block->type() == Block::kTransition) { + TransitionBlock* transition = static_cast(block); + + // Create a rolling effect with the attached block + if (transition->connected_in_block() && block_mode == Timeline::kTrimOut) { + AddGhostFromBlock(transition->connected_in_block(), clip_item->Track(), Timeline::kTrimIn); + } else if (transition->connected_out_block() && block_mode == Timeline::kTrimIn) { + AddGhostFromBlock(transition->connected_out_block(), clip_item->Track(), Timeline::kTrimOut); + } + } } } } @@ -531,6 +543,11 @@ bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip, return true; } +rational GetEarliestPointForClip(Block* block) +{ + return qMax(rational(0), block->in() - block->media_in()); +} + rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, const QVector ghosts, bool prevent_overwriting) @@ -542,8 +559,34 @@ rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, Block* block = Node::ValueToPtr(ghost->data(TimelineViewGhostItem::kAttachedBlock)); - // Determine the earliest in point this block could have - rational earliest_in = qMax(rational(0), block->in() - block->media_in()); + rational earliest_in = RATIONAL_MIN; + rational latest_in = ghost->Out(); + + if (block->type() == Block::kTransition) { + // For transitions, validate with the attached block + TransitionBlock* transition = static_cast(block); + + if (transition->connected_in_block() && transition->connected_out_block()) { + // Here, we try to get the latest earliest point for both the in and out blocks, we do in here and out will + // be calculated later + earliest_in = GetEarliestPointForClip(transition->connected_in_block()); + + // We set the block to the out block since that will be before the in block and will be the one we use to + // prevent overwriting since we're trimming the in side of this transition + block = transition->connected_out_block(); + + latest_in = transition->in() + transition->out_offset(); + } else { + // Use whatever block is attached + block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block(); + } + } + + earliest_in = qMax(earliest_in, GetEarliestPointForClip(block)); + + if (!ghost->CanHaveZeroLength()) { + latest_in -= parent()->timebase(); + } if (prevent_overwriting) { // Look for a Block in the way @@ -557,13 +600,6 @@ rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement, } } - // Determine the latest point this block could have - rational latest_in = ghost->Out(); - - if (!ghost->CanHaveZeroLength()) { - latest_in -= parent()->timebase(); - } - // Clamp adjusted value between the earliest and latest values rational adjusted = ghost->In() + movement; rational clamped = clamp(adjusted, earliest_in, latest_in); @@ -596,6 +632,24 @@ rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement, rational latest_out = RATIONAL_MAX; + if (block->type() == Block::kTransition) { + // For transitions, validate with the attached block + TransitionBlock* transition = static_cast(block); + + if (transition->connected_in_block() && transition->connected_out_block()) { + // We set the block to the out block since that will be before the in block and will be the one we use to + // prevent overwriting since we're trimming the in side of this transition + + // FIXME: At some point we may add some better logic to `latest_out` akin to the logic in ValidateInTrimming + // which is why this hasn't yet been collapsed into the ternary below. + block = transition->connected_in_block(); + + earliest_out = transition->out() - transition->in_offset(); + } else { + block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block(); + } + } + if (prevent_overwriting) { // Determine if there's a block in the way Block* next = block->next(); diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 346f49424..73bd7a64d 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -97,6 +97,7 @@ void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event) new NodeAddCommand(static_cast(parent()->timeline_node_->parent()), transition, command); + new TrackPlaceBlockCommand(parent()->timeline_node_->track_list(track.type()), track.index(), transition, @@ -104,8 +105,8 @@ void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command); if (dual_transition_) { - //transition->set_in_and_out_offset(ghost_->AdjustedLength()/2, ghost_->AdjustedLength()/2); transition->set_length_and_media_out(ghost_->AdjustedLength()); + transition->set_media_in(-ghost_->AdjustedLength()/2); // Block mouse is hovering over Block* active_block = Node::ValueToPtr(ghost_->data(TimelineViewGhostItem::kAttachedBlock)); diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 61b894a83..eb5c0dd0d 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -546,6 +546,7 @@ void TrackCleanGapsCommand::redo() rational new_gap_length = on_gap->length(); foreach (GapBlock* gap, consecutive_gaps) { track->RippleRemoveBlock(gap); + static_cast(gap->parent())->TakeNode(gap, &memory_manager_); new_gap_length += gap->length(); } @@ -566,6 +567,7 @@ void TrackCleanGapsCommand::redo() foreach (GapBlock* gap, removed_end_gaps_) { track->RippleRemoveBlock(gap); + static_cast(gap->parent())->TakeNode(gap, &memory_manager_); } } } @@ -576,6 +578,7 @@ void TrackCleanGapsCommand::undo() // Restored removed end gaps foreach (GapBlock* gap, removed_end_gaps_) { + static_cast(gap->parent())->AddNode(gap); track->AppendBlock(gap); } removed_end_gaps_.clear(); @@ -590,6 +593,7 @@ void TrackCleanGapsCommand::undo() GapBlock* last_gap_added = merge_info.merged; foreach (GapBlock* gap, merge_info.removed) { + static_cast(gap->parent())->AddNode(gap); track->InsertBlockAfter(gap, last_gap_added); last_gap_added = gap; } diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 88e5178b7..c90837e76 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -146,17 +146,8 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI TransitionBlock* t = static_cast(block_); - if (t->connected_out_block()) { - // Transition fades something out, we'll draw a line - painter->drawLine(rect().topLeft(), rect().bottomRight()); - } - - if (t->connected_in_block()) { - // Transition fades something in, we'll draw a line - painter->drawLine(rect().bottomLeft(), rect().topRight()); - } - if (t->connected_out_block() && t->connected_in_block()) { + // Draw line between out offset and in offset qreal crossover_line = rect().left(); crossover_line += TimeToScene(t->out_offset()); @@ -164,6 +155,24 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI qRound(rect().top()), qRound(crossover_line), qRound(rect().bottom())); + + // Draw lines to mid point + QPointF mid_point(crossover_line, rect().center().y()); + painter->drawLine(rect().topLeft(), mid_point); + painter->drawLine(rect().bottomLeft(), mid_point); + painter->drawLine(rect().topRight(), mid_point); + painter->drawLine(rect().bottomRight(), mid_point); + + } else if (t->connected_out_block()) { + + // Transition fades something out, we'll draw a line + painter->drawLine(rect().topLeft(), rect().bottomRight()); + + } else if (t->connected_in_block()) { + + // Transition fades something in, we'll draw a line + painter->drawLine(rect().bottomLeft(), rect().topRight()); + } break; }