VSE: Show thumbnails at strip ends by default

Implements design #151070: by default show thumbnails only at strip
ends. Less clutter, and fewer thumbnails to load. There is an option
to show thumbnails across the whole strip (i.e. previous behavior).

Pull Request: https://projects.blender.org/blender/blender/pulls/151148
This commit is contained in:
Bipin Yadav 2026-02-07 08:39:33 +01:00 • committed by Aras Pranckevicius
parent 2ef6be31f1
commit 7d8fb0e00e
7 changed files with 275 additions and 55 deletions

View file

@ -284,12 +284,33 @@ class SEQUENCER_PT_sequencer_overlay_strips(Panel):
col.prop(overlay_settings, "show_fcurves", text="Animation Curves")
col = split.column()
col.prop(overlay_settings, "show_thumbnails", text="Thumbnails")
col.prop(overlay_settings, "show_strip_tag_color", text="Color Tags")
col.prop(overlay_settings, "show_strip_offset", text="Offsets")
col.prop(overlay_settings, "show_strip_retiming", text="Retiming")
class SEQUENCER_PT_sequencer_overlay_thumbnails(Panel):
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
bl_parent_id = "SEQUENCER_PT_overlay"
bl_label = "Thumbnails"
@classmethod
def poll(cls, context):
st = context.space_data
return st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}
def draw(self, context):
st = context.space_data
overlay_settings = st.timeline_overlay
layout = self.layout
layout.active = st.show_overlays
row = layout.row()
row.prop(overlay_settings, "thumbnail_display_style", expand=True)
class SEQUENCER_PT_sequencer_overlay_waveforms(Panel):
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'HEADER'
@ -2076,6 +2097,7 @@ classes = (
SEQUENCER_PT_preview_overlay,
SEQUENCER_PT_sequencer_overlay,
SEQUENCER_PT_sequencer_overlay_strips,
SEQUENCER_PT_sequencer_overlay_thumbnails,
SEQUENCER_PT_sequencer_overlay_waveforms,

View file

@ -185,7 +185,8 @@ static void blo_update_defaults_screen(bScreen *screen,
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG |
SEQ_TIMELINE_SHOW_STRIP_RETIMING |
SEQ_TIMELINE_WAVEFORMS_HALF | SEQ_TIMELINE_SHOW_THUMBNAILS;
SEQ_TIMELINE_WAVEFORMS_HALF |
SEQ_TIMELINE_STRIP_END_THUMBNAILS;
seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
seq->cache_overlay.flag = SEQ_CACHE_SHOW | SEQ_CACHE_SHOW_FINAL_OUT;
seq->draw_flag |= SEQ_DRAW_TRANSFORM_PREVIEW;

View file

@ -81,6 +81,160 @@ static void strip_get_thumb_image_dimensions(const Strip *strip,
*r_image_height = image_height;
}
static bool add_thumbnail_at_frame(float timeline_frame,
const bContext *C,
const View2D *v2d,
const StripDrawContext &strip,
Scene *scene,
float thumb_width,
float crop_left,
float crop_right,
float crop_x_multiplier,
float upper_thumb_bound,
float display_offset,
bool is_muted,
Vector<SeqThumbInfo> &r_thumbs)
{
/* Frame at which the thumb at `timeline_frame` will be drawn. */
const float display_frame = timeline_frame + display_offset;
float thumb_x_end = display_frame + thumb_width;
bool clipped = false;
/* Reached end of view, no more thumbnails needed. */
if (display_frame > v2d->cur.xmax) {
return false;
}
/* Clip if full thumbnail cannot be displayed. */
if (thumb_x_end > upper_thumb_bound) {
thumb_x_end = upper_thumb_bound;
clipped = true;
}
crop_left = max_ff(crop_left, 0.0f);
crop_right = max_ff(crop_right, 0.0f);
if (crop_left > 0.0f || crop_right > 0.0f) {
clipped = true;
}
float cropx_min = crop_left * crop_x_multiplier;
float cropx_max = min_ff(thumb_width - crop_right, (thumb_x_end - display_frame)) *
crop_x_multiplier;
if (cropx_max - cropx_min < 1.0f) {
return false;
}
/* Get the thumbnail image. */
ImBuf *ibuf = seq::thumbnail_cache_get(C, scene, strip.strip, timeline_frame);
if (ibuf == nullptr) {
/* Thumbnail is not in cache but still other frames have to request for thumbnails. */
return true;
}
SeqThumbInfo thumb = {};
thumb.ibuf = ibuf;
thumb.cropx_min = 0;
thumb.cropx_max = ibuf->x - 1;
if (clipped) {
thumb.cropx_min = clamp_f(cropx_min, 0, ibuf->x - 1);
thumb.cropx_max = clamp_f(cropx_max, 0, ibuf->x - 1);
}
thumb.left_handle = strip.left_handle;
thumb.right_handle = strip.right_handle;
thumb.is_muted = is_muted;
thumb.bottom = strip.bottom;
thumb.top = strip.top;
thumb.x1 = display_frame + crop_left;
thumb.x2 = min_ff(thumb_x_end, display_frame + thumb_width - crop_right);
thumb.y1 = strip.bottom;
thumb.y2 = strip.strip_content_top;
r_thumbs.append(thumb);
return true;
};
static bool is_thumbnail_in_view(const float timeline_frame,
const float thumb_width,
const View2D *v2d)
{
if (timeline_frame < v2d->cur.xmax && timeline_frame + thumb_width > v2d->cur.xmin) {
return true;
}
return false;
}
static void get_seq_strip_ends_thumbnails(const View2D *v2d,
const bContext *C,
const StripDrawContext &strip,
Scene *scene,
const float thumb_width,
const float crop_x_multiplier,
const float pixelx,
const float upper_thumb_bound,
bool is_muted,
Vector<SeqThumbInfo> &r_thumbs)
{
const float left_frame = max_ff(strip.content_start, strip.left_handle);
const float right_frame = strip.is_single_image ? left_frame :
min_ff(strip.content_end, strip.right_handle);
const float strip_width = strip.is_single_image ? (strip.right_handle - strip.left_handle) :
(right_frame - left_frame);
const float overlap = max_ff(0.0f, 2.0f * thumb_width - strip_width);
const bool only_right_handle_selected = ((strip.strip->flag & SEQ_RIGHTSEL) &&
!(strip.strip->flag & SEQ_LEFTSEL));
/* Offset the start of last thumbnail. */
const float display_offset = (strip.is_single_image ? strip_width : 0.0f) - thumb_width;
const float gap = 1.5f * pixelx * UI_SCALE_FAC;
float crop_left = 0.0;
float crop_right = 0.0;
if (overlap > 0.0f && only_right_handle_selected) {
/* Crop left thumbnail from right. */
crop_right = overlap + gap;
}
else if (overlap > 0.0f) {
/* Crop right thumbnail from left. */
crop_left = overlap + gap;
}
if (is_thumbnail_in_view(left_frame, thumb_width, v2d)) {
/* Draw left thumbnail. */
add_thumbnail_at_frame(left_frame,
C,
v2d,
strip,
scene,
thumb_width,
0.0f,
crop_right,
crop_x_multiplier,
upper_thumb_bound,
0.0f,
is_muted,
r_thumbs);
}
if (is_thumbnail_in_view(right_frame + display_offset, thumb_width, v2d)) {
/* Draw right thumbnail. */
add_thumbnail_at_frame(right_frame,
C,
v2d,
strip,
scene,
thumb_width,
crop_left,
0.0f,
crop_x_multiplier,
upper_thumb_bound,
display_offset,
is_muted,
r_thumbs);
}
}
static void get_seq_strip_thumbnails(const View2D *v2d,
const bContext *C,
Scene *scene,
@ -88,6 +242,7 @@ static void get_seq_strip_thumbnails(const View2D *v2d,
float pixelx,
float pixely,
bool is_muted,
bool show_only_at_strip_ends,
Vector<SeqThumbInfo> &r_thumbs)
{
if (!seq::strip_can_have_thumbnail(scene, strip.strip)) {
@ -107,10 +262,24 @@ static void get_seq_strip_thumbnails(const View2D *v2d,
const float crop_x_multiplier = 1.0f / pixelx / (thumb_height / image_height / pixely);
float upper_thumb_bound = min_ff(strip.right_handle, strip.content_end);
if (strip.strip->type == STRIP_TYPE_IMAGE) {
if (strip.is_single_image) {
upper_thumb_bound = strip.right_handle;
}
if (show_only_at_strip_ends) {
get_seq_strip_ends_thumbnails(v2d,
C,
strip,
scene,
thumb_width,
crop_x_multiplier,
pixelx,
upper_thumb_bound,
is_muted,
r_thumbs);
return;
}
int first_drawable_frame = max_iii(strip.left_handle, strip.strip->start, v2d->cur.xmin);
/* Calculate how many thumbnails should we skip over to get to the first visible thumbnail. */
float aligned_frame_offset = int((first_drawable_frame - strip.strip->start) / thumb_width) *
@ -132,49 +301,25 @@ static void get_seq_strip_thumbnails(const View2D *v2d,
/* Start going over the strip length. */
while (timeline_frame < upper_thumb_bound) {
float thumb_x_end = timeline_frame + thumb_width;
bool clipped = false;
/* Reached end of view, no more thumbnails needed. */
if (timeline_frame > v2d->cur.xmax) {
const bool should_add_next_thumbnail = add_thumbnail_at_frame(timeline_frame,
C,
v2d,
strip,
scene,
thumb_width,
0.0f,
0.0f,
crop_x_multiplier,
upper_thumb_bound,
0.0f,
is_muted,
r_thumbs);
if (!should_add_next_thumbnail) {
break;
}
/* Clip if full thumbnail cannot be displayed. */
if (thumb_x_end > upper_thumb_bound) {
thumb_x_end = upper_thumb_bound;
clipped = true;
}
float cropx_max = (thumb_x_end - timeline_frame) * crop_x_multiplier;
if (cropx_max < 1.0f) {
break;
}
/* Get the thumbnail image. */
ImBuf *ibuf = seq::thumbnail_cache_get(C, scene, strip.strip, timeline_frame);
if (ibuf == nullptr) {
break;
}
SeqThumbInfo thumb = {};
thumb.ibuf = ibuf;
thumb.cropx_min = 0;
thumb.cropx_max = ibuf->x - 1;
if (clipped) {
thumb.cropx_max = clamp_f(cropx_max - 1 * 0, 0, ibuf->x - 1);
}
thumb.left_handle = strip.left_handle;
thumb.right_handle = strip.right_handle;
thumb.is_muted = is_muted;
thumb.bottom = strip.bottom;
thumb.top = strip.top;
thumb.x1 = timeline_frame;
thumb.x2 = thumb_x_end;
thumb.y1 = strip.bottom;
thumb.y2 = strip.strip_content_top;
r_thumbs.append(thumb);
if (draw_next_frame_ontop) {
timeline_frame = strip.strip->start + aligned_frame_offset + thumb_width;
draw_next_frame_ontop = false;
@ -265,18 +410,31 @@ void draw_strip_thumbnails(const TimelineDrawContext &ctx,
StripsDrawBatch &strips_batch,
const Vector<StripDrawContext> &strips)
{
const bool show_thumbnails = (ctx.sseq->timeline_overlay.flag &
SEQ_TIMELINE_STRIP_END_THUMBNAILS) ||
(ctx.sseq->timeline_overlay.flag &
SEQ_TIMELINE_CONTINUOUS_THUMBNAILS);
/* Nothing to do if we're not showing thumbnails overall. */
if ((ctx.sseq->flag & SEQ_SHOW_OVERLAY) == 0 ||
(ctx.sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_THUMBNAILS) == 0)
{
if ((ctx.sseq->flag & SEQ_SHOW_OVERLAY) == 0 || !show_thumbnails) {
return;
}
/* Gather information for all thumbnails. */
Vector<SeqThumbInfo> thumbs;
/* Thumbnail display mode (Strip ends / Continuous). */
const bool show_only_at_strip_ends = (ctx.sseq->timeline_overlay.flag &
SEQ_TIMELINE_STRIP_END_THUMBNAILS);
for (const StripDrawContext &strip : strips) {
get_seq_strip_thumbnails(
ctx.v2d, ctx.C, ctx.scene, strip, ctx.pixelx, ctx.pixely, strip.is_muted, thumbs);
get_seq_strip_thumbnails(ctx.v2d,
ctx.C,
ctx.scene,
strip,
ctx.pixelx,
ctx.pixely,
strip.is_muted,
show_only_at_strip_ends,
thumbs);
}
if (thumbs.is_empty()) {
return;

View file

@ -1292,6 +1292,27 @@ static void draw_strips_background(const TimelineDrawContext &ctx,
}
data.col_background = color_pack(col);
const bool show_thumbnails = (ctx.sseq->timeline_overlay.flag &
SEQ_TIMELINE_STRIP_END_THUMBNAILS) ||
(ctx.sseq->timeline_overlay.flag &
SEQ_TIMELINE_CONTINUOUS_THUMBNAILS);
/* Darker color band for thumbnail strips. */
if (show_overlay && seq::strip_can_have_thumbnail(scene, strip.strip) && show_thumbnails) {
/* The more negative the offset, darker the color. */
const int color_offset = -20;
uchar col_in[3] = {col[0], col[1], col[2]};
uchar col_out[3];
ui::theme::get_color_shade_3ubv(col_in, color_offset, col_out);
col[0] = col_out[0];
col[1] = col_out[1];
col[2] = col_out[2];
data.flags |= GPU_SEQ_FLAG_COLOR_BAND;
data.col_color_band = color_pack(col);
}
/* Color band state. */
if (show_overlay && (strip.strip->type == STRIP_TYPE_COLOR)) {
data.flags |= GPU_SEQ_FLAG_COLOR_BAND;

View file

@ -83,7 +83,8 @@ static SpaceLink *sequencer_create(const ScrArea * /*area*/, const Scene *scene)
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
SEQ_TIMELINE_SHOW_FCURVES | SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG |
SEQ_TIMELINE_SHOW_STRIP_RETIMING | SEQ_TIMELINE_WAVEFORMS_HALF |
SEQ_TIMELINE_SHOW_THUMBNAILS;
SEQ_TIMELINE_STRIP_END_THUMBNAILS;
sseq->cache_overlay.flag = SEQ_CACHE_SHOW | SEQ_CACHE_SHOW_FINAL_OUT;
sseq->draw_flag |= SEQ_DRAW_TRANSFORM_PREVIEW;

View file

@ -368,7 +368,8 @@ enum eSpaceSeq_SequencerPreviewOverlay_Flag {
/** #SequencerTimelineOverlay.flag */
enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1),
SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2),
/** Show thumbnails only at ends of a strip. */
SEQ_TIMELINE_STRIP_END_THUMBNAILS = (1 << 2),
/** Use #Strip::color_tag */
SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG = (1 << 3),
SEQ_TIMELINE_SHOW_STRIP_RETIMING = (1 << 4),
@ -383,6 +384,8 @@ enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_STRIP_SOURCE = (1 << 15),
SEQ_TIMELINE_SHOW_STRIP_DURATION = (1 << 16),
SEQ_TIMELINE_SHOW_GRID = (1 << 18),
/** Show continuous sequence of thumbnails. */
SEQ_TIMELINE_CONTINUOUS_THUMBNAILS = (1 << 19),
};
/** #SequencerCacheOverlay.flag */

View file

@ -6473,6 +6473,26 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Waveform Style", "How Waveforms are displayed");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
static const EnumPropertyItem thumbnail_style_items[] = {
{0, "NO_THUMBNAILS", 0, "None", "Do not show strip thumbnails"},
{SEQ_TIMELINE_STRIP_END_THUMBNAILS,
"STRIP_ENDS",
0,
"Strip Ends",
"Show thumbnails only at the beginning and end of the strip"},
{SEQ_TIMELINE_CONTINUOUS_THUMBNAILS,
"CONTINUOUS",
0,
"Continuous",
"Display thumbnails as a filmstrip"},
{0, nullptr, 0, nullptr, nullptr}};
prop = RNA_def_property(srna, "thumbnail_display_style", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "timeline_overlay.flag");
RNA_def_property_enum_items(prop, thumbnail_style_items);
RNA_def_property_ui_text(prop, "Thumbnail style", "How thumbnails are displayed");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
prop = RNA_def_property(srna, "show_fcurves", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "timeline_overlay.flag", SEQ_TIMELINE_SHOW_FCURVES);
RNA_def_property_ui_text(prop, "Show F-Curves", "Display strip opacity/volume curve");
@ -6508,12 +6528,6 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
prop = RNA_def_property(srna, "show_thumbnails", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "timeline_overlay.flag", SEQ_TIMELINE_SHOW_THUMBNAILS);
RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, nullptr);
prop = RNA_def_property(srna, "show_strip_tag_color", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "timeline_overlay.flag", SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG);