729 lines
22 KiB
C++
729 lines
22 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edtransform
|
|
*/
|
|
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_vector.h"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "ED_markers.hh"
|
|
#include "ED_time_scrub_ui.hh"
|
|
|
|
#include "SEQ_animation.hh"
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_edit.hh"
|
|
#include "SEQ_effects.hh"
|
|
#include "SEQ_iterator.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_time.hh"
|
|
#include "SEQ_transform.hh"
|
|
#include "SEQ_utils.hh"
|
|
|
|
#include "UI_view2d.hh"
|
|
|
|
#include "transform.hh"
|
|
#include "transform_convert.hh"
|
|
|
|
#define SEQ_EDGE_PAN_INSIDE_PAD 3.5
|
|
#define SEQ_EDGE_PAN_OUTSIDE_PAD 0 /* Disable clamping for panning, use whole screen. */
|
|
#define SEQ_EDGE_PAN_SPEED_RAMP 1
|
|
#define SEQ_EDGE_PAN_MAX_SPEED 4 /* In UI units per second, slower than default. */
|
|
#define SEQ_EDGE_PAN_DELAY 1.0f
|
|
#define SEQ_EDGE_PAN_ZOOM_INFLUENCE 0.5f
|
|
|
|
/** Used for sequencer transform. */
|
|
struct TransDataSeq {
|
|
Sequence *seq;
|
|
/** A copy of #Sequence.flag that may be modified for nested strips. */
|
|
int flag;
|
|
/** Use this so we can have transform data at the strips start,
|
|
* but apply correctly to the start frame. */
|
|
int start_offset;
|
|
/** one of #SELECT, #SEQ_LEFTSEL and #SEQ_RIGHTSEL. */
|
|
short sel_flag;
|
|
};
|
|
|
|
/**
|
|
* Sequencer transform customdata (stored in #TransCustomDataContainer).
|
|
*/
|
|
struct TransSeq {
|
|
TransDataSeq *tdseq;
|
|
int selection_channel_range_min;
|
|
int selection_channel_range_max;
|
|
|
|
/* Initial rect of the view2d, used for computing offset during edge panning */
|
|
rctf initial_v2d_cur;
|
|
View2DEdgePanData edge_pan;
|
|
|
|
/* Strips that aren't selected, but their position entirely depends on transformed strips. */
|
|
blender::VectorSet<Sequence *> time_dependent_strips;
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sequencer Transform Creation
|
|
* \{ */
|
|
|
|
/* This function applies the rules for transforming a strip so duplicate
|
|
* checks don't need to be added in multiple places.
|
|
*
|
|
* count and flag MUST be set.
|
|
*/
|
|
static void SeqTransInfo(TransInfo *t, Sequence *seq, int *r_count, int *r_flag)
|
|
{
|
|
Scene *scene = t->scene;
|
|
Editing *ed = SEQ_editing_get(t->scene);
|
|
ListBase *channels = SEQ_channels_displayed_get(ed);
|
|
|
|
/* for extend we need to do some tricks */
|
|
if (t->mode == TFM_TIME_EXTEND) {
|
|
|
|
/* *** Extend Transform *** */
|
|
int cfra = scene->r.cfra;
|
|
int left = SEQ_time_left_handle_frame_get(scene, seq);
|
|
int right = SEQ_time_right_handle_frame_get(scene, seq);
|
|
|
|
if ((seq->flag & SELECT) == 0 || SEQ_transform_is_locked(channels, seq)) {
|
|
*r_count = 0;
|
|
*r_flag = 0;
|
|
}
|
|
else {
|
|
*r_count = 1; /* unless its set to 0, extend will never set 2 handles at once */
|
|
*r_flag = (seq->flag | SELECT) & ~(SEQ_LEFTSEL | SEQ_RIGHTSEL);
|
|
|
|
if (t->frame_side == 'R') {
|
|
if (right <= cfra) {
|
|
*r_count = *r_flag = 0;
|
|
} /* ignore */
|
|
else if (left > cfra) {
|
|
} /* keep the selection */
|
|
else {
|
|
*r_flag |= SEQ_RIGHTSEL;
|
|
}
|
|
}
|
|
else {
|
|
if (left >= cfra) {
|
|
*r_count = *r_flag = 0;
|
|
} /* ignore */
|
|
else if (right < cfra) {
|
|
} /* keep the selection */
|
|
else {
|
|
*r_flag |= SEQ_LEFTSEL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
t->frame_side = 'B';
|
|
|
|
/* *** Normal Transform *** */
|
|
|
|
/* Count */
|
|
|
|
/* Non nested strips (reset selection and handles). */
|
|
if ((seq->flag & SELECT) == 0 || SEQ_transform_is_locked(channels, seq)) {
|
|
*r_count = 0;
|
|
*r_flag = 0;
|
|
}
|
|
else {
|
|
if ((seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) == (SEQ_LEFTSEL | SEQ_RIGHTSEL)) {
|
|
*r_flag = seq->flag;
|
|
*r_count = 2; /* we need 2 transdata's */
|
|
}
|
|
else {
|
|
*r_flag = seq->flag;
|
|
*r_count = 1; /* selected or with a handle selected */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int SeqTransCount(TransInfo *t, ListBase *seqbase)
|
|
{
|
|
int tot = 0, count, flag;
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
SeqTransInfo(t, seq, &count, &flag); /* ignore the flag */
|
|
tot += count;
|
|
}
|
|
|
|
return tot;
|
|
}
|
|
|
|
static TransData *SeqToTransData(Scene *scene,
|
|
TransData *td,
|
|
TransData2D *td2d,
|
|
TransDataSeq *tdsq,
|
|
Sequence *seq,
|
|
int flag,
|
|
int sel_flag)
|
|
{
|
|
int start_left;
|
|
|
|
switch (sel_flag) {
|
|
case SELECT:
|
|
/* Use seq_tx_get_final_left() and an offset here
|
|
* so transform has the left hand location of the strip.
|
|
* tdsq->start_offset is used when flushing the tx data back */
|
|
start_left = SEQ_time_left_handle_frame_get(scene, seq);
|
|
td2d->loc[0] = start_left;
|
|
tdsq->start_offset = start_left - seq->start; /* use to apply the original location */
|
|
break;
|
|
case SEQ_LEFTSEL:
|
|
start_left = SEQ_time_left_handle_frame_get(scene, seq);
|
|
td2d->loc[0] = start_left;
|
|
break;
|
|
case SEQ_RIGHTSEL:
|
|
td2d->loc[0] = SEQ_time_right_handle_frame_get(scene, seq);
|
|
break;
|
|
}
|
|
|
|
td2d->loc[1] = seq->machine; /* channel - Y location */
|
|
td2d->loc[2] = 0.0f;
|
|
td2d->loc2d = nullptr;
|
|
|
|
tdsq->seq = seq;
|
|
|
|
/* Use instead of seq->flag for nested strips and other
|
|
* cases where the selection may need to be modified */
|
|
tdsq->flag = flag;
|
|
tdsq->sel_flag = sel_flag;
|
|
|
|
td->extra = (void *)tdsq; /* allow us to update the strip from here */
|
|
|
|
td->flag = 0;
|
|
td->loc = td2d->loc;
|
|
copy_v3_v3(td->center, td->loc);
|
|
copy_v3_v3(td->iloc, td->loc);
|
|
|
|
memset(td->axismtx, 0, sizeof(td->axismtx));
|
|
td->axismtx[2][2] = 1.0f;
|
|
|
|
td->ext = nullptr;
|
|
td->val = nullptr;
|
|
|
|
td->flag |= TD_SELECTED;
|
|
td->dist = 0.0;
|
|
|
|
unit_m3(td->mtx);
|
|
unit_m3(td->smtx);
|
|
|
|
/* Time Transform (extend) */
|
|
td->val = td2d->loc;
|
|
td->ival = td2d->loc[0];
|
|
|
|
return td;
|
|
}
|
|
|
|
static int SeqToTransData_build(
|
|
TransInfo *t, ListBase *seqbase, TransData *td, TransData2D *td2d, TransDataSeq *tdsq)
|
|
{
|
|
Scene *scene = t->scene;
|
|
int count, flag;
|
|
int tot = 0;
|
|
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
|
|
SeqTransInfo(t, seq, &count, &flag);
|
|
|
|
/* use 'flag' which is derived from seq->flag but modified for special cases */
|
|
if (flag & SELECT) {
|
|
if (flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) {
|
|
if (flag & SEQ_LEFTSEL) {
|
|
SeqToTransData(scene, td++, td2d++, tdsq++, seq, flag, SEQ_LEFTSEL);
|
|
tot++;
|
|
}
|
|
if (flag & SEQ_RIGHTSEL) {
|
|
SeqToTransData(scene, td++, td2d++, tdsq++, seq, flag, SEQ_RIGHTSEL);
|
|
tot++;
|
|
}
|
|
}
|
|
else {
|
|
SeqToTransData(scene, td++, td2d++, tdsq++, seq, flag, SELECT);
|
|
tot++;
|
|
}
|
|
}
|
|
}
|
|
return tot;
|
|
}
|
|
|
|
static void free_transform_custom_data(TransCustomData *custom_data)
|
|
{
|
|
if ((custom_data->data != nullptr) && custom_data->use_free) {
|
|
TransSeq *ts = static_cast<TransSeq *>(custom_data->data);
|
|
MEM_freeN(ts->tdseq);
|
|
MEM_delete(ts);
|
|
custom_data->data = nullptr;
|
|
}
|
|
}
|
|
|
|
/* Canceled, need to update the strips display. */
|
|
static void seq_transform_cancel(TransInfo *t, blender::Span<Sequence *> transformed_strips)
|
|
{
|
|
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(t->scene));
|
|
|
|
for (Sequence *seq : transformed_strips) {
|
|
/* Handle pre-existing overlapping strips even when operator is canceled.
|
|
* This is necessary for SEQUENCER_OT_duplicate_move macro for example. */
|
|
if (SEQ_transform_test_overlap(t->scene, seqbase, seq)) {
|
|
SEQ_transform_seqbase_shuffle(seqbase, seq, t->scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ListBase *seqbase_active_get(const TransInfo *t)
|
|
{
|
|
Editing *ed = SEQ_editing_get(t->scene);
|
|
return SEQ_active_seqbase_get(ed);
|
|
}
|
|
|
|
static bool seq_transform_check_overlap(blender::Span<Sequence *> transformed_strips)
|
|
{
|
|
for (Sequence *seq : transformed_strips) {
|
|
if (seq->flag & SEQ_OVERLAP) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static blender::VectorSet<Sequence *> seq_transform_collection_from_transdata(
|
|
TransDataContainer *tc)
|
|
{
|
|
blender::VectorSet<Sequence *> strips;
|
|
TransData *td = tc->data;
|
|
for (int a = 0; a < tc->data_len; a++, td++) {
|
|
Sequence *seq = ((TransDataSeq *)td->extra)->seq;
|
|
strips.add(seq);
|
|
}
|
|
return strips;
|
|
}
|
|
|
|
static void freeSeqData(TransInfo *t, TransDataContainer *tc, TransCustomData *custom_data)
|
|
{
|
|
Editing *ed = SEQ_editing_get(t->scene);
|
|
if (ed == nullptr) {
|
|
free_transform_custom_data(custom_data);
|
|
return;
|
|
}
|
|
|
|
blender::VectorSet transformed_strips = seq_transform_collection_from_transdata(tc);
|
|
SEQ_iterator_set_expand(
|
|
t->scene, seqbase_active_get(t), transformed_strips, SEQ_query_strip_effect_chain);
|
|
|
|
for (Sequence *seq : transformed_strips) {
|
|
seq->flag &= ~SEQ_IGNORE_CHANNEL_LOCK;
|
|
}
|
|
|
|
if (t->state == TRANS_CANCEL) {
|
|
seq_transform_cancel(t, transformed_strips);
|
|
free_transform_custom_data(custom_data);
|
|
return;
|
|
}
|
|
|
|
TransSeq *ts = static_cast<TransSeq *>(tc->custom.type.data);
|
|
ListBase *seqbasep = seqbase_active_get(t);
|
|
Scene *scene = t->scene;
|
|
const bool use_sync_markers = (((SpaceSeq *)t->area->spacedata.first)->flag &
|
|
SEQ_MARKER_TRANS) != 0;
|
|
if (seq_transform_check_overlap(transformed_strips)) {
|
|
SEQ_transform_handle_overlap(
|
|
scene, seqbasep, transformed_strips, ts->time_dependent_strips, use_sync_markers);
|
|
}
|
|
|
|
DEG_id_tag_update(&t->scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
free_transform_custom_data(custom_data);
|
|
}
|
|
|
|
static blender::VectorSet<Sequence *> query_selected_strips_no_handles(ListBase *seqbase)
|
|
{
|
|
blender::VectorSet<Sequence *> strips;
|
|
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
|
|
if ((seq->flag & SELECT) != 0 && ((seq->flag & (SEQ_LEFTSEL | SEQ_RIGHTSEL)) == 0)) {
|
|
strips.add(seq);
|
|
}
|
|
}
|
|
return strips;
|
|
}
|
|
|
|
enum SeqInputSide {
|
|
SEQ_INPUT_LEFT = -1,
|
|
SEQ_INPUT_RIGHT = 1,
|
|
};
|
|
|
|
static Sequence *effect_input_get(const Scene *scene, Sequence *effect, SeqInputSide side)
|
|
{
|
|
Sequence *input = effect->seq1;
|
|
if (effect->seq2 && (SEQ_time_left_handle_frame_get(scene, effect->seq2) -
|
|
SEQ_time_left_handle_frame_get(scene, effect->seq1)) *
|
|
side >
|
|
0)
|
|
{
|
|
input = effect->seq2;
|
|
}
|
|
return input;
|
|
}
|
|
|
|
static Sequence *effect_base_input_get(const Scene *scene, Sequence *effect, SeqInputSide side)
|
|
{
|
|
Sequence *input = effect, *seq_iter = effect;
|
|
while (seq_iter != nullptr) {
|
|
input = seq_iter;
|
|
seq_iter = effect_input_get(scene, seq_iter, side);
|
|
}
|
|
return input;
|
|
}
|
|
|
|
/**
|
|
* Strips that aren't stime_dependent_stripselected, but their position entirely depends on
|
|
* transformed strips. This collection is used to offset animation.
|
|
*/
|
|
static void query_time_dependent_strips_strips(
|
|
TransInfo *t, blender::VectorSet<Sequence *> &time_dependent_strips)
|
|
{
|
|
ListBase *seqbase = seqbase_active_get(t);
|
|
|
|
/* Query dependent strips where used strips do not have handles selected.
|
|
* If all inputs of any effect even indirectly(through another effect) points to selected strip,
|
|
* its position will change. */
|
|
|
|
blender::VectorSet<Sequence *> strips_no_handles = query_selected_strips_no_handles(seqbase);
|
|
time_dependent_strips.add_multiple(strips_no_handles);
|
|
|
|
SEQ_iterator_set_expand(t->scene, seqbase, strips_no_handles, SEQ_query_strip_effect_chain);
|
|
bool strip_added = true;
|
|
|
|
while (strip_added) {
|
|
strip_added = false;
|
|
|
|
for (Sequence *seq : strips_no_handles) {
|
|
if (time_dependent_strips.contains(seq)) {
|
|
continue; /* Strip is already in collection, skip it. */
|
|
}
|
|
|
|
/* If both seq1 and seq2 exist, both must be selected. */
|
|
if (seq->seq1 && time_dependent_strips.contains(seq->seq1)) {
|
|
if (seq->seq2 && !time_dependent_strips.contains(seq->seq2)) {
|
|
continue;
|
|
}
|
|
strip_added = true;
|
|
time_dependent_strips.add(seq);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Query dependent strips where used strips do have handles selected.
|
|
* If any 2-input effect changes position because handles were moved, animation should be offset.
|
|
* With single input effect, it is less likely desirable to move animation. */
|
|
|
|
blender::VectorSet selected_strips = SEQ_query_selected_strips(seqbase);
|
|
SEQ_iterator_set_expand(t->scene, seqbase, selected_strips, SEQ_query_strip_effect_chain);
|
|
for (Sequence *seq : selected_strips) {
|
|
/* Check only 2 input effects. */
|
|
if (seq->seq1 == nullptr || seq->seq2 == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
/* Find immediate base inputs(left and right side). */
|
|
Sequence *input_left = effect_base_input_get(t->scene, seq, SEQ_INPUT_LEFT);
|
|
Sequence *input_right = effect_base_input_get(t->scene, seq, SEQ_INPUT_RIGHT);
|
|
|
|
if ((input_left->flag & SEQ_RIGHTSEL) != 0 && (input_right->flag & SEQ_LEFTSEL) != 0) {
|
|
time_dependent_strips.add(seq);
|
|
}
|
|
}
|
|
|
|
/* Remove all non-effects. */
|
|
time_dependent_strips.remove_if(
|
|
[&](Sequence *seq) { return SEQ_transform_sequence_can_be_translated(seq); });
|
|
}
|
|
|
|
static void createTransSeqData(bContext * /*C*/, TransInfo *t)
|
|
{
|
|
Scene *scene = t->scene;
|
|
Editing *ed = SEQ_editing_get(t->scene);
|
|
TransData *td = nullptr;
|
|
TransData2D *td2d = nullptr;
|
|
TransDataSeq *tdsq = nullptr;
|
|
TransSeq *ts = nullptr;
|
|
|
|
int count = 0;
|
|
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
|
|
if (ed == nullptr) {
|
|
tc->data_len = 0;
|
|
return;
|
|
}
|
|
|
|
/* Disable cursor wrapping for edge pan. */
|
|
if (t->mode == TFM_TRANSLATION) {
|
|
t->flag |= T_NO_CURSOR_WRAP;
|
|
}
|
|
|
|
tc->custom.type.free_cb = freeSeqData;
|
|
t->frame_side = transform_convert_frame_side_dir_get(t, float(scene->r.cfra));
|
|
|
|
count = SeqTransCount(t, ed->seqbasep);
|
|
|
|
/* allocate memory for data */
|
|
tc->data_len = count;
|
|
|
|
/* stop if trying to build list if nothing selected */
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
|
|
tc->custom.type.data = ts = MEM_new<TransSeq>(__func__);
|
|
tc->custom.type.use_free = true;
|
|
td = tc->data = static_cast<TransData *>(
|
|
MEM_callocN(tc->data_len * sizeof(TransData), "TransSeq TransData"));
|
|
td2d = tc->data_2d = static_cast<TransData2D *>(
|
|
MEM_callocN(tc->data_len * sizeof(TransData2D), "TransSeq TransData2D"));
|
|
ts->tdseq = tdsq = static_cast<TransDataSeq *>(
|
|
MEM_callocN(tc->data_len * sizeof(TransDataSeq), "TransSeq TransDataSeq"));
|
|
|
|
/* Custom data to enable edge panning during transformation. */
|
|
UI_view2d_edge_pan_init(t->context,
|
|
&ts->edge_pan,
|
|
SEQ_EDGE_PAN_INSIDE_PAD,
|
|
SEQ_EDGE_PAN_OUTSIDE_PAD,
|
|
SEQ_EDGE_PAN_SPEED_RAMP,
|
|
SEQ_EDGE_PAN_MAX_SPEED,
|
|
SEQ_EDGE_PAN_DELAY,
|
|
SEQ_EDGE_PAN_ZOOM_INFLUENCE);
|
|
UI_view2d_edge_pan_set_limits(&ts->edge_pan, -FLT_MAX, FLT_MAX, 1, MAXSEQ + 1);
|
|
ts->initial_v2d_cur = t->region->v2d.cur;
|
|
|
|
/* loop 2: build transdata array */
|
|
SeqToTransData_build(t, ed->seqbasep, td, td2d, tdsq);
|
|
|
|
ts->selection_channel_range_min = MAXSEQ + 1;
|
|
LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) {
|
|
if ((seq->flag & SELECT) != 0) {
|
|
ts->selection_channel_range_min = min_ii(ts->selection_channel_range_min, seq->machine);
|
|
ts->selection_channel_range_max = max_ii(ts->selection_channel_range_max, seq->machine);
|
|
}
|
|
}
|
|
|
|
query_time_dependent_strips_strips(t, ts->time_dependent_strips);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UVs Transform Flush
|
|
* \{ */
|
|
|
|
static void view2d_edge_pan_loc_compensate(TransInfo *t, float loc_in[2], float r_loc[2])
|
|
{
|
|
TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
|
|
|
|
/* Initial and current view2D rects for additional transform due to view panning and zooming */
|
|
const rctf *rect_src = &ts->initial_v2d_cur;
|
|
const rctf *rect_dst = &t->region->v2d.cur;
|
|
|
|
if (t->options & CTX_VIEW2D_EDGE_PAN) {
|
|
if (t->state == TRANS_CANCEL) {
|
|
UI_view2d_edge_pan_cancel(t->context, &ts->edge_pan);
|
|
}
|
|
else {
|
|
/* Edge panning functions expect window coordinates, mval is relative to region */
|
|
const int xy[2] = {
|
|
t->region->winrct.xmin + int(t->mval[0]),
|
|
t->region->winrct.ymin + int(t->mval[1]),
|
|
};
|
|
UI_view2d_edge_pan_apply(t->context, &ts->edge_pan, xy);
|
|
}
|
|
}
|
|
|
|
copy_v2_v2(r_loc, loc_in);
|
|
/* Additional offset due to change in view2D rect. */
|
|
BLI_rctf_transform_pt_v(rect_dst, rect_src, r_loc, r_loc);
|
|
}
|
|
|
|
static void flushTransSeq(TransInfo *t)
|
|
{
|
|
/* Editing null check already done */
|
|
ListBase *seqbasep = seqbase_active_get(t);
|
|
|
|
int a, new_frame, offset;
|
|
|
|
TransData *td = nullptr;
|
|
TransData2D *td2d = nullptr;
|
|
TransDataSeq *tdsq = nullptr;
|
|
Sequence *seq;
|
|
|
|
Scene *scene = t->scene;
|
|
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
|
|
/* This is calculated for offsetting animation of effects that change position with inputs.
|
|
* Maximum(positive or negative) value is used, because individual strips can be clamped. This
|
|
* works fairly well in most scenarios, but there can be some edge cases.
|
|
*
|
|
* Better solution would be to store effect position and calculate real offset. However with many
|
|
* (>5) effects in chain, there is visible lag in strip position update, because during
|
|
* recalculation, hierarchy is not taken into account. */
|
|
int max_offset = 0;
|
|
|
|
/* Flush to 2D vector from internally used 3D vector. */
|
|
for (a = 0, td = tc->data, td2d = tc->data_2d; a < tc->data_len; a++, td++, td2d++) {
|
|
tdsq = (TransDataSeq *)td->extra;
|
|
seq = tdsq->seq;
|
|
float loc[2];
|
|
view2d_edge_pan_loc_compensate(t, td->loc, loc);
|
|
new_frame = round_fl_to_int(loc[0]);
|
|
|
|
switch (tdsq->sel_flag) {
|
|
case SELECT: {
|
|
if (SEQ_transform_sequence_can_be_translated(seq)) {
|
|
offset = new_frame - tdsq->start_offset - seq->start;
|
|
SEQ_transform_translate_sequence(scene, seq, offset);
|
|
if (abs(offset) > abs(max_offset)) {
|
|
max_offset = offset;
|
|
}
|
|
}
|
|
seq->machine = round_fl_to_int(loc[1]);
|
|
CLAMP(seq->machine, 1, MAXSEQ);
|
|
break;
|
|
}
|
|
case SEQ_LEFTSEL: { /* No vertical transform. */
|
|
int old_startdisp = SEQ_time_left_handle_frame_get(scene, seq);
|
|
SEQ_time_left_handle_frame_set(t->scene, seq, new_frame);
|
|
|
|
if (abs(SEQ_time_left_handle_frame_get(scene, seq) - old_startdisp) > abs(max_offset)) {
|
|
max_offset = SEQ_time_left_handle_frame_get(scene, seq) - old_startdisp;
|
|
}
|
|
break;
|
|
}
|
|
case SEQ_RIGHTSEL: { /* No vertical transform. */
|
|
int old_enddisp = SEQ_time_right_handle_frame_get(scene, seq);
|
|
SEQ_time_right_handle_frame_set(t->scene, seq, new_frame);
|
|
|
|
if (abs(SEQ_time_right_handle_frame_get(scene, seq) - old_enddisp) > abs(max_offset)) {
|
|
max_offset = SEQ_time_right_handle_frame_get(scene, seq) - old_enddisp;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
|
|
|
|
/* Update animation for effects. */
|
|
for (Sequence *seq : ts->time_dependent_strips) {
|
|
SEQ_offset_animdata(t->scene, seq, max_offset);
|
|
}
|
|
|
|
/* need to do the overlap check in a new loop otherwise adjacent strips
|
|
* will not be updated and we'll get false positives */
|
|
blender::VectorSet transformed_strips = seq_transform_collection_from_transdata(tc);
|
|
SEQ_iterator_set_expand(
|
|
t->scene, seqbase_active_get(t), transformed_strips, SEQ_query_strip_effect_chain);
|
|
|
|
for (Sequence *seq : transformed_strips) {
|
|
/* test overlap, displays red outline */
|
|
seq->flag &= ~SEQ_OVERLAP;
|
|
if (SEQ_transform_test_overlap(scene, seqbasep, seq)) {
|
|
seq->flag |= SEQ_OVERLAP;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void recalcData_sequencer(TransInfo *t)
|
|
{
|
|
TransData *td;
|
|
int a;
|
|
Sequence *seq_prev = nullptr;
|
|
|
|
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
|
|
|
|
for (a = 0, td = tc->data; a < tc->data_len; a++, td++) {
|
|
TransDataSeq *tdsq = (TransDataSeq *)td->extra;
|
|
Sequence *seq = tdsq->seq;
|
|
|
|
if (seq != seq_prev) {
|
|
SEQ_relations_invalidate_cache_composite(t->scene, seq);
|
|
}
|
|
|
|
seq_prev = seq;
|
|
}
|
|
|
|
DEG_id_tag_update(&t->scene->id, ID_RECALC_SEQUENCER_STRIPS);
|
|
|
|
flushTransSeq(t);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Special After Transform Sequencer
|
|
* \{ */
|
|
|
|
static void special_aftertrans_update__sequencer(bContext * /*C*/, TransInfo *t)
|
|
{
|
|
if (t->state == TRANS_CANCEL) {
|
|
return;
|
|
}
|
|
/* freeSeqData in transform_conversions.c does this
|
|
* keep here so the else at the end won't run... */
|
|
|
|
SpaceSeq *sseq = (SpaceSeq *)t->area->spacedata.first;
|
|
|
|
/* Marker transform, not especially nice but we may want to move markers
|
|
* at the same time as strips in the Video Sequencer. */
|
|
if (sseq->flag & SEQ_MARKER_TRANS) {
|
|
/* can't use TFM_TIME_EXTEND
|
|
* for some reason EXTEND is changed into TRANSLATE, so use frame_side instead */
|
|
|
|
if (t->mode == TFM_SEQ_SLIDE) {
|
|
if (t->frame_side == 'B') {
|
|
ED_markers_post_apply_transform(
|
|
&t->scene->markers, t->scene, TFM_TIME_TRANSLATE, t->values_final[0], t->frame_side);
|
|
}
|
|
}
|
|
else if (ELEM(t->frame_side, 'L', 'R')) {
|
|
ED_markers_post_apply_transform(
|
|
&t->scene->markers, t->scene, TFM_TIME_EXTEND, t->values_final[0], t->frame_side);
|
|
}
|
|
}
|
|
}
|
|
|
|
void transform_convert_sequencer_channel_clamp(TransInfo *t, float r_val[2])
|
|
{
|
|
const TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
|
|
const int channel_offset = round_fl_to_int(r_val[1]);
|
|
const int min_channel_after_transform = ts->selection_channel_range_min + channel_offset;
|
|
const int max_channel_after_transform = ts->selection_channel_range_max + channel_offset;
|
|
|
|
if (max_channel_after_transform > MAXSEQ) {
|
|
r_val[1] -= max_channel_after_transform - MAXSEQ;
|
|
}
|
|
if (min_channel_after_transform < 1) {
|
|
r_val[1] -= min_channel_after_transform - 1;
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
TransConvertTypeInfo TransConvertType_Sequencer = {
|
|
/*flags*/ (T_POINTS | T_2D_EDIT),
|
|
/*create_trans_data*/ createTransSeqData,
|
|
/*recalc_data*/ recalcData_sequencer,
|
|
/*special_aftertrans_update*/ special_aftertrans_update__sequencer,
|
|
};
|