VSE: Add option to limit timeline view height

When height is limited, it is defined by space occupied by strips,
but at least channels 1 to 7 will be always visible. This allows it to
easily overview timeline content by zooming out to maximum extent in Y
axis and panning in X axis.

More channels can be "created" on demand by moving strip to higher
channel. When strip is removed and highest channel becomes empty, view
will stay as is until it is moved down. Then new highest point is
remembered and it is not possible to pan upwards until strip is moved to
higher channel.

Limiting takes into account height of scrubbing and markers area as
well as scrollers. This means that when zoomed out to maximum extent,
no strips are obstructed by fixed UI element.

Fixes T57976

Reviewed By: Severin

Differential Revision: https://developer.blender.org/D14263
This commit is contained in:
Richard Antalik 2022-04-28 16:07:57 +02:00
parent b1b153b88c
commit 17769489d9
Notes: blender-bot 2023-02-14 08:33:26 +01:00
Referenced by issue #97733, Regression: VSE crash when opening/changing scene
Referenced by issue #90964, VSE: Mouse cursor over invisible (i.e. marker-less) marker region inhibits delete hotkey meant for selected strip deletion
Referenced by issue #57976, VSE, marker area is invisibly blocking the strips
11 changed files with 193 additions and 17 deletions

View File

@ -439,6 +439,7 @@ class SEQUENCER_MT_view(Menu):
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("sequencer.view_all")
layout.operator("view2d.zoom_border", text="Zoom")
layout.prop(st, "clamp_view")
if is_preview:
layout.operator_context = 'INVOKE_REGION_PREVIEW'

View File

@ -25,7 +25,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 13
#define BLENDER_FILE_SUBVERSION 14
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@ -786,6 +786,33 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports))
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 302, 14)) {
/* Sequencer channels region. */
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
if (sl->spacetype != SPACE_SEQ) {
continue;
}
SpaceSeq *sseq = (SpaceSeq *)sl;
sseq->flag |= SEQ_CLAMP_VIEW;
if (ELEM(sseq->view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW)) {
continue;
}
ARegion *timeline_region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
if (timeline_region == NULL) {
continue;
}
timeline_region->v2d.cur.ymax = 8.5f;
timeline_region->v2d.align &= ~V2D_ALIGN_NO_NEG_Y;
}
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -32,7 +32,7 @@
#include "RNA_access.h"
#include "RNA_prototypes.h"
static void get_time_scrub_region_rect(const ARegion *region, rcti *rect)
void ED_time_scrub_region_rect_get(const ARegion *region, rcti *rect)
{
rect->xmin = 0;
rect->xmax = region->winx;
@ -154,7 +154,7 @@ void ED_time_scrub_draw_current_frame(const ARegion *region,
wmOrtho2_region_pixelspace(region);
rcti scrub_region_rect;
get_time_scrub_region_rect(region, &scrub_region_rect);
ED_time_scrub_region_rect_get(region, &scrub_region_rect);
draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra);
GPU_matrix_pop_projection();
@ -171,7 +171,7 @@ void ED_time_scrub_draw(const ARegion *region,
wmOrtho2_region_pixelspace(region);
rcti scrub_region_rect;
get_time_scrub_region_rect(region, &scrub_region_rect);
ED_time_scrub_region_rect_get(region, &scrub_region_rect);
draw_background(&scrub_region_rect);

View File

@ -14,6 +14,7 @@ extern "C" {
struct bContext;
struct bDopeSheet;
struct wmEvent;
struct rcti;
void ED_time_scrub_draw_current_frame(const struct ARegion *region,
const struct Scene *scene,
@ -29,6 +30,7 @@ bool ED_time_scrub_event_in_region(const struct ARegion *region, const struct wm
void ED_time_scrub_channel_search_draw(const struct bContext *C,
struct ARegion *region,
struct bDopeSheet *dopesheet);
void ED_time_scrub_region_rect_get(const struct ARegion *region, struct rcti *rect);
#ifdef __cplusplus
}

View File

@ -32,6 +32,7 @@
#include "ED_anim_api.h"
#include "ED_screen.h"
#include "ED_util_imbuf.h"
#include "ED_time_scrub_ui.h"
/* Own include. */
#include "sequencer_intern.h"
@ -86,6 +87,16 @@ static int sequencer_view_all_exec(bContext *C, wmOperator *op)
box.xmax = ms->disp_range[1] + 1;
}
SEQ_timeline_expand_boundbox(SEQ_active_seqbase_get(ed), &box);
View2D *v2d = &region->v2d;
rcti scrub_rect;
ED_time_scrub_region_rect_get(region, &scrub_rect);
const float pixel_view_size_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
const float scrub_bar_height = BLI_rcti_size_y(&scrub_rect) * pixel_view_size_y;
/* Channel n has range of <n, n+1>. */
box.ymax += 1.0f + scrub_bar_height;
UI_view2d_smooth_view(C, region, &box, smooth_viewtx);
return OPERATOR_FINISHED;
}

View File

@ -16,6 +16,7 @@
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_math_base.h"
#include "BKE_global.h"
#include "BKE_lib_remap.h"
@ -24,8 +25,10 @@
#include "GPU_state.h"
#include "ED_markers.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "ED_time_scrub_ui.h"
#include "ED_transform.h"
#include "ED_view3d.h"
#include "ED_view3d_offscreen.h" /* Only for sequencer view3d drawing callback. */
@ -33,6 +36,8 @@
#include "WM_api.h"
#include "WM_message.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
#include "SEQ_utils.h"
@ -164,7 +169,7 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
region->v2d.tot.xmin = 0.0f;
region->v2d.tot.ymin = 0.0f;
region->v2d.tot.xmax = scene->r.efra;
region->v2d.tot.ymax = 8.0f;
region->v2d.tot.ymax = 8.5f;
region->v2d.cur = region->v2d.tot;
@ -534,6 +539,90 @@ static void sequencer_main_region_draw_overlay(const bContext *C, ARegion *regio
draw_timeline_seq_display(C, region);
}
static void sequencer_main_clamp_view(const bContext *C, ARegion *region)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if ((sseq->flag & SEQ_CLAMP_VIEW) == 0) {
return;
}
View2D *v2d = &region->v2d;
Editing *ed = SEQ_editing_get(CTX_data_scene(C));
/* Transformation uses edge panning to move view. Also if smooth view is running, don't apply
* clamping to prevent overriding this functionality. */
if (G.moving || v2d->smooth_timer != NULL) {
return;
}
/* Initialize default view with 7 channels, that are visible even if empty. */
rctf strip_boundbox;
BLI_rctf_init(&strip_boundbox, 0.0f, 0.0f, 1.0f, 7.0f);
SEQ_timeline_expand_boundbox(ed->seqbasep, &strip_boundbox);
/* Clamp Y max. Scrubbing area height must be added, so strips aren't occluded. */
rcti scrub_rect;
ED_time_scrub_region_rect_get(region, &scrub_rect);
const float pixel_view_size_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
const float scrub_bar_height = BLI_rcti_size_y(&scrub_rect) * pixel_view_size_y;
/* Channel n has range of <n, n+1>. */
strip_boundbox.ymax += 1.0f + scrub_bar_height;
/* Clamp Y min. Scroller and marker area height must be added, so strips aren't occluded. */
float scroll_bar_height = v2d->hor.ymax * pixel_view_size_y;
ListBase *markers = ED_context_get_markers(C);
if (markers != NULL && !BLI_listbase_is_empty(markers)) {
float markers_size = UI_MARKER_MARGIN_Y * pixel_view_size_y;
strip_boundbox.ymin -= markers_size;
}
else {
strip_boundbox.ymin -= scroll_bar_height;
}
/* If strip is deleted, don't move view automatically, keep current range until it is changed. */
strip_boundbox.ymax = max_ff(sseq->runtime.timeline_clamp_custom_range, strip_boundbox.ymax);
rctf view_clamped = v2d->cur;
bool do_clamp = false;
const float range_y = BLI_rctf_size_y(&view_clamped);
if (view_clamped.ymax > strip_boundbox.ymax) {
view_clamped.ymax = strip_boundbox.ymax;
view_clamped.ymin = max_ff(strip_boundbox.ymin, strip_boundbox.ymax - range_y);
do_clamp = true;
}
if (view_clamped.ymin < strip_boundbox.ymin) {
view_clamped.ymin = strip_boundbox.ymin;
view_clamped.ymax = min_ff(strip_boundbox.ymax, strip_boundbox.ymin + range_y);
do_clamp = true;
}
}
static void sequencer_main_region_clamp_custom_set(const bContext *C, ARegion *region)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
View2D *v2d = &region->v2d;
if ((v2d->flag & V2D_IS_NAVIGATING) == 0) {
sseq->runtime.timeline_clamp_custom_range = v2d->cur.ymax;
}
}
static void sequencer_main_region_layout(const bContext *C, ARegion *region)
{
sequencer_main_region_clamp_custom_set(C, region);
sequencer_main_clamp_view(C, region);
}
static void sequencer_main_region_view2d_changed(const bContext *C, ARegion *region)
{
sequencer_main_region_clamp_custom_set(C, region);
sequencer_main_clamp_view(C, region);
}
static void sequencer_main_region_listener(const wmRegionListenerParams *params)
{
ARegion *region = params->region;
@ -926,6 +1015,8 @@ void ED_spacetype_sequencer(void)
art->init = sequencer_main_region_init;
art->draw = sequencer_main_region_draw;
art->draw_overlay = sequencer_main_region_draw_overlay;
art->layout = sequencer_main_region_layout;
art->on_view2d_changed = sequencer_main_region_view2d_changed;
art->listener = sequencer_main_region_listener;
art->message_subscribe = sequencer_main_region_message_subscribe;
/* NOTE: inclusion of #ED_KEYMAP_GIZMO is currently for scripts and isn't used by default. */

View File

@ -35,7 +35,7 @@
#include "transform.h"
#include "transform_convert.h"
#define SEQ_EDGE_PAN_INSIDE_PAD 2
#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. */
@ -769,6 +769,11 @@ void createTransSeqData(TransInfo *t)
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)CFRA);
@ -845,14 +850,6 @@ static void view2d_edge_pan_loc_compensate(TransInfo *t, float loc_in[2], float
const rctf *rect_src = &ts->initial_v2d_cur;
const rctf *rect_dst = &t->region->v2d.cur;
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)
{
TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
if (t->options & CTX_VIEW2D_EDGE_PAN) {
if (t->state == TRANS_CANCEL) {
UI_view2d_edge_pan_cancel(t->context, &ts->edge_pan);
@ -867,6 +864,13 @@ static void flushTransSeq(TransInfo *t)
}
}
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);
@ -934,6 +938,8 @@ static void flushTransSeq(TransInfo *t)
}
}
TransSeq *ts = (TransSeq *)TRANS_DATA_CONTAINER_FIRST_SINGLE(t)->custom.type.data;
/* Update animation for effects. */
SEQ_ITERATOR_FOREACH (seq, ts->time_dependent_strips) {
SEQ_offset_animdata(t->scene, seq, max_offset);

View File

@ -618,7 +618,7 @@ typedef struct SpaceSeqRuntime {
/** Stores lists of most recently displayed thumbnails. */
struct GHash *last_displayed_thumbnails;
int rename_channel_index;
char _pad0[4];
float timeline_clamp_custom_range;
} SpaceSeqRuntime;
/** Sequencer. */
@ -691,7 +691,7 @@ typedef enum eSpaceSeq_Flag {
SEQ_DRAWFRAMES = (1 << 0),
SEQ_MARKER_TRANS = (1 << 1),
SEQ_DRAW_COLOR_SEPARATED = (1 << 2),
SPACE_SEQ_FLAG_UNUSED_3 = (1 << 3),
SEQ_CLAMP_VIEW = (1 << 3),
SPACE_SEQ_FLAG_UNUSED_4 = (1 << 4),
SPACE_SEQ_FLAG_UNUSED_5 = (1 << 5),
SEQ_USE_ALPHA = (1 << 6), /* use RGBA display mode for preview */

View File

@ -2346,6 +2346,36 @@ static void rna_SequenceEditor_render_size_update(bContext *C, PointerRNA *ptr)
rna_SequenceEditor_update_cache(CTX_data_main(C), CTX_data_scene(C), ptr);
}
static bool rna_SequenceEditor_clamp_view_get(PointerRNA *ptr)
{
SpaceSeq *sseq = ptr->data;
return (sseq->flag & SEQ_CLAMP_VIEW) != 0;
}
static void rna_SequenceEditor_clamp_view_set(PointerRNA *ptr, bool value)
{
SpaceSeq *sseq = ptr->data;
ScrArea *area;
ARegion *region;
area = rna_area_from_space(ptr); /* can be NULL */
if (area == NULL) {
return;
}
region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
if (region) {
if (value) {
sseq->flag |= SEQ_CLAMP_VIEW;
region->v2d.align &= ~V2D_ALIGN_NO_NEG_Y;
}
else {
sseq->flag &= ~SEQ_CLAMP_VIEW;
region->v2d.align |= V2D_ALIGN_NO_NEG_Y;
}
}
}
static void rna_Sequencer_view_type_update(Main *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *ptr)
@ -5709,6 +5739,14 @@ static void rna_def_space_sequencer(BlenderRNA *brna)
prop, "Use Proxies", "Use optimized files for faster scrubbing when available");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, "rna_SequenceEditor_update_cache");
prop = RNA_def_property(srna, "clamp_view", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_CLAMP_VIEW);
RNA_def_property_boolean_funcs(
prop, "rna_SequenceEditor_clamp_view_get", "rna_SequenceEditor_clamp_view_set");
RNA_def_property_ui_text(
prop, "Limit View to Contents", "Limit timeline height to maximum used channel slot");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
/* grease pencil */
prop = RNA_def_property(srna, "grease_pencil", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "gpd");

View File

@ -445,7 +445,7 @@ void SEQ_timeline_expand_boundbox(const ListBase *seqbase, rctf *rect)
rect->xmax = seq->enddisp + 1;
}
if (rect->ymax < seq->machine) {
rect->ymax = seq->machine + 2;
rect->ymax = seq->machine;
}
}
}