From f4d6edd4764a780cf673a2007b45a7de871fc6f1 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Mon, 12 Jun 2023 02:30:41 +0200 Subject: [PATCH] VSE: Add "Set Speed" gizmo to retiming tool This gizmo is now responsible for drawing labels with speed of retimed segments. By clicking on the value it is possible to type in new speed, which is more convenient, than dragging handles by hand. Note: Due to granularity of frames, the precise value may not be respected. Pull Request: https://projects.blender.org/blender/blender/pulls/108422 --- .../space_sequencer/sequencer_gizmo_retime.cc | 5 + .../sequencer_gizmo_retime_type.cc | 237 +++++++++++++----- .../space_sequencer/sequencer_intern.h | 2 + .../editors/space_sequencer/sequencer_ops.c | 1 + .../space_sequencer/sequencer_retiming.cc | 92 +++++++ .../editors/space_sequencer/space_sequencer.c | 1 + source/blender/sequencer/SEQ_retiming.h | 4 + .../sequencer/intern/strip_retiming.cc | 23 ++ 8 files changed, 308 insertions(+), 57 deletions(-) diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc index e8e2ed8bd09..93986675381 100644 --- a/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime.cc @@ -37,6 +37,7 @@ typedef struct GizmoGroup_retime { wmGizmo *add_handle_gizmo; wmGizmo *move_handle_gizmo; wmGizmo *remove_handle_gizmo; + wmGizmo *speed_set_gizmo; } GizmoGroup_retime; static bool gizmogroup_retime_poll(const bContext *C, wmGizmoGroupType *gzgt) @@ -81,6 +82,8 @@ static void gizmogroup_retime_setup(const bContext * /* C */, wmGizmoGroup *gzgr ggd->remove_handle_gizmo = WM_gizmo_new_ptr(gzt_remove_handle, gzgroup, nullptr); const wmGizmoType *gzt_move_handle = WM_gizmotype_find("GIZMO_GT_retime_handle_move", true); ggd->move_handle_gizmo = WM_gizmo_new_ptr(gzt_move_handle, gzgroup, nullptr); + const wmGizmoType *gzt_speed_set = WM_gizmotype_find("GIZMO_GT_retime_speed_set", true); + ggd->speed_set_gizmo = WM_gizmo_new_ptr(gzt_speed_set, gzgroup, nullptr); gzgroup->customdata = ggd; /* Assign operators. */ @@ -90,6 +93,8 @@ static void gizmogroup_retime_setup(const bContext * /* C */, wmGizmoGroup *gzgr WM_gizmo_operator_set(ggd->add_handle_gizmo, 0, ot, nullptr); ot = WM_operatortype_find("SEQUENCER_OT_retiming_handle_remove", true); WM_gizmo_operator_set(ggd->remove_handle_gizmo, 0, ot, nullptr); + ot = WM_operatortype_find("SEQUENCER_OT_retiming_segment_speed_set", true); + WM_gizmo_operator_set(ggd->speed_set_gizmo, 0, ot, nullptr); } void SEQUENCER_GGT_gizmo_retime(wmGizmoGroupType *gzgt) diff --git a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc index 91bd14b0d19..c10b0498f14 100644 --- a/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc +++ b/source/blender/editors/space_sequencer/sequencer_gizmo_retime_type.cc @@ -403,61 +403,6 @@ static void retime_handle_draw(const bContext *C, immEnd(); } -static void retime_speed_text_draw(const bContext *C, - const Sequence *seq, - const SeqRetimingHandle *handle) -{ - SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); - if (handle == last_handle) { - return; - } - - const Scene *scene = CTX_data_scene(C); - const int start_frame = SEQ_time_left_handle_frame_get(scene, seq); - const int end_frame = SEQ_time_right_handle_frame_get(scene, seq); - - int next_handle_index = SEQ_retiming_handle_index_get(seq, handle) + 1; - const SeqRetimingHandle *next_handle = &SEQ_retiming_handles_get(seq)[next_handle_index]; - if (handle_x_get(scene, seq, next_handle) < start_frame || - handle_x_get(scene, seq, handle) > end_frame) - { - return; /* Label out of strip bounds. */ - } - - char label_str[40]; - size_t label_len; - - if (SEQ_retiming_handle_is_transition_type(handle)) { - const float prev_speed = SEQ_retiming_handle_speed_get(seq, handle - 1); - const float next_speed = SEQ_retiming_handle_speed_get(seq, next_handle + 1); - label_len = SNPRINTF_RLEN(label_str, - "%d%% - %d%%", - round_fl_to_int(prev_speed * 100.0f), - round_fl_to_int(next_speed * 100.0f)); - } - else { - const float speed = SEQ_retiming_handle_speed_get(seq, next_handle); - label_len = SNPRINTF_RLEN(label_str, "%d%%", round_fl_to_int(speed * 100.0f)); - } - - const float width = pixels_to_view_width(C, BLF_width(BLF_default(), label_str, label_len)); - - const float xmin = max_ff(SEQ_time_left_handle_frame_get(scene, seq), - handle_x_get(scene, seq, handle)); - const float xmax = min_ff(SEQ_time_right_handle_frame_get(scene, seq), - handle_x_get(scene, seq, next_handle)); - - const float text_x = (xmin + xmax - width) / 2; - const float text_y = strip_y_rescale(seq, 0) + pixels_to_view_height(C, 5); - - if (width > xmax - xmin) { - return; /* Not enough space to draw label. */ - } - - const uchar col[4] = {255, 255, 255, 255}; - UI_view2d_text_cache_add(UI_view2d_fromcontext(C), text_x, text_y, label_str, label_len, col); -} - static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz) { RetimeHandleMoveGizmo *gizmo = (RetimeHandleMoveGizmo *)gz; @@ -488,8 +433,6 @@ static void gizmo_retime_handle_draw(const bContext *C, wmGizmo *gz) MutableSpan handles = SEQ_retiming_handles_get(seq); for (const SeqRetimingHandle &handle : handles) { - retime_speed_text_draw(C, seq, &handle); - if (&handle == handles.begin()) { continue; /* Ignore first handle. */ } @@ -646,3 +589,183 @@ void GIZMO_GT_retime_remove(wmGizmoType *gzt) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Speed Set Gizmo + * \{ */ + +size_t label_str_get(const Sequence *seq, + const SeqRetimingHandle *handle, + size_t str_len, + char *r_label_str) +{ + const SeqRetimingHandle *next_handle = handle + 1; + if (SEQ_retiming_handle_is_transition_type(handle)) { + const float prev_speed = SEQ_retiming_handle_speed_get(seq, handle - 1); + const float next_speed = SEQ_retiming_handle_speed_get(seq, next_handle + 1); + return BLI_snprintf_rlen(r_label_str, + str_len, + "%d%% - %d%%", + round_fl_to_int(prev_speed * 100.0f), + round_fl_to_int(next_speed * 100.0f)); + } + const float speed = SEQ_retiming_handle_speed_get(seq, next_handle); + return BLI_snprintf_rlen(r_label_str, str_len, "%d%%", round_fl_to_int(speed * 100.0f)); +} + +static bool label_rect_get(const bContext *C, + const Sequence *seq, + const SeqRetimingHandle *handle, + char *label_str, + size_t label_len, + rctf *rect) +{ + const Scene *scene = CTX_data_scene(C); + const SeqRetimingHandle *next_handle = handle + 1; + const float width = pixels_to_view_width(C, BLF_width(BLF_default(), label_str, label_len)); + const float height = pixels_to_view_height(C, BLF_height(BLF_default(), label_str, label_len)); + + const float xmin = max_ff(SEQ_time_left_handle_frame_get(scene, seq), + handle_x_get(scene, seq, handle)); + const float xmax = min_ff(SEQ_time_right_handle_frame_get(scene, seq), + handle_x_get(scene, seq, next_handle)); + + rect->xmin = (xmin + xmax - width) / 2; + rect->xmax = rect->xmin + width; + rect->ymin = strip_y_rescale(seq, 0) + pixels_to_view_height(C, 5); + rect->ymax = rect->ymin + height; + + return width < xmax - xmin; +} + +static void label_rect_apply_mouseover_offset(const View2D *v2d, rctf *rect) +{ + float scale_x, scale_y; + UI_view2d_scale_get_inverse(v2d, &scale_x, &scale_y); + rect->xmin -= RETIME_HANDLE_MOUSEOVER_THRESHOLD * scale_x; + rect->xmax += RETIME_HANDLE_MOUSEOVER_THRESHOLD * scale_x; + rect->ymax += RETIME_HANDLE_MOUSEOVER_THRESHOLD * scale_y; +} + +static void retime_speed_text_draw(const bContext *C, + const Sequence *seq, + const SeqRetimingHandle *handle) +{ + SeqRetimingHandle *last_handle = SEQ_retiming_last_handle_get(seq); + if (handle == last_handle) { + return; + } + + const Scene *scene = CTX_data_scene(C); + const int start_frame = SEQ_time_left_handle_frame_get(scene, seq); + const int end_frame = SEQ_time_right_handle_frame_get(scene, seq); + + const SeqRetimingHandle *next_handle = handle + 1; + if (handle_x_get(scene, seq, next_handle) < start_frame || + handle_x_get(scene, seq, handle) > end_frame) + { + return; /* Label out of strip bounds. */ + } + + char label_str[40]; + rctf label_rect; + size_t label_len = label_str_get(seq, handle, sizeof(label_str), label_str); + + if (!label_rect_get(C, seq, handle, label_str, label_len, &label_rect)) { + return; /* Not enough space to draw label. */ + } + + const uchar col[4] = {255, 255, 255, 255}; + UI_view2d_text_cache_add( + UI_view2d_fromcontext(C), label_rect.xmin, label_rect.ymin, label_str, label_len, col); +} + +static void gizmo_retime_speed_set_draw(const bContext *C, wmGizmo * /* gz */) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + + wmOrtho2_region_pixelspace(CTX_wm_region(C)); + GPU_blend(GPU_BLEND_ALPHA); + GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(seq); + MutableSpan handles = SEQ_retiming_handles_get(seq); + + for (const SeqRetimingHandle &handle : handles) { + retime_speed_text_draw(C, seq, &handle); + } + + immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); + + UI_view2d_text_cache_draw(CTX_wm_region(C)); + UI_view2d_view_ortho(v2d); /* 'UI_view2d_text_cache_draw()' messes up current view. */ +} + +static int gizmo_retime_speed_set_test_select(bContext *C, wmGizmo *gz, const int mval[2]) +{ + Scene *scene = CTX_data_scene(C); + wmGizmoOpElem *op_elem = WM_gizmo_operator_get(gz, 0); + const View2D *v2d = UI_view2d_fromcontext(C); + + Sequence *seq = active_seq_from_context(C); + SEQ_retiming_data_ensure(seq); + + for (const SeqRetimingHandle &handle : SEQ_retiming_handles_get(seq)) { + if (SEQ_retiming_handle_is_transition_type(&handle)) { + continue; + } + + char label_str[40]; + rctf label_rect; + size_t label_len = label_str_get(seq, &handle, sizeof(label_str), label_str); + + if (!label_rect_get(C, seq, &handle, label_str, label_len, &label_rect)) { + continue; + } + + label_rect_apply_mouseover_offset(v2d, &label_rect); + + float mouse_view[2]; + UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouse_view[0], &mouse_view[1]); + + if (!BLI_rctf_isect_pt(&label_rect, mouse_view[0], mouse_view[1])) { + continue; + } + + /* Store next handle in RNA property, since label rect uses first handle as reference. */ + const int handle_index = SEQ_retiming_handle_index_get(seq, &handle) + 1; + RNA_int_set(&op_elem->ptr, "handle_index", handle_index); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return 0; + } + + return -1; +} + +static int gizmo_retime_speed_set_cursor_get(wmGizmo *gz) +{ + if (RNA_boolean_get(gz->ptr, "show_drag")) { + return WM_CURSOR_TEXT_EDIT; + } + return WM_CURSOR_DEFAULT; +} + +void GIZMO_GT_speed_set_remove(wmGizmoType *gzt) +{ + /* Identifiers. */ + gzt->idname = "GIZMO_GT_retime_speed_set"; + + /* Api callbacks. */ + gzt->draw = gizmo_retime_speed_set_draw; + gzt->test_select = gizmo_retime_speed_set_test_select; + gzt->cursor_get = gizmo_retime_speed_set_cursor_get; + gzt->struct_size = sizeof(wmGizmo); + + /* Currently only used for cursor display. */ + RNA_def_boolean(gzt->srna, "show_drag", true, "Show Drag", ""); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index f23861a1353..77fb977c55a 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -312,6 +312,7 @@ void SEQUENCER_OT_retiming_reset(struct wmOperatorType *ot); void SEQUENCER_OT_retiming_handle_move(struct wmOperatorType *ot); void SEQUENCER_OT_retiming_handle_add(struct wmOperatorType *ot); void SEQUENCER_OT_retiming_handle_remove(struct wmOperatorType *ot); +void SEQUENCER_OT_retiming_segment_speed_set(struct wmOperatorType *ot); /* sequencer_gizmo_retime.c */ void SEQUENCER_GGT_gizmo_retime(struct wmGizmoGroupType *gzgt); @@ -320,6 +321,7 @@ void SEQUENCER_GGT_gizmo_retime(struct wmGizmoGroupType *gzgt); void GIZMO_GT_retime_handle_add(struct wmGizmoType *gzt); void GIZMO_GT_retime_handle(struct wmGizmoType *gzt); void GIZMO_GT_retime_remove(struct wmGizmoType *gzt); +void GIZMO_GT_speed_set_remove(struct wmGizmoType *gzt); #ifdef __cplusplus } diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c index b808f13de1d..4745923a4da 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.c +++ b/source/blender/editors/space_sequencer/sequencer_ops.c @@ -74,6 +74,7 @@ void sequencer_operatortypes(void) WM_operatortype_append(SEQUENCER_OT_retiming_handle_move); WM_operatortype_append(SEQUENCER_OT_retiming_handle_add); WM_operatortype_append(SEQUENCER_OT_retiming_handle_remove); + WM_operatortype_append(SEQUENCER_OT_retiming_segment_speed_set); /* sequencer_select.c */ WM_operatortype_append(SEQUENCER_OT_select_all); diff --git a/source/blender/editors/space_sequencer/sequencer_retiming.cc b/source/blender/editors/space_sequencer/sequencer_retiming.cc index 26fe877b959..51ee8af7a08 100644 --- a/source/blender/editors/space_sequencer/sequencer_retiming.cc +++ b/source/blender/editors/space_sequencer/sequencer_retiming.cc @@ -461,3 +461,95 @@ void SEQUENCER_OT_retiming_handle_remove(wmOperatorType *ot) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Retiming Set Segment Speed + * \{ */ + +static int sequencer_retiming_segment_speed_set_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + Sequence *seq = ed->act_seq; + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = &handles[RNA_int_get(op->ptr, "handle_index")]; + + SEQ_retiming_handle_speed_set(scene, seq, handle, RNA_float_get(op->ptr, "speed")); + SEQ_relations_invalidate_cache_raw(scene, seq); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +static int sequencer_retiming_segment_speed_set_invoke(bContext *C, + wmOperator *op, + const wmEvent *event) +{ + const Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + const Sequence *seq = ed->act_seq; + + if (seq == nullptr) { + return OPERATOR_CANCELLED; + } + + MutableSpan handles = SEQ_retiming_handles_get(seq); + SeqRetimingHandle *handle = nullptr; + + if (RNA_struct_property_is_set(op->ptr, "handle_index")) { + const int handle_index = RNA_int_get(op->ptr, "handle_index"); + BLI_assert(handle_index < handles.size()); + handle = &handles[handle_index]; + } + else { + handle = closest_retiming_handle_get(C, seq, event->mval[0]); + } + + if (handle == nullptr) { + BKE_report(op->reports, RPT_ERROR, "No handle available"); + return OPERATOR_CANCELLED; + } + + RNA_float_set(op->ptr, "speed", SEQ_retiming_handle_speed_get(seq, handle) * 100.0f); + RNA_int_set(op->ptr, "handle_index", SEQ_retiming_handle_index_get(seq, handle)); + return WM_operator_props_popup(C, op, event); +} + +void SEQUENCER_OT_retiming_segment_speed_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Speed"; + ot->description = "Set speed of retimed segment"; + ot->idname = "SEQUENCER_OT_retiming_segment_speed_set"; + + /* api callbacks */ + ot->invoke = sequencer_retiming_segment_speed_set_invoke; + ot->exec = sequencer_retiming_segment_speed_set_exec; + ot->poll = retiming_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop = RNA_def_int(ot->srna, + "handle_index", + 0, + 0, + INT_MAX, + "Handle Index", + "Index of handle to be removed", + 0, + INT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); + + prop = RNA_def_float(ot->srna, + "speed", + 100.0f, + 0.001f, + FLT_MAX, + "Speed", + "New speed of retimed segment", + 0.1f, + INT_MAX); +} + +/** \} */ diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 7806fb4f3eb..f95d364f9c1 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -430,6 +430,7 @@ static void sequencer_gizmos(void) WM_gizmotype_append(GIZMO_GT_retime_handle_add); WM_gizmotype_append(GIZMO_GT_retime_handle); WM_gizmotype_append(GIZMO_GT_retime_remove); + WM_gizmotype_append(GIZMO_GT_speed_set_remove); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d); WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate); diff --git a/source/blender/sequencer/SEQ_retiming.h b/source/blender/sequencer/SEQ_retiming.h index b1879b68d95..16f70065823 100644 --- a/source/blender/sequencer/SEQ_retiming.h +++ b/source/blender/sequencer/SEQ_retiming.h @@ -48,6 +48,10 @@ void SEQ_retiming_offset_handle(const struct Scene *scene, const int offset); float SEQ_retiming_handle_speed_get(const struct Sequence *seq, const struct SeqRetimingHandle *handle); +void SEQ_retiming_handle_speed_set(const struct Scene *scene, + struct Sequence *seq, + struct SeqRetimingHandle *handle, + const float speed); int SEQ_retiming_handle_index_get(const struct Sequence *seq, const struct SeqRetimingHandle *handle); void SEQ_retiming_sound_animation_data_set(const struct Scene *scene, const struct Sequence *seq); diff --git a/source/blender/sequencer/intern/strip_retiming.cc b/source/blender/sequencer/intern/strip_retiming.cc index 89645192d3e..b9b85485f21 100644 --- a/source/blender/sequencer/intern/strip_retiming.cc +++ b/source/blender/sequencer/intern/strip_retiming.cc @@ -535,6 +535,29 @@ float SEQ_retiming_handle_speed_get(const Sequence *seq, const SeqRetimingHandle return speed; } +void SEQ_retiming_handle_speed_set(const Scene *scene, + Sequence *seq, + SeqRetimingHandle *handle, + const float speed) +{ + if (handle->strip_frame_index == 0) { + return; + } + + const SeqRetimingHandle *handle_prev = handle - 1; + const float speed_fac = 100.0f / speed; + + const int frame_index_max = seq->len; + const int frame_retimed_prev = round_fl_to_int(handle_prev->retiming_factor * frame_index_max); + const int frame_retimed = round_fl_to_int(handle->retiming_factor * frame_index_max); + + const int segment_duration = frame_retimed - frame_retimed_prev; + const int new_duration = segment_duration * speed_fac; + + const int offset = (handle_prev->strip_frame_index + new_duration) - handle->strip_frame_index; + SEQ_retiming_offset_handle(scene, seq, handle, offset); +} + enum eRangeType { LINEAR = 0, TRANSITION = 1,