diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 86cd3e5ef46..91b90191a74 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3246,6 +3246,10 @@ def km_sequencer_preview(params): {"properties": [("unselected", False)]}), ("sequencer.delete", {"type": 'X', "value": 'PRESS'}, None), ("sequencer.delete", {"type": 'DEL', "value": 'PRESS'}, None), + ("sequencer.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "ctrl": True}, + {"properties": [("mode", 'ADD')]}), + ("sequencer.select_lasso", {"type": params.action_mouse, "value": 'CLICK_DRAG', "shift": True, "ctrl": True}, + {"properties": [("mode", 'SUB')]}), ("sequencer.copy", {"type": 'C', "value": 'PRESS', "ctrl": True}, None), ("sequencer.paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None), ("sequencer.paste", {"type": 'V', "value": 'PRESS', "ctrl": True, "shift": True}, @@ -8451,6 +8455,19 @@ def km_sequencer_tool_generic_select_box(params, *, fallback): ) +def km_sequencer_tool_generic_select_lasso(params, *, fallback): + return ( + _fallback_id("Sequencer Tool: Select Lasso", fallback), + {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( + "sequencer.select_lasso", + **(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else + params.tool_tweak_event))), + ]}, + ) + + def km_sequencer_tool_generic_select_circle(params, *, fallback): return ( _fallback_id("Sequencer Tool: Select Circle", fallback), @@ -8498,6 +8515,19 @@ def km_sequencer_preview_tool_generic_select_box(params, *, fallback): ) +def km_sequencer_preview_tool_generic_select_lasso(params, *, fallback): + return ( + _fallback_id("Preview Tool: Select Lasso", fallback), + {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, + {"items": [ + *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( + "sequencer.select_lasso", + **(params.select_tweak_event if (fallback and params.use_fallback_tool_select_mouse) else + params.tool_tweak_event))), + ]}, + ) + + def km_sequencer_preview_tool_generic_select_circle(params, *, fallback): return ( _fallback_id("Preview Tool: Select Circle", fallback), @@ -8897,6 +8927,10 @@ def generate_keymaps(params=None): for fallback in (False, True)), *(km_sequencer_preview_tool_generic_select_box(params, fallback=fallback) for fallback in (False, True)), + *(km_sequencer_tool_generic_select_lasso(params, fallback=fallback) + for fallback in (False, True)), + *(km_sequencer_preview_tool_generic_select_lasso(params, fallback=fallback) + for fallback in (False, True)), *(km_sequencer_preview_tool_generic_select_circle(params, fallback=fallback) for fallback in (False, True)), *(km_sequencer_tool_generic_select_circle(params, fallback=fallback) diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 96d2fbe0328..cbb7d8e9cc2 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -3272,6 +3272,38 @@ class _defs_sequencer_select: draw_settings=draw_settings, ) + @ToolDef.from_fn + def lasso_timeline(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("sequencer.select_lasso") + row = layout.row() + row.use_property_split = False + row.prop(props, "mode", text="", expand=True, icon_only=True) + return dict( + idname="sequencer.select_lasso", + label="Select Lasso", + icon="ops.generic.select_lasso", + widget=None, + keymap="Sequencer Tool: Select Lasso", + draw_settings=draw_settings, + ) + + @ToolDef.from_fn + def lasso_preview(): + def draw_settings(_context, layout, tool): + props = tool.operator_properties("sequencer.select_lasso") + row = layout.row() + row.use_property_split = False + row.prop(props, "mode", text="", expand=True, icon_only=True) + return dict( + idname="sequencer.select_lasso", + label="Select Lasso", + icon="ops.generic.select_lasso", + widget=None, + keymap="Preview Tool: Select Lasso", + draw_settings=draw_settings, + ) + @ToolDef.from_fn def circle_timeline(): def draw_settings(_context, layout, tool): @@ -4006,6 +4038,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel): ( _defs_sequencer_select.select_preview, _defs_sequencer_select.box_preview, + _defs_sequencer_select.lasso_preview, _defs_sequencer_select.circle_preview, ), _defs_sequencer_generic.cursor, @@ -4021,6 +4054,7 @@ class SEQUENCER_PT_tools_active(ToolSelectPanelHelper, Panel): 'SEQUENCER': [ ( _defs_sequencer_select.box_timeline, + _defs_sequencer_select.lasso_timeline, _defs_sequencer_select.circle_timeline, ), _defs_sequencer_generic.blade, diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index 0333b5ca664..6b822af2707 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -274,6 +274,7 @@ void SEQUENCER_OT_select_linked_pick(wmOperatorType *ot); void SEQUENCER_OT_select_handles(wmOperatorType *ot); void SEQUENCER_OT_select_side(wmOperatorType *ot); void SEQUENCER_OT_select_box(wmOperatorType *ot); +void SEQUENCER_OT_select_lasso(wmOperatorType *ot); void SEQUENCER_OT_select_circle(wmOperatorType *ot); void SEQUENCER_OT_select_inverse(wmOperatorType *ot); void SEQUENCER_OT_select_grouped(wmOperatorType *ot); diff --git a/source/blender/editors/space_sequencer/sequencer_ops.cc b/source/blender/editors/space_sequencer/sequencer_ops.cc index 2daa7d0811a..58b746de6d3 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.cc +++ b/source/blender/editors/space_sequencer/sequencer_ops.cc @@ -105,6 +105,7 @@ void sequencer_operatortypes() WM_operatortype_append(SEQUENCER_OT_select_side); WM_operatortype_append(SEQUENCER_OT_select_side_of_frame); WM_operatortype_append(SEQUENCER_OT_select_box); + WM_operatortype_append(SEQUENCER_OT_select_lasso); WM_operatortype_append(SEQUENCER_OT_select_circle); WM_operatortype_append(SEQUENCER_OT_select_grouped); diff --git a/source/blender/editors/space_sequencer/sequencer_select.cc b/source/blender/editors/space_sequencer/sequencer_select.cc index c79eb5ba178..cda2671d85f 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.cc +++ b/source/blender/editors/space_sequencer/sequencer_select.cc @@ -10,6 +10,7 @@ #include #include +#include "BLI_lasso_2d.hh" #include "BLI_rect.h" #include "MEM_guardedalloc.h" @@ -2325,7 +2326,176 @@ void SEQUENCER_OT_select_box(wmOperatorType *ot) "Select strips individually whether or not they are connected"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } +/** \} */ +/* -------------------------------------------------------------------- */ +/** \name Lasso Select Operator + * \{ */ +static bool do_lasso_select_is_origin_inside(const ARegion *region, + const rcti *clip_rect, + const Span mcoords, + const float co_test[2]) +{ + int co_screen[2]; + if (UI_view2d_view_to_region_clip( + ®ion->v2d, co_test[0], co_test[1], &co_screen[0], &co_screen[1]) && + BLI_rcti_isect_pt_v(clip_rect, co_screen) && + BLI_lasso_is_point_inside(mcoords, co_screen[0], co_screen[1], V2D_IS_CLIPPED)) + { + return true; + } + return false; +} + +static bool rcti_in_lasso(const rcti rect, const Span mcoords) +{ + rcti lasso_rect; + BLI_lasso_boundbox(&lasso_rect, mcoords); + /* Check if edge of strip is in the lasso. */ + if (BLI_lasso_is_edge_inside( + mcoords, rect.xmin, rect.ymin, rect.xmax, rect.ymin, V2D_IS_CLIPPED) || + BLI_lasso_is_edge_inside( + mcoords, rect.xmax, rect.ymin, rect.xmax, rect.ymax, V2D_IS_CLIPPED) || + BLI_lasso_is_edge_inside( + mcoords, rect.xmax, rect.ymax, rect.xmin, rect.ymax, V2D_IS_CLIPPED) || + BLI_lasso_is_edge_inside( + mcoords, rect.xmin, rect.ymax, rect.xmin, rect.ymin, V2D_IS_CLIPPED)) + { + return true; + } + + /* Check if lasso is in the strip rect. Used when the lasso is only inside one strip. */ + if (BLI_rcti_inside_rcti(&rect, &lasso_rect)) { + return true; + } + return false; +} + +static bool do_lasso_select_timeline(bContext *C, + const Span mcoords, + ARegion *region, + const eSelectOp sel_op) +{ + Scene *scene = CTX_data_scene(C); + Editing *ed = seq::editing_get(scene); + + bool changed = false; + const bool select = (sel_op != SEL_OP_SUB); + + LISTBASE_FOREACH (Strip *, strip, &ed->seqbase) { + rctf strip_rct; + rcti region_rct; + strip_rectf(scene, strip, &strip_rct); + UI_view2d_view_to_region_clip( + ®ion->v2d, strip_rct.xmin, strip_rct.ymin, ®ion_rct.xmin, ®ion_rct.ymin); + UI_view2d_view_to_region_clip( + ®ion->v2d, strip_rct.xmax, strip_rct.ymax, ®ion_rct.xmax, ®ion_rct.ymax); + + if (rcti_in_lasso(region_rct, mcoords)) { + SET_FLAG_FROM_TEST(strip->flag, select, SELECT); + changed = true; + } + } + return changed; +} + +static bool do_lasso_select_preview(bContext *C, + Editing *ed, + const Span mcoords, + const eSelectOp sel_op) +{ + Scene *scene = CTX_data_scene(C); + const ARegion *region = CTX_wm_region(C); + + bool changed = false; + rcti rect; + BLI_lasso_boundbox(&rect, mcoords); + + ListBase *seqbase = seq::active_seqbase_get(ed); + ListBase *channels = seq::channels_displayed_get(ed); + SpaceSeq *sseq = CTX_wm_space_seq(C); + + blender::VectorSet strips = seq::query_rendered_strips( + scene, channels, seqbase, scene->r.cfra, sseq->chanshown); + for (Strip *strip : strips) { + float2 origin = seq::image_transform_origin_offset_pixelspace_get(scene, strip); + if (do_lasso_select_is_origin_inside(region, &rect, mcoords, origin)) { + changed = true; + if (ELEM(sel_op, SEL_OP_ADD, SEL_OP_SET)) { + strip->flag |= SELECT; + } + else { + BLI_assert(sel_op == SEL_OP_SUB); + strip->flag &= ~SELECT; + } + } + } + + return changed; +} + +static wmOperatorStatus vse_lasso_select_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + Array mcoords = WM_gesture_lasso_path_to_array(C, op); + Editing *ed = seq::editing_get(scene); + + if (ed == nullptr) { + return OPERATOR_CANCELLED; + } + + if (mcoords.is_empty()) { + return OPERATOR_PASS_THROUGH; + } + + const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode")); + const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op); + bool changed = false; + + if (use_pre_deselect) { + changed |= deselect_all_strips(scene); + } + + if (region->regiontype == RGN_TYPE_PREVIEW) { + changed = do_lasso_select_preview(C, ed, mcoords, sel_op); + } + else { + changed = do_lasso_select_timeline(C, mcoords, region, sel_op); + } + + if (changed) { + sequencer_select_do_updates(C, scene); + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; +} + +void SEQUENCER_OT_select_lasso(wmOperatorType *ot) +{ + ot->name = "Lasso Select"; + ot->description = "Select strips using lasso selection"; + ot->idname = "SEQUENCER_OT_select_lasso"; + + ot->invoke = WM_gesture_lasso_invoke; + ot->modal = WM_gesture_lasso_modal; + ot->exec = vse_lasso_select_exec; + ot->poll = ED_operator_sequencer_active; + ot->cancel = WM_gesture_lasso_cancel; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; + + /* properties */ + WM_operator_properties_gesture_lasso(ot); + WM_operator_properties_select_operation_simple(ot); +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Circle Select Operator + * \{ */ static bool strip_circle_select_radius_image_isect(const Scene *scene, const Strip *strip, const int *radius, diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 71878863484..89d9b6cbb68 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -4402,6 +4402,7 @@ static void gesture_lasso_modal_keymap(wmKeyConfig *keyconf) WM_modalkeymap_assign(keymap, "GRAPH_OT_select_lasso"); WM_modalkeymap_assign(keymap, "NODE_OT_select_lasso"); WM_modalkeymap_assign(keymap, "UV_OT_select_lasso"); + WM_modalkeymap_assign(keymap, "SEQUENCER_OT_select_lasso"); WM_modalkeymap_assign(keymap, "PAINT_OT_hide_show_lasso_gesture"); WM_modalkeymap_assign(keymap, "GREASE_PENCIL_OT_erase_lasso"); }