diff --git a/release/datafiles/userdef/userdef_default.c b/release/datafiles/userdef/userdef_default.c index 9b9d5d293eb..030a5b2ae67 100644 --- a/release/datafiles/userdef/userdef_default.c +++ b/release/datafiles/userdef/userdef_default.c @@ -237,6 +237,8 @@ const UserDef U_default = { .statusbar_flag = STATUSBAR_SHOW_VERSION, .file_preview_type = USER_FILE_PREVIEW_AUTO, + .sequencer_editor_flag = USER_SEQ_ED_SIMPLE_TWEAKING, + .runtime = { .is_dirty = 0, diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index c2dd68e09ae..e9b4151314e 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3037,7 +3037,7 @@ def km_sequencer(params): ("transform.seq_slide", {"type": 'G', "value": 'PRESS'}, {"properties": [("view2d_edge_pan", True)]}), ("transform.seq_slide", {"type": params.select_mouse, "value": 'CLICK_DRAG'}, - {"properties": [("view2d_edge_pan", True)]}), + {"properties": [("view2d_edge_pan", True), ("use_restore_handle_selection", True)]}), ("transform.transform", {"type": 'E', "value": 'PRESS'}, {"properties": [("mode", 'TIME_EXTEND')]}), ("marker.add", {"type": 'M', "value": 'PRESS'}, None), @@ -8841,10 +8841,11 @@ def km_3d_view_tool_sculpt_gpencil_select_lasso(params): def km_sequencer_editor_tool_generic_select_timeline_rcs(params, fallback): return [ + ("sequencer.select_handle", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), *_template_items_change_frame(params), # Frame change can be canceled if click happens on strip handle. In such case move the handle. ("transform.seq_slide", {"type": 'LEFTMOUSE', "value": 'PRESS'}, - {"properties": [("view2d_edge_pan", True)]}), + {"properties": [("view2d_edge_pan", True), ("use_restore_handle_selection", True)]}), ] diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index fd9c04ea5bb..472f594093d 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -535,6 +535,17 @@ class USERPREF_PT_edit_node_editor(EditingPanel, CenterAlignMixIn, Panel): layout.prop(edit, "node_preview_resolution", text="Preview Resolution") +class USERPREF_PT_edit_sequence_editor(EditingPanel, CenterAlignMixIn, Panel): + bl_label = "Video Sequencer" + bl_options = {'DEFAULT_CLOSED'} + + def draw_centered(self, context, layout): + prefs = context.preferences + edit = prefs.edit + + layout.prop(edit, "use_sequencer_simplified_tweaking") + + class USERPREF_PT_edit_misc(EditingPanel, CenterAlignMixIn, Panel): bl_label = "Miscellaneous" bl_options = {'DEFAULT_CLOSED'} @@ -2841,6 +2852,7 @@ classes = ( USERPREF_PT_edit_gpencil, USERPREF_PT_edit_text_editor, USERPREF_PT_edit_node_editor, + USERPREF_PT_edit_sequence_editor, USERPREF_PT_edit_misc, USERPREF_PT_animation_timeline, diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 1775f5228a7..5d0a081e865 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -29,7 +29,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 50 +#define BLENDER_FILE_SUBVERSION 51 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index 63fe32a41bf..397a173c092 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -969,6 +969,10 @@ void blo_do_versions_userdef(UserDef *userdef) userdef->statusbar_flag |= STATUSBAR_SHOW_EXTENSIONS_UPDATES; } + if (!USER_VERSION_ATLEAST(402, 51)) { + userdef->sequencer_editor_flag |= USER_SEQ_ED_SIMPLE_TWEAKING; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a USER_VERSION_ATLEAST check. diff --git a/source/blender/editors/animation/anim_ops.cc b/source/blender/editors/animation/anim_ops.cc index 16a42f48e47..31d3010a88a 100644 --- a/source/blender/editors/animation/anim_ops.cc +++ b/source/blender/editors/animation/anim_ops.cc @@ -225,6 +225,23 @@ static bool use_sequencer_snapping(bContext *C) (snap_flag & SEQ_SNAP_CURRENT_FRAME_TO_STRIPS); } +static bool sequencer_skip_for_handle_tweak(const bContext *C, const wmEvent *event) +{ + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + return false; + } + + const Scene *scene = CTX_data_scene(C); + const View2D *v2d = UI_view2d_fromcontext(C); + + float mouse_co[2]; + UI_view2d_region_to_view(v2d, event->mval[0], event->mval[1], &mouse_co[0], &mouse_co[1]); + + StripSelection selection = ED_sequencer_pick_strip_and_handle(scene, v2d, mouse_co); + + return selection.handle != SEQ_HANDLE_NONE; +} + /* Modal Operator init */ static int change_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event) { @@ -233,6 +250,9 @@ static int change_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event if (CTX_wm_space_seq(C) != nullptr && region->regiontype == RGN_TYPE_PREVIEW) { return OPERATOR_CANCELLED; } + if (sequencer_skip_for_handle_tweak(C, event)) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } /* Change to frame that mouse is over before adding modal handler, * as user could click on a single frame (jump to frame) as well as diff --git a/source/blender/editors/include/ED_sequencer.hh b/source/blender/editors/include/ED_sequencer.hh index bdfc6591841..79bcb91594b 100644 --- a/source/blender/editors/include/ED_sequencer.hh +++ b/source/blender/editors/include/ED_sequencer.hh @@ -14,11 +14,19 @@ struct Scene; struct Sequence; struct SpaceSeq; struct bContext; +struct View2D; enum eSeqHandle { SEQ_HANDLE_NONE, SEQ_HANDLE_LEFT, SEQ_HANDLE_RIGHT, + SEQ_HANDLE_BOTH, +}; + +struct StripSelection { + Sequence *seq1 = nullptr; + Sequence *seq2 = nullptr; + eSeqHandle handle = SEQ_HANDLE_NONE; }; void ED_sequencer_select_sequence_single(Scene *scene, Sequence *seq, bool deselect_all); @@ -54,7 +62,6 @@ Sequence *ED_sequencer_special_preview_get(); void ED_sequencer_special_preview_set(bContext *C, const int mval[2]); void ED_sequencer_special_preview_clear(); bool sequencer_retiming_mode_is_active(const bContext *C); - /** * Returns collection with selected strips presented to user. If operation is done in preview, * collection is limited to selected presented strips, that can produce image output at current @@ -64,5 +71,8 @@ bool sequencer_retiming_mode_is_active(const bContext *C); * \return collection of strips (`Sequence`) */ blender::VectorSet ED_sequencer_selected_strips_from_context(bContext *C); -bool ED_sequencer_can_select_handle(const Sequence *seq); +StripSelection ED_sequencer_pick_strip_and_handle(const struct Scene *scene, + const View2D *v2d, + float mouse_co[2]); +bool ED_sequencer_can_select_handle(const Scene *scene, const Sequence *seq, const View2D *v2d); bool ED_sequencer_handle_is_selected(const Sequence *seq, eSeqHandle handle); diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index fd145c8049c..002c7fb935c 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -233,6 +233,7 @@ void SEQUENCER_OT_scene_frame_range_update(wmOperatorType *ot); void SEQUENCER_OT_select_all(wmOperatorType *ot); void SEQUENCER_OT_select(wmOperatorType *ot); +void SEQUENCER_OT_select_handle(wmOperatorType *ot); void SEQUENCER_OT_select_side_of_frame(wmOperatorType *ot); void SEQUENCER_OT_select_more(wmOperatorType *ot); void SEQUENCER_OT_select_less(wmOperatorType *ot); @@ -329,6 +330,7 @@ SeqRetimingKey *retiming_mousover_key_get(const bContext *C, const int mval[2], int left_fake_key_frame_get(const bContext *C, const Sequence *seq); int right_fake_key_frame_get(const bContext *C, const Sequence *seq); bool retiming_keys_are_visible(const SpaceSeq *sseq); +rctf seq_retiming_keys_box_get(const Scene *scene, const View2D *v2d, const Sequence *seq); /* `sequencer_timeline_draw.cc` */ blender::Vector sequencer_visible_strips_get(const bContext *C); diff --git a/source/blender/editors/space_sequencer/sequencer_ops.cc b/source/blender/editors/space_sequencer/sequencer_ops.cc index cc3f1965340..c69fd84b981 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.cc +++ b/source/blender/editors/space_sequencer/sequencer_ops.cc @@ -80,6 +80,7 @@ void sequencer_operatortypes() /* `sequencer_select.cc` */ WM_operatortype_append(SEQUENCER_OT_select_all); WM_operatortype_append(SEQUENCER_OT_select); + WM_operatortype_append(SEQUENCER_OT_select_handle); WM_operatortype_append(SEQUENCER_OT_select_more); WM_operatortype_append(SEQUENCER_OT_select_less); WM_operatortype_append(SEQUENCER_OT_select_linked_pick); diff --git a/source/blender/editors/space_sequencer/sequencer_retiming_draw.cc b/source/blender/editors/space_sequencer/sequencer_retiming_draw.cc index afcbc84179e..226afd3cea4 100644 --- a/source/blender/editors/space_sequencer/sequencer_retiming_draw.cc +++ b/source/blender/editors/space_sequencer/sequencer_retiming_draw.cc @@ -100,7 +100,7 @@ static rctf strip_box_get(const Scene *scene, const View2D *v2d, const Sequence /** Size in pixels. */ #define RETIME_KEY_MOUSEOVER_THRESHOLD (16.0f * UI_SCALE_FAC) -static rctf retiming_keys_box_get(const Scene *scene, const View2D *v2d, const Sequence *seq) +rctf seq_retiming_keys_box_get(const Scene *scene, const View2D *v2d, const Sequence *seq) { rctf rect = strip_box_get(scene, v2d, seq); rect.ymax = KEY_CENTER + KEY_SIZE / 2; @@ -129,7 +129,7 @@ static bool retiming_fake_key_is_clicked(const bContext *C, { const View2D *v2d = UI_view2d_fromcontext(C); - rctf box = retiming_keys_box_get(CTX_data_scene(C), v2d, seq); + rctf box = seq_retiming_keys_box_get(CTX_data_scene(C), v2d, seq); if (!BLI_rctf_isect_pt(&box, mval[0], mval[1])) { return false; } @@ -206,7 +206,7 @@ SeqRetimingKey *retiming_mousover_key_get(const bContext *C, const int mval[2], const Scene *scene = CTX_data_scene(C); const View2D *v2d = UI_view2d_fromcontext(C); for (Sequence *seq : sequencer_visible_strips_get(C)) { - rctf box = retiming_keys_box_get(scene, v2d, seq); + rctf box = seq_retiming_keys_box_get(scene, v2d, seq); if (!BLI_rctf_isect_pt(&box, mval[0], mval[1])) { continue; } diff --git a/source/blender/editors/space_sequencer/sequencer_select.cc b/source/blender/editors/space_sequencer/sequencer_select.cc index ab7283b738a..3b81ed71fa8 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.cc +++ b/source/blender/editors/space_sequencer/sequencer_select.cc @@ -16,9 +16,11 @@ #include "BLI_ghash.h" #include "BLI_math_geom.h" #include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" #include "BLI_utildefines.h" #include "DNA_scene_types.h" +#include "DNA_space_types.h" #include "BKE_context.hh" #include "BKE_report.hh" @@ -54,6 +56,19 @@ /** \name Selection Utilities * \{ */ +class MouseCoords { + public: + blender::int2 region; + blender::float2 view; + + MouseCoords(const View2D *v2d, int x, int y) + { + region[0] = x; + region[1] = y; + UI_view2d_region_to_view(v2d, x, y, &view[0], &view[1]); + } +}; + blender::VectorSet all_strips_from_context(bContext *C) { Scene *scene = CTX_data_scene(C); @@ -331,7 +346,7 @@ Sequence *find_nearest_seq(const Scene *scene, if (SEQ_transform_sequence_can_be_translated(seq)) { /* Clamp handles to defined size in pixel space. */ - handsize = 2.0f * sequence_handle_size_get_clamped(scene, seq, pixelx); + handsize = 4.0f * sequence_handle_size_get_clamped(scene, seq, pixelx); displen = float(abs(SEQ_time_left_handle_frame_get(scene, seq) - SEQ_time_right_handle_frame_get(scene, seq))); @@ -833,10 +848,25 @@ bool ED_sequencer_handle_is_selected(const Sequence *seq, eSeqHandle handle) ((handle == SEQ_HANDLE_RIGHT) && (seq->flag & SEQ_RIGHTSEL)); } -static bool element_already_selected(const Sequence *seq, const eSeqHandle handle_clicked) +static bool element_already_selected(const StripSelection selection) { - return ((seq->flag & SELECT) && handle_clicked == SEQ_HANDLE_NONE) || - ED_sequencer_handle_is_selected(seq, handle_clicked); + if (selection.seq1 == nullptr) { + return false; + } + const bool seq1_already_selected = ((selection.seq1->flag & SELECT) != 0); + if (selection.seq2 == nullptr) { + const bool handle_already_selected = ED_sequencer_handle_is_selected(selection.seq1, + selection.handle) || + selection.handle == SEQ_HANDLE_NONE; + return seq1_already_selected && handle_already_selected; + } + const bool seq2_already_selected = ((selection.seq2->flag & SELECT) != 0); + const int seq1_handle = selection.seq1->flag & (SEQ_RIGHTSEL | SEQ_LEFTSEL); + const int seq2_handle = selection.seq2->flag & (SEQ_RIGHTSEL | SEQ_LEFTSEL); + /* Handles must be selected in XOR fashion, with `seq1` matching `handle_clicked`. */ + const bool both_handles_selected = seq1_handle == selection.handle && seq2_handle != 0 && + seq1_handle != seq2_handle; + return seq1_already_selected && seq2_already_selected && both_handles_selected; } static void sequencer_select_strip_impl(const Editing *ed, @@ -891,14 +921,188 @@ static void sequencer_select_strip_impl(const Editing *ed, } } -bool ED_sequencer_can_select_handle(const Sequence *seq) +/* Similar to `sequence_handle_size_get_clamped()` but allows for larger clickable area. */ +static float clickable_handle_size_get(const Scene *scene, const Sequence *seq, const View2D *v2d) { - if ((seq->type & SEQ_TYPE_EFFECT) && SEQ_effect_get_num_inputs(seq->type) > 0) { + const float pixelx = 1 / UI_view2d_scale_get_x(v2d); + const float strip_len = SEQ_time_right_handle_frame_get(scene, seq) - + SEQ_time_left_handle_frame_get(scene, seq); + return min_ff(15.0f * pixelx * U.pixelsize, strip_len / 4); +} + +bool ED_sequencer_can_select_handle(const Scene *scene, const Sequence *seq, const View2D *v2d) +{ + if (SEQ_effect_get_num_inputs(seq->type) > 0) { + return false; + } + + int min_len = 25 * U.pixelsize; + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + min_len = 15 * U.pixelsize; + } + + const float pixelx = 1 / UI_view2d_scale_get_x(v2d); + const int strip_len = SEQ_time_right_handle_frame_get(scene, seq) - + SEQ_time_left_handle_frame_get(scene, seq); + if (strip_len / pixelx < min_len) { return false; } return true; } +static void strip_clickable_areas_get(const Scene *scene, + const Sequence *seq, + const View2D *v2d, + rctf *r_body, + rctf *r_left_handle, + rctf *r_right_handle) +{ + seq_rectf(scene, seq, r_body); + *r_left_handle = *r_body; + *r_right_handle = *r_body; + + const float handsize = clickable_handle_size_get(scene, seq, v2d); + BLI_rctf_pad(r_left_handle, handsize / 3, 0.0f); + BLI_rctf_pad(r_right_handle, handsize / 3, 0.0f); + r_left_handle->xmax = r_body->xmin + handsize; + r_right_handle->xmin = r_body->xmax - handsize; + BLI_rctf_pad(r_body, -handsize, 0.0f); +} + +static rctf strip_clickable_area_get(const Scene *scene, const View2D *v2d, const Sequence *seq) +{ + rctf body, left, right; + strip_clickable_areas_get(scene, seq, v2d, &body, &left, &right); + BLI_rctf_union(&body, &left); + BLI_rctf_union(&body, &right); + return body; +} + +static float strip_to_frame_distance(const Scene *scene, + const View2D *v2d, + const Sequence *seq, + float timeline_frame) +{ + rctf body, left, right; + strip_clickable_areas_get(scene, seq, v2d, &body, &left, &right); + return BLI_rctf_length_x(&body, timeline_frame); +} + +/* Get strips that can be selected by click. */ +static blender::Vector mouseover_strips_sorted_get(const Scene *scene, + const View2D *v2d, + float mouse_co[2]) +{ + Editing *ed = SEQ_editing_get(scene); + + blender::Vector strips; + LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) { + if (seq->machine != int(mouse_co[1])) { + continue; + } + if (SEQ_time_left_handle_frame_get(scene, seq) > v2d->cur.xmax) { + continue; + } + if (SEQ_time_right_handle_frame_get(scene, seq) < v2d->cur.xmin) { + continue; + } + if (!ED_sequencer_can_select_handle(scene, seq, v2d)) { + continue; + } + const rctf body = strip_clickable_area_get(scene, v2d, seq); + if (!BLI_rctf_isect_pt_v(&body, mouse_co)) { + continue; + } + strips.append(seq); + } + + BLI_assert(strips.size() <= 2); + + /* Ensure, that `seq1` is left strip and `seq2` right strip. */ + if (strips.size() == 2 && strip_to_frame_distance(scene, v2d, strips[0], mouse_co[0]) < + strip_to_frame_distance(scene, v2d, strips[1], mouse_co[0])) + { + std::swap(strips[0], strips[1]); + } + + return strips; +} + +static bool strips_are_adjacent(const Scene *scene, const Sequence *seq1, const Sequence *seq2) +{ + const int s1_left = SEQ_time_left_handle_frame_get(scene, seq1); + const int s1_right = SEQ_time_right_handle_frame_get(scene, seq1); + const int s2_left = SEQ_time_left_handle_frame_get(scene, seq2); + const int s2_right = SEQ_time_right_handle_frame_get(scene, seq2); + + return s1_right == s2_left || s1_left == s2_right; +} + +static eSeqHandle get_strip_handle_under_cursor(const Scene *scene, + const Sequence *seq, + const View2D *v2d, + float mouse_co[2]) +{ + rctf body, left, right; + strip_clickable_areas_get(scene, seq, v2d, &body, &left, &right); + if (BLI_rctf_isect_pt_v(&left, mouse_co)) { + return SEQ_HANDLE_LEFT; + } + if (BLI_rctf_isect_pt_v(&right, mouse_co)) { + return SEQ_HANDLE_RIGHT; + } + + return SEQ_HANDLE_NONE; +} + +static bool is_mouse_over_both_handles_of_adjacent_strips(const Scene *scene, + blender::Vector strips, + const View2D *v2d, + float mouse_co[2]) +{ + const eSeqHandle seq1_handle = get_strip_handle_under_cursor(scene, strips[0], v2d, mouse_co); + + if (seq1_handle == SEQ_HANDLE_NONE) { + return false; + } + if (!strips_are_adjacent(scene, strips[0], strips[1])) { + return false; + } + const eSeqHandle seq2_handle = get_strip_handle_under_cursor(scene, strips[1], v2d, mouse_co); + if (seq1_handle == SEQ_HANDLE_RIGHT && seq2_handle != SEQ_HANDLE_LEFT) { + return false; + } + else if (seq1_handle == SEQ_HANDLE_LEFT && seq2_handle != SEQ_HANDLE_RIGHT) { + return false; + } + + return true; +} + +StripSelection ED_sequencer_pick_strip_and_handle(const Scene *scene, + const View2D *v2d, + float mouse_co[2]) +{ + blender::Vector strips = mouseover_strips_sorted_get(scene, v2d, mouse_co); + + StripSelection selection; + + if (strips.size() == 0) { + return selection; + } + + selection.seq1 = strips[0]; + selection.handle = get_strip_handle_under_cursor(scene, selection.seq1, v2d, mouse_co); + + if (strips.size() == 2 && (U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) != 0 && + is_mouse_over_both_handles_of_adjacent_strips(scene, strips, v2d, mouse_co)) + { + selection.seq2 = strips[1]; + } + + return selection; +} + static bool use_retiming_mode(const bContext *C, const Sequence *seq_key_test) { return seq_key_test && SEQ_retiming_data_is_editable(seq_key_test) && @@ -907,7 +1111,7 @@ static bool use_retiming_mode(const bContext *C, const Sequence *seq_key_test) int sequencer_select_exec(bContext *C, wmOperator *op) { - View2D *v2d = UI_view2d_fromcontext(C); + const View2D *v2d = UI_view2d_fromcontext(C); Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); ARegion *region = CTX_wm_region(C); @@ -936,25 +1140,24 @@ int sequencer_select_exec(bContext *C, wmOperator *op) bool toggle = RNA_boolean_get(op->ptr, "toggle"); bool center = RNA_boolean_get(op->ptr, "center"); - int mval[2]; - mval[0] = RNA_int_get(op->ptr, "mouse_x"); - mval[1] = RNA_int_get(op->ptr, "mouse_y"); + MouseCoords mouse_co(v2d, RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y")); - eSeqHandle handle_clicked = SEQ_HANDLE_NONE; - Sequence *seq = nullptr; + StripSelection selection; if (region->regiontype == RGN_TYPE_PREVIEW) { - seq = seq_select_seq_from_preview(C, mval, toggle, extend, center); + selection.seq1 = seq_select_seq_from_preview(C, mouse_co.region, toggle, extend, center); } else { - seq = find_nearest_seq(scene, v2d, mval, &handle_clicked); + selection = ED_sequencer_pick_strip_and_handle(scene, v2d, mouse_co.view); } + /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one + * keymap, therefore both properties can be true at the same time. */ Sequence *seq_key_test = nullptr; - SeqRetimingKey *key = retiming_mousover_key_get(C, mval, &seq_key_test); + SeqRetimingKey *key = retiming_mousover_key_get(C, mouse_co.region, &seq_key_test); - /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap, - * therefore both properties can be true at the same time. */ - if (seq && RNA_boolean_get(op->ptr, "linked_time")) { + /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one + * keymap, therefore both properties can be true at the same time. */ + if (selection.seq1 && RNA_boolean_get(op->ptr, "linked_time")) { if (use_retiming_mode(C, seq_key_test)) { return sequencer_retiming_select_linked_time(C, op); } @@ -962,10 +1165,10 @@ int sequencer_select_exec(bContext *C, wmOperator *op) if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } - sequencer_select_strip_impl(ed, seq, handle_clicked, extend, deselect, toggle); - select_linked_time(scene, ed->seqbasep, seq); + sequencer_select_strip_impl(ed, selection.seq1, selection.handle, extend, deselect, toggle); + select_linked_time(scene, ed->seqbasep, selection.seq1); sequencer_select_do_updates(C, scene); - sequencer_select_set_active(scene, seq); + sequencer_select_set_active(scene, selection.seq1); return OPERATOR_FINISHED; } } @@ -975,27 +1178,36 @@ int sequencer_select_exec(bContext *C, wmOperator *op) if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } - sequencer_select_side_of_frame(C, v2d, mval, scene); + sequencer_select_side_of_frame(C, v2d, mouse_co.region, scene); sequencer_select_do_updates(C, scene); return OPERATOR_FINISHED; } /* On Alt selection, select the strip and bordering handles. */ - if (seq && RNA_boolean_get(op->ptr, "linked_handle")) { + if (selection.seq1 && RNA_boolean_get(op->ptr, "linked_handle")) { if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } - sequencer_select_linked_handle(C, seq, handle_clicked); + sequencer_select_linked_handle(C, selection.seq1, selection.handle); sequencer_select_do_updates(C, scene); - sequencer_select_set_active(scene, seq); + sequencer_select_set_active(scene, selection.seq1); return OPERATOR_FINISHED; } const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); + const bool already_selected = element_already_selected(selection); + + SpaceSeq *sseq = CTX_wm_space_seq(C); + if (selection.handle != SEQ_HANDLE_NONE && already_selected) { + sseq->flag &= ~SPACE_SEQ_DESELECT_STRIP_HANDLE; + } + else { + sseq->flag |= SPACE_SEQ_DESELECT_STRIP_HANDLE; + } /* Clicking on already selected element falls on modal operation. * All strips are deselected on mouse button release unless extend mode is used. */ - if (seq && element_already_selected(seq, handle_clicked) && wait_to_deselect_others && !toggle) { + if (already_selected && wait_to_deselect_others && !toggle) { return OPERATOR_RUNNING_MODAL; } @@ -1003,7 +1215,7 @@ int sequencer_select_exec(bContext *C, wmOperator *op) /* Realize "fake" key, if it is clicked on. */ if (key == nullptr && seq_key_test != nullptr) { - key = try_to_realize_virtual_keys(C, seq_key_test, mval); + key = try_to_realize_virtual_keys(C, seq_key_test, mouse_co.region); } bool retiming_key_clicked = (key != nullptr); @@ -1020,12 +1232,15 @@ int sequencer_select_exec(bContext *C, wmOperator *op) bool changed = false; /* Deselect everything */ - if (deselect_all || (seq && (extend == false && deselect == false && toggle == false))) { + if (deselect_all || + (selection.seq1 && (extend == false && deselect == false && toggle == false) && + !already_selected)) + { changed |= ED_sequencer_deselect_all(scene); } /* Nothing to select, but strips could be deselected. */ - if (!seq) { + if (!selection.seq1) { if (changed) { sequencer_select_do_updates(C, scene); } @@ -1033,10 +1248,16 @@ int sequencer_select_exec(bContext *C, wmOperator *op) } /* Do actual selection. */ - sequencer_select_strip_impl(ed, seq, handle_clicked, extend, deselect, toggle); + sequencer_select_strip_impl(ed, selection.seq1, selection.handle, extend, deselect, toggle); + if (selection.seq2 != nullptr) { + /* Invert handle selection for second strip */ + eSeqHandle seq2_handle_clicked = (selection.handle == SEQ_HANDLE_LEFT) ? SEQ_HANDLE_RIGHT : + SEQ_HANDLE_LEFT; + sequencer_select_strip_impl(ed, selection.seq2, seq2_handle_clicked, extend, deselect, toggle); + } sequencer_select_do_updates(C, scene); - sequencer_select_set_active(scene, seq); + sequencer_select_set_active(scene, selection.seq1); return OPERATOR_FINISHED; } @@ -1104,6 +1325,101 @@ void SEQUENCER_OT_select(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Select Handle Operator + * \{ */ + +static int sequencer_select_handle_exec(bContext *C, wmOperator *op) +{ + const View2D *v2d = UI_view2d_fromcontext(C); + Scene *scene = CTX_data_scene(C); + Editing *ed = SEQ_editing_get(scene); + + if (ed == nullptr) { + return OPERATOR_CANCELLED; + } + + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + if (sequencer_retiming_mode_is_active(C) && retiming_keys_are_visible(CTX_wm_space_seq(C))) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + MouseCoords mouse_co(v2d, RNA_int_get(op->ptr, "mouse_x"), RNA_int_get(op->ptr, "mouse_y")); + + StripSelection selection = ED_sequencer_pick_strip_and_handle(scene, v2d, mouse_co.view); + if (selection.seq1 == nullptr || selection.handle == SEQ_HANDLE_NONE) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + /* Ignore clicks on retiming keys. */ + Sequence *seq_key_test = nullptr; + retiming_mousover_key_get(C, mouse_co.region, &seq_key_test); + if (use_retiming_mode(C, seq_key_test) && seq_key_test != nullptr) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + SpaceSeq *sseq = CTX_wm_space_seq(C); + + if (element_already_selected(selection)) { + sseq->flag &= ~SPACE_SEQ_DESELECT_STRIP_HANDLE; + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + else { + sseq->flag |= SPACE_SEQ_DESELECT_STRIP_HANDLE; + ED_sequencer_deselect_all(scene); + } + + /* Do actual selection. */ + sequencer_select_strip_impl(ed, selection.seq1, selection.handle, false, false, false); + if (selection.seq2 != nullptr) { + /* Invert handle selection for second strip */ + eSeqHandle seq2_handle_clicked = (selection.handle == SEQ_HANDLE_LEFT) ? SEQ_HANDLE_RIGHT : + SEQ_HANDLE_LEFT; + sequencer_select_strip_impl(ed, selection.seq2, seq2_handle_clicked, false, false, false); + } + + sequencer_select_do_updates(C, scene); + sequencer_select_set_active(scene, selection.seq1); + return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; +} + +static int sequencer_select_handle_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + + int mval[2]; + WM_event_drag_start_mval(event, region, mval); + + RNA_int_set(op->ptr, "mouse_x", mval[0]); + RNA_int_set(op->ptr, "mouse_y", mval[1]); + + return sequencer_select_handle_exec(C, op); +} + +void SEQUENCER_OT_select_handle(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Select Handle"; + ot->idname = "SEQUENCER_OT_select_handle"; + ot->description = "Select strip handle"; + + /* Api callbacks. */ + ot->exec = sequencer_select_handle_exec; + ot->invoke = sequencer_select_handle_invoke; + ot->poll = ED_operator_sequencer_active; + + /* Flags. */ + ot->flag = OPTYPE_UNDO; + + /* Properties. */ + WM_operator_properties_generic_select(ot); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Select More Operator * \{ */ @@ -1715,7 +2031,7 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op) if (handles) { /* Get the handles draw size. */ float pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask); - float handsize = sequence_handle_size_get_clamped(scene, seq, pixelx); + float handsize = sequence_handle_size_get_clamped(scene, seq, pixelx) * 4; /* Right handle. */ if (rectf.xmax > (SEQ_time_right_handle_frame_get(scene, seq) - handsize)) { @@ -1770,7 +2086,7 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op) static int sequencer_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Scene *scene = CTX_data_scene(C); - View2D *v2d = &CTX_wm_region(C)->v2d; + const View2D *v2d = UI_view2d_fromcontext(C); ARegion *region = CTX_wm_region(C); if (region->regiontype == RGN_TYPE_PREVIEW && !sequencer_view_preview_only_poll(C)) { @@ -1780,11 +2096,14 @@ static int sequencer_box_select_invoke(bContext *C, wmOperator *op, const wmEven const bool tweak = RNA_boolean_get(op->ptr, "tweak"); if (tweak) { - eSeqHandle hand_dummy; int mval[2]; + float mouse_co[2]; WM_event_drag_start_mval(event, region, mval); - Sequence *seq = find_nearest_seq(scene, v2d, mval, &hand_dummy); - if (seq != nullptr) { + UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouse_co[0], &mouse_co[1]); + + StripSelection selection = ED_sequencer_pick_strip_and_handle(scene, v2d, mouse_co); + + if (selection.seq1 != nullptr) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } } diff --git a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc index 99a60161b70..99eb6ea4f27 100644 --- a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc +++ b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc @@ -64,7 +64,6 @@ #include "sequencer_intern.hh" #include "sequencer_quads_batch.hh" -#define SEQ_HANDLE_SIZE 8.0f #define MUTE_ALPHA 120 constexpr float MISSING_ICON_SIZE = 16.0f; @@ -758,7 +757,9 @@ static void draw_handle_transform_text(const TimelineDrawContext *timeline_ctx, float sequence_handle_size_get_clamped(const Scene *scene, Sequence *seq, const float pixelx) { - const float maxhandle = (pixelx * SEQ_HANDLE_SIZE) * U.pixelsize; + const bool use_thin_handle = (U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) != 0; + const float handle_size = use_thin_handle ? 5.0f : 8.0f; + const float maxhandle = (pixelx * handle_size) * U.pixelsize; /* Ensure that handle is not wider, than quarter of strip. */ return min_ff(maxhandle, @@ -767,21 +768,28 @@ float sequence_handle_size_get_clamped(const Scene *scene, Sequence *seq, const 4.0f)); } -/* Draw a handle, on left or right side of strip. */ -static void draw_seq_handle(TimelineDrawContext *timeline_ctx, +static void draw_seq_handle(const TimelineDrawContext *timeline_ctx, const StripDrawContext *strip_ctx, eSeqHandle handle) { const Sequence *seq = strip_ctx->seq; + const bool show_handles = (U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0; const bool strip_selected = (seq->flag & SELECT) != 0; const bool handle_selected = ED_sequencer_handle_is_selected(seq, handle); + if ((!strip_selected || !handle_selected) && !show_handles) { + return; + } if (SEQ_transform_is_locked(timeline_ctx->channels, seq)) { return; } - if (!ED_sequencer_can_select_handle(seq)) { + if ((seq->type & SEQ_TYPE_EFFECT) && SEQ_effect_get_num_inputs(seq->type) > 0) { return; } + if (!ED_sequencer_can_select_handle(timeline_ctx->scene, seq, timeline_ctx->v2d)) { + return; + } + uchar col[4]; if (strip_selected && handle_selected && seq == SEQ_select_active_get(timeline_ctx->scene)) { UI_GetThemeColor4ubv(TH_SEQ_ACTIVE, col); diff --git a/source/blender/editors/space_sequencer/space_sequencer.cc b/source/blender/editors/space_sequencer/space_sequencer.cc index 18b99d6053e..98737e7d2d9 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.cc +++ b/source/blender/editors/space_sequencer/space_sequencer.cc @@ -30,6 +30,7 @@ #include "ED_markers.hh" #include "ED_screen.hh" +#include "ED_sequencer.hh" #include "ED_space_api.hh" #include "ED_time_scrub_ui.hh" #include "ED_transform.hh" @@ -39,6 +40,7 @@ #include "WM_api.hh" #include "WM_message.hh" +#include "SEQ_retiming.hh" #include "SEQ_sequencer.hh" #include "SEQ_time.hh" #include "SEQ_transform.hh" @@ -645,6 +647,92 @@ static void sequencer_main_region_message_subscribe(const wmRegionMessageSubscri } } +static bool is_mouse_over_retiming_key(const Scene *scene, + const Sequence *seq, + const View2D *v2d, + const ScrArea *area, + float mouse_co_region[2]) +{ + const SpaceSeq *sseq = static_cast(area->spacedata.first); + + if (!SEQ_retiming_data_is_editable(seq) || !retiming_keys_are_visible(sseq)) { + return false; + } + + rctf retiming_keys_box = seq_retiming_keys_box_get(scene, v2d, seq); + return BLI_rctf_isect_pt_v(&retiming_keys_box, mouse_co_region); +} + +static void sequencer_main_cursor(wmWindow *win, ScrArea *area, ARegion *region) +{ + int wmcursor = WM_CURSOR_DEFAULT; + + const bToolRef *tref = area->runtime.tool; + if (!STREQ(tref->idname, "builtin.select")) { + WM_cursor_set(win, wmcursor); + return; + } + + rcti scrub_rect = region->winrct; + scrub_rect.ymin = scrub_rect.ymax - UI_TIME_SCRUB_MARGIN_Y; + if (BLI_rcti_isect_pt_v(&scrub_rect, win->eventstate->xy)) { + WM_cursor_set(win, wmcursor); + return; + } + + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + WM_cursor_set(win, wmcursor); + return; + } + + float mouse_co_region[2] = {float(win->eventstate->xy[0] - region->winrct.xmin), + float(win->eventstate->xy[1] - region->winrct.ymin)}; + float mouse_co_view[2]; + UI_view2d_region_to_view( + ®ion->v2d, mouse_co_region[0], mouse_co_region[1], &mouse_co_view[0], &mouse_co_view[1]); + + const Scene *scene = win->scene; + const Editing *ed = SEQ_editing_get(scene); + + if (ed == NULL) { + WM_cursor_set(win, wmcursor); + return; + } + + StripSelection selection = ED_sequencer_pick_strip_and_handle( + scene, ®ion->v2d, mouse_co_view); + + if (selection.seq1 == nullptr) { + WM_cursor_set(win, wmcursor); + return; + } + + if (is_mouse_over_retiming_key(scene, selection.seq1, ®ion->v2d, area, mouse_co_region)) { + WM_cursor_set(win, wmcursor); + return; + } + + const View2D *v2d = ®ion->v2d; + const float scale_y = UI_view2d_scale_get_y(v2d); + + if (!ED_sequencer_can_select_handle(scene, selection.seq1, v2d) || scale_y < 16 * U.pixelsize) { + WM_cursor_set(win, wmcursor); + return; + } + + if (selection.seq1 != nullptr && selection.seq2 != nullptr) { + wmcursor = WM_CURSOR_BOTH_HANDLES; + } + else if (selection.handle == SEQ_HANDLE_LEFT) { + wmcursor = WM_CURSOR_LEFT_HANDLE; + } + else if (selection.handle == SEQ_HANDLE_RIGHT) { + wmcursor = WM_CURSOR_RIGHT_HANDLE; + } + + WM_cursor_set(win, wmcursor); +} + /* *********************** header region ************************ */ /* Add handlers, stuff you only do once or on area/region changes. */ static void sequencer_header_region_init(wmWindowManager * /*wm*/, ARegion *region) @@ -1014,6 +1102,9 @@ void ED_spacetype_sequencer() art->message_subscribe = sequencer_main_region_message_subscribe; art->keymapflag = ED_KEYMAP_TOOL | ED_KEYMAP_GIZMO | ED_KEYMAP_VIEW2D | ED_KEYMAP_FRAMES | ED_KEYMAP_ANIMATION; + art->cursor = sequencer_main_cursor; + art->event_cursor = true; + art->clip_gizmo_events_by_ui = true; BLI_addhead(&st->regiontypes, art); /* Preview. */ diff --git a/source/blender/editors/transform/transform_convert_sequencer.cc b/source/blender/editors/transform/transform_convert_sequencer.cc index c0bd93d0e3f..efb274cfadd 100644 --- a/source/blender/editors/transform/transform_convert_sequencer.cc +++ b/source/blender/editors/transform/transform_convert_sequencer.cc @@ -31,6 +31,7 @@ #include "transform.hh" #include "transform_convert.hh" +#include "transform_mode.hh" #define SEQ_EDGE_PAN_INSIDE_PAD 3.5 #define SEQ_EDGE_PAN_OUTSIDE_PAD 0 /* Disable clamping for panning, use whole screen. */ @@ -671,13 +672,24 @@ static void recalcData_sequencer(TransInfo *t) static void special_aftertrans_update__sequencer(bContext * /*C*/, TransInfo *t) { + SpaceSeq *sseq = (SpaceSeq *)t->area->spacedata.first; + if ((sseq->flag & SPACE_SEQ_DESELECT_STRIP_HANDLE) != 0 && + transform_mode_edge_seq_slide_use_restore_handle_selection(t)) + { + TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t); + blender::VectorSet strips = seq_transform_collection_from_transdata(tc); + for (Sequence *seq : strips) { + seq->flag &= ~(SEQ_LEFTSEL | SEQ_RIGHTSEL); + } + } + + sseq->flag &= ~SPACE_SEQ_DESELECT_STRIP_HANDLE; + + /* #freeSeqData in `transform_conversions.cc` does this + * keep here so the else at the end won't run. */ if (t->state == TRANS_CANCEL) { return; } - /* #freeSeqData in `transform_conversions.cc` 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. */ diff --git a/source/blender/editors/transform/transform_input.cc b/source/blender/editors/transform/transform_input.cc index 5e8036d8ed2..00bca595faa 100644 --- a/source/blender/editors/transform/transform_input.cc +++ b/source/blender/editors/transform/transform_input.cc @@ -10,6 +10,8 @@ #include #include "DNA_screen_types.h" +#include "DNA_sequence_types.h" +#include "DNA_space_types.h" #include "BKE_context.hh" @@ -22,6 +24,11 @@ #include "transform.hh" #include "transform_mode.hh" +#include "ED_sequencer.hh" + +#include "SEQ_sequencer.hh" +#include "SEQ_time.hh" + #include "MEM_guardedalloc.h" using namespace blender; @@ -288,6 +295,60 @@ static void calcSpringFactor(MouseInput *mi) } } +static int transform_seq_slide_strip_cursor_get(const Sequence *seq) +{ + if ((seq->flag & SEQ_LEFTSEL) != 0) { + return WM_CURSOR_LEFT_HANDLE; + } + if ((seq->flag & SEQ_RIGHTSEL) != 0) { + return WM_CURSOR_RIGHT_HANDLE; + } + return WM_CURSOR_NSEW_SCROLL; +} + +static int transform_seq_slide_cursor_get(TransInfo *t) +{ + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + return WM_CURSOR_NSEW_SCROLL; + } + + const Scene *scene = t->scene; + blender::VectorSet strips = ED_sequencer_selected_strips_from_context(t->context); + + if (strips.size() == 1) { + return transform_seq_slide_strip_cursor_get(strips[0]); + } + if (strips.size() == 2) { + Sequence *seq1 = strips[0]; + Sequence *seq2 = strips[1]; + + if (SEQ_time_left_handle_frame_get(scene, seq1) > SEQ_time_left_handle_frame_get(scene, seq2)) + { + SWAP(Sequence *, seq1, seq2); + } + + if (seq1->machine != seq2->machine) { + return WM_CURSOR_NSEW_SCROLL; + } + + const Scene *scene = t->scene; + if (SEQ_time_right_handle_frame_get(scene, seq1) != + SEQ_time_left_handle_frame_get(scene, seq2)) + { + return WM_CURSOR_NSEW_SCROLL; + } + + const int cursor1 = transform_seq_slide_strip_cursor_get(seq1); + const int cursor2 = transform_seq_slide_strip_cursor_get(seq2); + + if (cursor1 == WM_CURSOR_RIGHT_HANDLE && cursor2 == WM_CURSOR_LEFT_HANDLE) { + return WM_CURSOR_BOTH_HANDLES; + } + } + + return WM_CURSOR_NSEW_SCROLL; +} + void initMouseInputMode(TransInfo *t, MouseInput *mi, MouseInputMode mode) { /* In case we allocate a new value. */ @@ -383,6 +444,19 @@ void initMouseInputMode(TransInfo *t, MouseInput *mi, MouseInputMode mode) t->flag |= T_MODAL_CURSOR_SET; WM_cursor_modal_set(win, WM_CURSOR_NSEW_SCROLL); } + /* Only use special cursor, when tweaking strips with mouse. */ + if (t->mode == TFM_SEQ_SLIDE) { + if (transform_mode_edge_seq_slide_use_restore_handle_selection(t)) { + WM_cursor_modal_set(win, transform_seq_slide_cursor_get(t)); + } + else { + SpaceSeq *sseq = CTX_wm_space_seq(t->context); + if (sseq != nullptr) { + sseq->flag &= ~SPACE_SEQ_DESELECT_STRIP_HANDLE; + } + } + } + break; case HLP_SPRING: case HLP_ANGLE: diff --git a/source/blender/editors/transform/transform_mode.hh b/source/blender/editors/transform/transform_mode.hh index 09229713e4d..fe5001b4c0d 100644 --- a/source/blender/editors/transform/transform_mode.hh +++ b/source/blender/editors/transform/transform_mode.hh @@ -140,6 +140,7 @@ extern TransModeInfo TransMode_rotatenormal; /* `transform_mode_edge_seq_slide.cc` */ extern TransModeInfo TransMode_seqslide; +bool transform_mode_edge_seq_slide_use_restore_handle_selection(const TransInfo *t); /* `transform_mode_edge_slide.cc` */ diff --git a/source/blender/editors/transform/transform_mode_edge_seq_slide.cc b/source/blender/editors/transform/transform_mode_edge_seq_slide.cc index edf159f5137..d765507149b 100644 --- a/source/blender/editors/transform/transform_mode_edge_seq_slide.cc +++ b/source/blender/editors/transform/transform_mode_edge_seq_slide.cc @@ -17,6 +17,8 @@ #include "ED_screen.hh" +#include "RNA_access.hh" + #include "WM_api.hh" #include "WM_types.hh" @@ -102,8 +104,20 @@ static void applySeqSlide(TransInfo *t) ED_area_status_text(t->area, str); } -static void initSeqSlide(TransInfo *t, wmOperator * /*op*/) +struct SeqSlideParams { + bool use_restore_handle_selection; +}; + +static void initSeqSlide(TransInfo *t, wmOperator *op) { + SeqSlideParams *ssp = MEM_cnew(__func__); + t->custom.mode.data = ssp; + t->custom.mode.use_free = true; + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_restore_handle_selection"); + if (op != nullptr && prop != nullptr) { + ssp->use_restore_handle_selection = RNA_property_boolean_get(op->ptr, prop); + } + initMouseInputMode(t, &t->mouse, INPUT_VECTOR); t->idx_max = 1; @@ -119,11 +133,18 @@ static void initSeqSlide(TransInfo *t, wmOperator * /*op*/) * (supporting frames in addition to "natural" time...). */ t->num.unit_type[0] = B_UNIT_NONE; t->num.unit_type[1] = B_UNIT_NONE; +} - if (t->keymap) { - /* Workaround to use the same key as the modal keymap. */ - t->custom.mode.data = (void *)WM_modalkeymap_find_propvalue(t->keymap, TFM_MODAL_TRANSLATE); +bool transform_mode_edge_seq_slide_use_restore_handle_selection(const TransInfo *t) +{ + if ((U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0) { + return false; } + SeqSlideParams *ssp = static_cast(t->custom.mode.data); + if (ssp == nullptr) { + return false; + } + return ssp->use_restore_handle_selection; } /** \} */ diff --git a/source/blender/editors/transform/transform_ops.cc b/source/blender/editors/transform/transform_ops.cc index 164d825b70b..48f55ca7836 100644 --- a/source/blender/editors/transform/transform_ops.cc +++ b/source/blender/editors/transform/transform_ops.cc @@ -1349,6 +1349,13 @@ static void TRANSFORM_OT_seq_slide(wmOperatorType *ot) ot->srna, "value", 2, nullptr, -FLT_MAX, FLT_MAX, "Offset", "", -FLT_MAX, FLT_MAX); RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, 0); + prop = RNA_def_boolean(ot->srna, + "use_restore_handle_selection", + false, + "Restore Handle Selection", + "Restore handle selection after tweaking"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + WM_operatortype_props_advanced_begin(ot); Transform_Properties(ot, P_SNAP | P_VIEW2D_EDGE_PAN); diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index d03a50a7197..00c7c153e9a 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -731,7 +731,7 @@ typedef enum eSpaceSeq_Flag { SEQ_MARKER_TRANS = (1 << 1), SEQ_DRAW_COLOR_SEPARATED_UNUSED_2 = (1 << 2), SEQ_CLAMP_VIEW = (1 << 3), - SPACE_SEQ_FLAG_UNUSED_4 = (1 << 4), + SPACE_SEQ_DESELECT_STRIP_HANDLE = (1 << 4), SPACE_SEQ_FLAG_UNUSED_5 = (1 << 5), SEQ_USE_ALPHA = (1 << 6), /* use RGBA display mode for preview */ SPACE_SEQ_FLAG_UNUSED_10 = (1 << 10), diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index de2b82a084e..8b670da63d3 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -1071,7 +1071,7 @@ typedef struct UserDef { /** Pie menu distance from center before a direction is set. */ short pie_menu_threshold; - short _pad6[2]; + int sequencer_editor_flag; /* eUserpref_SeqEditorFlags */ char factor_display_type; @@ -1567,6 +1567,10 @@ typedef enum eUserpref_SeqProxySetup { USER_SEQ_PROXY_SETUP_AUTOMATIC = 1, } eUserpref_SeqProxySetup; +typedef enum eUserpref_SeqEditorFlags { + USER_SEQ_ED_SIMPLE_TWEAKING = (1 << 0), +} eUserpref_SeqEditorFlags; + /* Locale Ids. Auto will try to get local from OS. Our default is English though. */ /** #UserDef.language */ enum { diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index b16247e28b7..7288f5ee8a6 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -5687,6 +5687,13 @@ static void rna_def_userdef_edit(BlenderRNA *brna) RNA_def_property_array(prop, 3); RNA_def_property_ui_text(prop, "Sculpt/Paint Overlay Color", "Color of texture overlay"); + /* VSE */ + prop = RNA_def_property(srna, "use_sequencer_simplified_tweaking", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "sequencer_editor_flag", USER_SEQ_ED_SIMPLE_TWEAKING); + RNA_def_property_ui_text( + prop, "Tweak Handles", "Allows dragging handles without selecting them first"); + /* duplication linking */ prop = RNA_def_property(srna, "use_duplicate_mesh", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "dupflag", USER_DUP_MESH); diff --git a/source/blender/windowmanager/intern/wm_cursors.cc b/source/blender/windowmanager/intern/wm_cursors.cc index 4bb4a662db4..4ba9ca159bc 100644 --- a/source/blender/windowmanager/intern/wm_cursors.cc +++ b/source/blender/windowmanager/intern/wm_cursors.cc @@ -1181,5 +1181,83 @@ void wm_init_cursor_data() BlenderCursor[WM_CURSOR_PICK_AREA] = &PickAreaCursor; END_CURSOR_BLOCK; + /********************** Right handle cursor ***********************/ + BEGIN_CURSOR_BLOCK; + + static char right_handle_bitmap[] = { + 0x00, 0x00, 0xfc, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, + 0x11, 0xc0, 0x31, 0xc0, 0x71, 0xc0, 0x71, 0xc0, 0x31, 0xc0, 0x11, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xfc, 0x01, 0x00, 0x00, + }; + + static char right_handle_mask[] = { + 0xfc, 0x03, 0xfc, 0x03, 0xfc, 0x03, 0xe0, 0x0b, 0xe0, 0x1b, 0xe0, + 0x3b, 0xe0, 0x7b, 0xe0, 0xfb, 0xe0, 0xfb, 0xe0, 0x7b, 0xe0, 0x3b, + 0xe0, 0x1b, 0xe0, 0x0b, 0xfc, 0x03, 0xfc, 0x03, 0xfc, 0x03, + }; + + static BCursor RightHandleCursor = { + right_handle_bitmap, + right_handle_mask, + 7, + 7, + false, + }; + + BlenderCursor[WM_CURSOR_RIGHT_HANDLE] = &RightHandleCursor; + END_CURSOR_BLOCK; + + /********************** Left handle cursor ***********************/ + BEGIN_CURSOR_BLOCK; + + static char left_handle_bitmap[] = { + 0x00, 0x00, 0x80, 0x3f, 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x88, + 0x03, 0x8c, 0x03, 0x8e, 0x03, 0x8e, 0x03, 0x8c, 0x03, 0x88, 0x03, + 0x80, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x3f, 0x00, 0x00, + }; + + static char left_handle_mask[] = { + 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x07, 0xd8, 0x07, 0xdc, + 0x07, 0xde, 0x07, 0xdf, 0x07, 0xdf, 0x07, 0xde, 0x07, 0xdc, 0x07, + 0xd8, 0x07, 0xc0, 0x07, 0xc0, 0x3f, 0xc0, 0x3f, 0xc0, 0x3f, + }; + + static BCursor LeftHandleCursor = { + left_handle_bitmap, + left_handle_mask, + 7, + 7, + false, + }; + + BlenderCursor[WM_CURSOR_LEFT_HANDLE] = &LeftHandleCursor; + END_CURSOR_BLOCK; + + /********************** both handles cursor ***********************/ + BEGIN_CURSOR_BLOCK; + + static char both_handles_bitmap[] = { + 0x00, 0x00, 0x3f, 0xfc, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x38, + 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, + 0x38, 0x1c, 0x38, 0x1c, 0x38, 0x1c, 0x3f, 0xfc, 0x00, 0x00, + }; + + static char both_handles_mask[] = { + 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, + 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, + 0x7c, 0x3e, 0x7c, 0x3e, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, + }; + + static BCursor BothHandlesCursor = { + both_handles_bitmap, + both_handles_mask, + 7, + 7, + false, + }; + + BlenderCursor[WM_CURSOR_BOTH_HANDLES] = &BothHandlesCursor; + END_CURSOR_BLOCK; + /********************** Put the cursors in the array ***********************/ } diff --git a/source/blender/windowmanager/wm_cursors.hh b/source/blender/windowmanager/wm_cursors.hh index 8eea7d26006..c689420bf4d 100644 --- a/source/blender/windowmanager/wm_cursors.hh +++ b/source/blender/windowmanager/wm_cursors.hh @@ -57,6 +57,10 @@ enum WMCursorType { WM_CURSOR_PICK_AREA, + WM_CURSOR_LEFT_HANDLE, + WM_CURSOR_RIGHT_HANDLE, + WM_CURSOR_BOTH_HANDLES, + /* --- ALWAYS LAST ----- */ WM_CURSOR_NUM, };