diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 7f34593e2b8..96d58dbd29e 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -3463,7 +3463,9 @@ def km_animation_channels(params): items.extend([ # Click select. ("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), - ("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, + ("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True}, + {"properties": [("extend_range", True)]}), + ("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True}, {"properties": [("extend", True)]}), ("anim.channels_click", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "ctrl": True}, {"properties": [("children_only", True)]}), diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index 1b55da28ef4..b99dc79d63e 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -214,7 +214,64 @@ void ANIM_set_active_channel(bAnimContext *ac, ANIM_animdata_freelist(&anim_data); } -static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, bAnimListElem *ale) +bool ANIM_is_active_channel(bAnimListElem *ale) +{ + switch (ale->type) { + case ANIMTYPE_FILLACTD: /* Action Expander */ + case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */ + case ANIMTYPE_DSLAM: + case ANIMTYPE_DSCAM: + case ANIMTYPE_DSCACHEFILE: + case ANIMTYPE_DSCUR: + case ANIMTYPE_DSSKEY: + case ANIMTYPE_DSWOR: + case ANIMTYPE_DSPART: + case ANIMTYPE_DSMBALL: + case ANIMTYPE_DSARM: + case ANIMTYPE_DSMESH: + case ANIMTYPE_DSNTREE: + case ANIMTYPE_DSTEX: + case ANIMTYPE_DSLAT: + case ANIMTYPE_DSLINESTYLE: + case ANIMTYPE_DSSPK: + case ANIMTYPE_DSGPENCIL: + case ANIMTYPE_DSMCLIP: + case ANIMTYPE_DSHAIR: + case ANIMTYPE_DSPOINTCLOUD: + case ANIMTYPE_DSVOLUME: + case ANIMTYPE_NLAACTION: + case ANIMTYPE_DSSIMULATION: { + if (ale->adt) { + return ale->adt->flag & ADT_UI_ACTIVE; + } + } + case ANIMTYPE_GROUP: { + bActionGroup *argp = (bActionGroup *)ale->data; + return argp->flag & AGRP_ACTIVE; + } + case ANIMTYPE_FCURVE: + case ANIMTYPE_NLACURVE: { + FCurve *fcu = (FCurve *)ale->data; + return fcu->flag & FCURVE_ACTIVE; + } + case ANIMTYPE_GPLAYER: { + bGPDlayer *gpl = (bGPDlayer *)ale->data; + return gpl->flag & GP_LAYER_ACTIVE; + } + /* These channel types do not have active flags. */ + case ANIMTYPE_MASKLAYER: + case ANIMTYPE_SHAPEKEY: + break; + } + return false; +} + +/* change_active determines whether to change the active bone of the armature when selecting pose + * channels. It is false during range selection otherwise true. */ +static void select_pchan_for_action_group(bAnimContext *ac, + bActionGroup *agrp, + bAnimListElem *ale, + const bool change_active) { /* Armatures-Specific Feature: * See mouse_anim_channels() -> ANIMTYPE_GROUP case for more details (#38737) @@ -230,11 +287,12 @@ static void select_pchan_for_action_group(bAnimContext *ac, bActionGroup *agrp, * TODO: check the first F-Curve or so to be sure... */ bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, agrp->name); + if (agrp->flag & AGRP_SELECTED) { - ED_pose_bone_select(ob, pchan, true); + ED_pose_bone_select(ob, pchan, true, change_active); } else { - ED_pose_bone_select(ob, pchan, false); + ED_pose_bone_select(ob, pchan, false, change_active); } } } @@ -342,10 +400,15 @@ static void anim_channels_select_set(bAnimContext *ac, eAnimChannels_SetFlag sel) { bAnimListElem *ale; + /* Boolean to keep active channel status during range selection. */ + const bool change_active = (sel != ACHANNEL_SETFLAG_EXTEND_RANGE); for (ale = anim_data.first; ale; ale = ale->next) { switch (ale->type) { case ANIMTYPE_SCENE: { + if (change_active) { + break; + } Scene *scene = (Scene *)ale->data; ACHANNEL_SET_FLAG(scene, sel, SCE_DS_SELECTED); @@ -372,8 +435,10 @@ static void anim_channels_select_set(bAnimContext *ac, case ANIMTYPE_GROUP: { bActionGroup *agrp = (bActionGroup *)ale->data; ACHANNEL_SET_FLAG(agrp, sel, AGRP_SELECTED); - select_pchan_for_action_group(ac, agrp, ale); - agrp->flag &= ~AGRP_ACTIVE; + select_pchan_for_action_group(ac, agrp, ale, change_active); + if (change_active) { + agrp->flag &= ~AGRP_ACTIVE; + } break; } case ANIMTYPE_FCURVE: @@ -381,7 +446,7 @@ static void anim_channels_select_set(bAnimContext *ac, FCurve *fcu = (FCurve *)ale->data; ACHANNEL_SET_FLAG(fcu, sel, FCURVE_SELECTED); - if ((fcu->flag & FCURVE_SELECTED) == 0) { + if (!(fcu->flag & FCURVE_SELECTED) && change_active) { /* Only erase the ACTIVE flag when deselecting. This ensures that "select all curves" * retains the currently active curve. */ fcu->flag &= ~FCURVE_ACTIVE; @@ -428,7 +493,9 @@ static void anim_channels_select_set(bAnimContext *ac, /* need to verify that this data is valid for now */ if (ale->adt) { ACHANNEL_SET_FLAG(ale->adt, sel, ADT_UI_SELECTED); - ale->adt->flag &= ~ADT_UI_ACTIVE; + if (change_active) { + ale->adt->flag &= ~ADT_UI_ACTIVE; + } } break; } @@ -2747,7 +2814,7 @@ static void box_select_anim_channels(bAnimContext *ac, rcti *rect, short selectm switch (ale->type) { case ANIMTYPE_GROUP: { bActionGroup *agrp = (bActionGroup *)ale->data; - select_pchan_for_action_group(ac, agrp, ale); + select_pchan_for_action_group(ac, agrp, ale, true); /* always clear active flag after doing this */ agrp->flag &= ~AGRP_ACTIVE; break; @@ -3027,6 +3094,70 @@ static int click_select_channel_scene(bAnimListElem *ale, return (ND_ANIMCHAN | NA_SELECTED); } +/* Return whether active channel of given type is present. */ +static bool animchannel_has_active_of_type(bAnimContext *ac, const eAnim_ChannelType type) +{ + ListBase anim_data = anim_channels_for_selection(ac); + bool is_active_found = false; + + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + if (ale->type != type) { + continue; + } + is_active_found = ANIM_is_active_channel(ale); + if (is_active_found) { + break; + } + } + + ANIM_animdata_freelist(&anim_data); + return is_active_found; +} + +/* Select channels that lies between active channel and cursor_elem. */ +static void animchannel_select_range(bAnimContext *ac, bAnimListElem *cursor_elem) +{ + ListBase anim_data = anim_channels_for_selection(ac); + bool in_selection_range = false; + + LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) { + + /* Allow selection when active channel and `cursor_elem` are of same type. */ + if (ale->type != cursor_elem->type) { + continue; + } + + const bool is_cursor_elem = (ale->data == cursor_elem->data); + const bool is_active_elem = ANIM_is_active_channel(ale); + + /* Restrict selection when active element is not found and group-channels are excluded from the + * selection. */ + if (is_active_elem || is_cursor_elem) { + /* Select first and last element from the range. Reverse selection status on extremes. */ + ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD); + in_selection_range = !in_selection_range; + if (ale->type == ANIMTYPE_GROUP) { + select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false); + } + } + else if (in_selection_range) { + /* Select elements between the range. */ + ANIM_channel_setting_set(ac, ale, ACHANNEL_SETTING_SELECT, ACHANNEL_SETFLAG_ADD); + if (ale->type == ANIMTYPE_GROUP) { + select_pchan_for_action_group(ac, (bActionGroup *)ale->data, ale, false); + } + } + + if (is_active_elem && is_cursor_elem) { + /* Selection range is only one element when active channel and clicked channel are same. So + * exit out of the loop when this condition is hit. */ + break; + } + } + + ANIM_animdata_freelist(&anim_data); +} + static int click_select_channel_object(bContext *C, bAnimContext *ac, bAnimListElem *ale, @@ -3050,8 +3181,13 @@ static int click_select_channel_object(bContext *C, adt->flag ^= ADT_UI_SELECTED; } } + else if (selectmode == SELECT_EXTEND_RANGE) { + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); + animchannel_select_range(ac, ale); + } else { /* deselect all */ + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); BKE_view_layer_synced_ensure(scene, view_layer); /* TODO: should this deselect all other types of channels too? */ LISTBASE_FOREACH (Base *, b, BKE_view_layer_object_bases_get(view_layer)) { @@ -3074,7 +3210,8 @@ static int click_select_channel_object(bContext *C, * to avoid getting stuck there, see: #48747. */ ED_object_base_activate_with_mode_exit_if_needed(C, base); /* adds notifier */ - if ((adt) && (adt->flag & ADT_UI_SELECTED)) { + /* Similar to outliner, do not change active element when selecting elements in range.*/ + if ((adt) && (adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) { adt->flag |= ADT_UI_ACTIVE; } @@ -3094,14 +3231,18 @@ static int click_select_channel_dummy(bAnimContext *ac, /* inverse selection status of this AnimData block only */ ale->adt->flag ^= ADT_UI_SELECTED; } + else if (selectmode == SELECT_EXTEND_RANGE) { + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); + animchannel_select_range(ac, ale); + } else { /* select AnimData block by itself */ ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); ale->adt->flag |= ADT_UI_SELECTED; } - /* set active? */ - if (ale->adt->flag & ADT_UI_SELECTED) { + /* Similar to outliner, do not change active element when selecting elements in range. */ + if ((ale->adt->flag & ADT_UI_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) { ale->adt->flag |= ADT_UI_ACTIVE; } @@ -3147,6 +3288,10 @@ static int click_select_channel_group(bAnimContext *ac, /* inverse selection status of this group only */ agrp->flag ^= AGRP_SELECTED; } + else if (selectmode == SELECT_EXTEND_RANGE) { + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); + animchannel_select_range(ac, ale); + } else if (selectmode == -1) { /* select all in group (and deselect everything else) */ FCurve *fcu; @@ -3173,17 +3318,22 @@ static int click_select_channel_group(bAnimContext *ac, agrp->flag |= AGRP_SELECTED; } - /* if group is selected now, make group the 'active' one in the visible list */ + /* if group is selected now, make group the 'active' one in the visible list. + * Similar to outliner, do not change active element when selecting elements in range. */ if (agrp->flag & AGRP_SELECTED) { - ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, agrp, ANIMTYPE_GROUP); - if (pchan) { - ED_pose_bone_select(ob, pchan, true); + if (selectmode != SELECT_EXTEND_RANGE) { + ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, agrp, ANIMTYPE_GROUP); + if (pchan) { + ED_pose_bone_select(ob, pchan, true, true); + } } } else { - ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, NULL, ANIMTYPE_GROUP); - if (pchan) { - ED_pose_bone_select(ob, pchan, false); + if (selectmode != SELECT_EXTEND_RANGE) { + ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, NULL, ANIMTYPE_GROUP); + if (pchan) { + ED_pose_bone_select(ob, pchan, false, true); + } } } @@ -3202,14 +3352,19 @@ static int click_select_channel_fcurve(bAnimContext *ac, /* inverse selection status of this F-Curve only */ fcu->flag ^= FCURVE_SELECTED; } + else if (selectmode == SELECT_EXTEND_RANGE) { + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); + animchannel_select_range(ac, ale); + } else { /* select F-Curve by itself */ ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); fcu->flag |= FCURVE_SELECTED; } - /* if F-Curve is selected now, make F-Curve the 'active' one in the visible list */ - if (fcu->flag & FCURVE_SELECTED) { + /* if F-Curve is selected now, make F-Curve the 'active' one in the visible list. + * Similar to outliner, do not change active element when selecting elements in range. */ + if ((fcu->flag & FCURVE_SELECTED) && (selectmode != SELECT_EXTEND_RANGE)) { ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, fcu, ale->type); } @@ -3277,14 +3432,19 @@ static int click_select_channel_gplayer(bContext *C, /* invert selection status of this layer only */ gpl->flag ^= GP_LAYER_SELECT; } + else if (selectmode == SELECT_EXTEND_RANGE) { + ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_EXTEND_RANGE); + animchannel_select_range(ac, ale); + } else { /* select layer by itself */ ANIM_anim_channels_select_set(ac, ACHANNEL_SETFLAG_CLEAR); gpl->flag |= GP_LAYER_SELECT; } - /* change active layer, if this is selected (since we must always have an active layer) */ - if (gpl->flag & GP_LAYER_SELECT) { + /* change active layer, if this is selected (since we must always have an active layer). + * Similar to outliner, do not change active element when selecting elements in range. */ + if ((gpl->flag & GP_LAYER_SELECT) && (selectmode != SELECT_EXTEND_RANGE)) { ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, gpl, ANIMTYPE_GPLAYER); /* update other layer status */ BKE_gpencil_layer_active_set(gpd, gpl); @@ -3333,7 +3493,7 @@ static int click_select_channel_masklayer(bAnimContext *ac, static int mouse_anim_channels(bContext *C, bAnimContext *ac, const int channel_index, - const short /* eEditKeyframes_Select or -1 */ selectmode) + short /* eEditKeyframes_Select or -1 */ selectmode) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; @@ -3371,6 +3531,11 @@ static int mouse_anim_channels(bContext *C, return 0; } + /* Change selection mode to single when no active element is found. */ + if ((selectmode == SELECT_EXTEND_RANGE) && !animchannel_has_active_of_type(ac, ale->type)) { + selectmode = SELECT_INVERT; + } + /* action to take depends on what channel we've got */ /* WARNING: must keep this in sync with the equivalent function in nla_channels.c */ switch (ale->type) { @@ -3474,6 +3639,9 @@ static int animchannels_mouseclick_invoke(bContext *C, wmOperator *op, const wmE if (RNA_boolean_get(op->ptr, "extend")) { selectmode = SELECT_INVERT; } + else if (RNA_boolean_get(op->ptr, "extend_range")) { + selectmode = SELECT_EXTEND_RANGE; + } else if (RNA_boolean_get(op->ptr, "children_only")) { /* this is a bit of a special case for ActionGroups only... * should it be removed or extended to all instead? */ @@ -3527,6 +3695,13 @@ static void ANIM_OT_channels_click(wmOperatorType *ot) prop = RNA_def_boolean(ot->srna, "extend", false, "Extend Select", ""); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, + "extend_range", + false, + "Extend Range", + "Selection of active channel to clicked channel"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + /* Key-map: Enable with `Ctrl-Shift`. */ prop = RNA_def_boolean(ot->srna, "children_only", false, "Select Children Only", ""); RNA_def_property_flag(prop, PROP_SKIP_SAVE); diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index 21938ad8727..3dd64e64768 100644 --- a/source/blender/editors/armature/pose_select.c +++ b/source/blender/editors/armature/pose_select.c @@ -92,7 +92,7 @@ void ED_pose_bone_select_tag_update(Object *ob) DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); } -void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select) +void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select, bool change_active) { bArmature *arm; @@ -109,11 +109,15 @@ void ED_pose_bone_select(Object *ob, bPoseChannel *pchan, bool select) /* change selection state - activate too if selected */ if (select) { pchan->bone->flag |= BONE_SELECTED; - arm->act_bone = pchan->bone; + if (change_active) { + arm->act_bone = pchan->bone; + } } else { pchan->bone->flag &= ~BONE_SELECTED; - arm->act_bone = NULL; + if (change_active) { + arm->act_bone = NULL; + } } /* TODO: select and activate corresponding vgroup? */ diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index a436fa43989..57f6e1487b0 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -530,6 +530,8 @@ typedef enum eAnimChannels_SetFlag { ACHANNEL_SETFLAG_INVERT = 2, /** some on -> all off / all on */ ACHANNEL_SETFLAG_TOGGLE = 3, + /** turn off, keep active flag **/ + ACHANNEL_SETFLAG_EXTEND_RANGE = 4, } eAnimChannels_SetFlag; /* types of settings for AnimChannels */ @@ -696,6 +698,11 @@ void ANIM_set_active_channel(bAnimContext *ac, void *channel_data, eAnim_ChannelType channel_type); +/** + * Return whether channel is active. + */ +bool ANIM_is_active_channel(bAnimListElem *ale); + /** * Delete the F-Curve from the given AnimData block (if possible), * as appropriate according to animation context. diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h index 13908d63bb6..14d66eaea93 100644 --- a/source/blender/editors/include/ED_armature.h +++ b/source/blender/editors/include/ED_armature.h @@ -356,8 +356,13 @@ bool ED_pose_deselect_all(struct Object *ob, int select_mode, bool ignore_visibi void ED_pose_bone_select_tag_update(struct Object *ob); /** * Utility method for changing the selection status of a bone. + * change_active determines whether to change the active bone of the armature when selecting pose + * channels. It is false during range selection otherwise true. */ -void ED_pose_bone_select(struct Object *ob, struct bPoseChannel *pchan, bool select); +void ED_pose_bone_select(struct Object *ob, + struct bPoseChannel *pchan, + bool select, + bool change_active); /* meshlaplacian.cc */ diff --git a/source/blender/editors/include/ED_keyframes_edit.h b/source/blender/editors/include/ED_keyframes_edit.h index 5bd672072b1..861482c1a10 100644 --- a/source/blender/editors/include/ED_keyframes_edit.h +++ b/source/blender/editors/include/ED_keyframes_edit.h @@ -58,6 +58,7 @@ typedef enum eEditKeyframes_Select { SELECT_SUBTRACT = (1 << 2), /* flip ok status of keyframes based on key status */ SELECT_INVERT = (1 << 3), + SELECT_EXTEND_RANGE = (1 << 4), } eEditKeyframes_Select; /* "selection map" building modes */ diff --git a/source/blender/editors/object/object_select.c b/source/blender/editors/object/object_select.c index 8d085cfc92a..235919c490e 100644 --- a/source/blender/editors/object/object_select.c +++ b/source/blender/editors/object/object_select.c @@ -345,7 +345,7 @@ bool ED_object_jump_to_bone(bContext *C, /* Select it. */ ED_pose_deselect_all(ob, SEL_DESELECT, true); - ED_pose_bone_select(ob, pchan, true); + ED_pose_bone_select(ob, pchan, true, true); arm->act_bone = pchan->bone;