Dopesheet: drag to box select in dopesheet

This includes refactoring of the `mouse_action_keys` to
make it easier to just detect if there is a key under
the mouse. The refactoring mostly consists of extracting
methods and reducing vertical scope of variables.

Reviewers: billreynish, brecht

Differential Revision: https://developer.blender.org/D5024
This commit is contained in:
Jacques Lucke 2019-06-05 15:48:30 +02:00
parent fb4ec706cd
commit b79976a9bd
2 changed files with 185 additions and 125 deletions

View File

@ -1894,6 +1894,10 @@ def km_dopesheet(params):
{"properties": [("axis_range", False)]}),
("action.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
{"properties": [("axis_range", True)]}),
("action.select_box", {"type": params.select_tweak, "value": 'ANY'},
{"properties": [("tweak", True), ("mode", 'SET')]}),
("action.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
{"properties": [("tweak", True), ("mode", 'ADD')]}),
("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
{"properties": [("mode", 'ADD')]}),
("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},

View File

@ -66,6 +66,163 @@
/* ************************************************************************** */
/* KEYFRAMES STUFF */
static bAnimListElem *actkeys_find_list_element_at_position(bAnimContext *ac,
int filter,
float region_x,
float region_y)
{
View2D *v2d = &ac->ar->v2d;
float view_x, view_y;
int channel_index;
UI_view2d_region_to_view(v2d, region_x, region_y, &view_x, &view_y);
UI_view2d_listview_view_to_cell(
0, ACHANNEL_STEP(ac), 0, ACHANNEL_FIRST_TOP(ac), view_x, view_y, NULL, &channel_index);
ListBase anim_data = {NULL, NULL};
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
bAnimListElem *ale = BLI_findlink(&anim_data, channel_index);
if (ale != NULL) {
BLI_remlink(&anim_data, ale);
ale->next = ale->prev = NULL;
}
ANIM_animdata_freelist(&anim_data);
return ale;
}
static void actkeys_list_element_to_keylist(bAnimContext *ac,
DLRBT_Tree *anim_keys,
bAnimListElem *ale)
{
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
bDopeSheet *ads = NULL;
if (ELEM(ac->datatype, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
ads = ac->data;
}
if (ale->key_data) {
switch (ale->datatype) {
case ALE_SCE: {
Scene *scene = (Scene *)ale->key_data;
scene_to_keylist(ads, scene, anim_keys, 0);
break;
}
case ALE_OB: {
Object *ob = (Object *)ale->key_data;
ob_to_keylist(ads, ob, anim_keys, 0);
break;
}
case ALE_ACT: {
bAction *act = (bAction *)ale->key_data;
action_to_keylist(adt, act, anim_keys, 0);
break;
}
case ALE_FCURVE: {
FCurve *fcu = (FCurve *)ale->key_data;
fcurve_to_keylist(adt, fcu, anim_keys, 0);
break;
}
}
}
else if (ale->type == ANIMTYPE_SUMMARY) {
/* dopesheet summary covers everything */
summary_to_keylist(ac, anim_keys, 0);
}
else if (ale->type == ANIMTYPE_GROUP) {
// TODO: why don't we just give groups key_data too?
bActionGroup *agrp = (bActionGroup *)ale->data;
agroup_to_keylist(adt, agrp, anim_keys, 0);
}
else if (ale->type == ANIMTYPE_GPLAYER) {
// TODO: why don't we just give gplayers key_data too?
bGPDlayer *gpl = (bGPDlayer *)ale->data;
gpl_to_keylist(ads, gpl, anim_keys);
}
else if (ale->type == ANIMTYPE_MASKLAYER) {
// TODO: why don't we just give masklayers key_data too?
MaskLayer *masklay = (MaskLayer *)ale->data;
mask_to_keylist(ads, masklay, anim_keys);
}
}
static void actkeys_find_key_in_list_element(bAnimContext *ac,
bAnimListElem *ale,
float region_x,
float *r_selx,
float *r_frame,
bool *r_found)
{
*r_found = false;
View2D *v2d = &ac->ar->v2d;
DLRBT_Tree anim_keys;
BLI_dlrbTree_init(&anim_keys);
actkeys_list_element_to_keylist(ac, &anim_keys, ale);
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
/* standard channel height (to allow for some slop) */
float key_hsize = ACHANNEL_HEIGHT(ac) * 0.8f;
/* half-size (for either side), but rounded up to nearest int (for easier targeting) */
key_hsize = roundf(key_hsize / 2.0f);
float xmin = UI_view2d_region_to_view_x(v2d, region_x - (int)key_hsize);
float xmax = UI_view2d_region_to_view_x(v2d, region_x + (int)key_hsize);
for (ActKeyColumn *ak = anim_keys.root; ak; ak = (ak->cfra < xmin) ? ak->right : ak->left) {
if (IN_RANGE(ak->cfra, xmin, xmax)) {
/* set the frame to use, and apply inverse-correction for NLA-mapping
* so that the frame will get selected by the selection functions without
* requiring to map each frame once again...
*/
*r_selx = BKE_nla_tweakedit_remap(adt, ak->cfra, NLATIME_CONVERT_UNMAP);
*r_frame = ak->cfra;
*r_found = true;
break;
}
}
/* cleanup temporary lists */
BLI_dlrbTree_free(&anim_keys);
}
static void actkeys_find_key_at_position(bAnimContext *ac,
int filter,
float region_x,
float region_y,
bAnimListElem **r_ale,
float *r_selx,
float *r_frame,
bool *r_found)
{
*r_found = false;
*r_ale = actkeys_find_list_element_at_position(ac, filter, region_x, region_y);
if (*r_ale != NULL) {
actkeys_find_key_in_list_element(ac, *r_ale, region_x, r_selx, r_frame, r_found);
}
}
static bool actkeys_is_key_at_position(bAnimContext *ac, float region_x, float region_y)
{
bAnimListElem *ale;
float selx, frame;
bool found;
int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS;
actkeys_find_key_at_position(ac, filter, region_x, region_y, &ale, &selx, &frame, &found);
if (ale != NULL) {
MEM_freeN(ale);
}
return found;
}
/* ******************** Deselect All Operator ***************************** */
/* This operator works in one of three ways:
* 1) (de)select all (AKEY) - test if select all or deselect all
@ -355,6 +512,21 @@ static void box_select_action(bAnimContext *ac, const rcti rect, short mode, sho
/* ------------------- */
static int actkeys_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
bAnimContext ac;
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
bool tweak = RNA_boolean_get(op->ptr, "tweak");
if (tweak && actkeys_is_key_at_position(&ac, event->mval[0], event->mval[1])) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
return WM_gesture_box_invoke(C, op, event);
}
static int actkeys_box_select_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
@ -412,7 +584,7 @@ void ACTION_OT_select_box(wmOperatorType *ot)
ot->description = "Select all keyframes within the specified region";
/* api callbacks */
ot->invoke = WM_gesture_box_invoke;
ot->invoke = actkeys_box_select_invoke;
ot->exec = actkeys_box_select_exec;
ot->modal = WM_gesture_box_modal;
ot->cancel = WM_gesture_box_cancel;
@ -428,6 +600,10 @@ void ACTION_OT_select_box(wmOperatorType *ot)
/* properties */
WM_operator_properties_gesture_box(ot);
WM_operator_properties_select_operation_simple(ot);
PropertyRNA *prop = RNA_def_boolean(
ot->srna, "tweak", 0, "Tweak", "Operator has been activated using a tweak event");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/* ******************** Region Select Operators ***************************** */
@ -1491,133 +1667,13 @@ static void mouse_action_keys(bAnimContext *ac,
const bool column,
const bool same_channel)
{
ListBase anim_data = {NULL, NULL};
DLRBT_Tree anim_keys;
bAnimListElem *ale;
int filter;
int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS;
View2D *v2d = &ac->ar->v2d;
bDopeSheet *ads = NULL;
int channel_index;
bAnimListElem *ale = NULL;
bool found = false;
float frame = 0.0f; /* frame of keyframe under mouse - NLA corrections not applied/included */
float selx = 0.0f; /* frame of keyframe under mouse */
float key_hsize;
float x, y;
rctf rectf;
/* get dopesheet info */
if (ELEM(ac->datatype, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
ads = ac->data;
}
/* use View2D to determine the index of the channel (i.e a row in the list) where keyframe was */
UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
UI_view2d_listview_view_to_cell(
0, ACHANNEL_STEP(ac), 0, ACHANNEL_FIRST_TOP(ac), x, y, NULL, &channel_index);
/* x-range to check is +/- 7px for standard keyframe under standard dpi/y-scale
* (in screen/region-space), on either side of mouse click (size of keyframe icon).
*/
/* standard channel height (to allow for some slop) */
key_hsize = ACHANNEL_HEIGHT(ac) * 0.8f;
/* half-size (for either side), but rounded up to nearest int (for easier targeting) */
key_hsize = roundf(key_hsize / 2.0f);
UI_view2d_region_to_view(v2d, mval[0] - (int)key_hsize, mval[1], &rectf.xmin, &rectf.ymin);
UI_view2d_region_to_view(v2d, mval[0] + (int)key_hsize, mval[1], &rectf.xmax, &rectf.ymax);
/* filter data */
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
/* try to get channel */
ale = BLI_findlink(&anim_data, channel_index);
if (ale != NULL) {
/* found match - must return here... */
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
ActKeyColumn *ak, *akn = NULL;
/* make list of keyframes */
BLI_dlrbTree_init(&anim_keys);
if (ale->key_data) {
switch (ale->datatype) {
case ALE_SCE: {
Scene *scene = (Scene *)ale->key_data;
scene_to_keylist(ads, scene, &anim_keys, 0);
break;
}
case ALE_OB: {
Object *ob = (Object *)ale->key_data;
ob_to_keylist(ads, ob, &anim_keys, 0);
break;
}
case ALE_ACT: {
bAction *act = (bAction *)ale->key_data;
action_to_keylist(adt, act, &anim_keys, 0);
break;
}
case ALE_FCURVE: {
FCurve *fcu = (FCurve *)ale->key_data;
fcurve_to_keylist(adt, fcu, &anim_keys, 0);
break;
}
}
}
else if (ale->type == ANIMTYPE_SUMMARY) {
/* dopesheet summary covers everything */
summary_to_keylist(ac, &anim_keys, 0);
}
else if (ale->type == ANIMTYPE_GROUP) {
// TODO: why don't we just give groups key_data too?
bActionGroup *agrp = (bActionGroup *)ale->data;
agroup_to_keylist(adt, agrp, &anim_keys, 0);
}
else if (ale->type == ANIMTYPE_GPLAYER) {
// TODO: why don't we just give gplayers key_data too?
bGPDlayer *gpl = (bGPDlayer *)ale->data;
gpl_to_keylist(ads, gpl, &anim_keys);
}
else if (ale->type == ANIMTYPE_MASKLAYER) {
// TODO: why don't we just give masklayers key_data too?
MaskLayer *masklay = (MaskLayer *)ale->data;
mask_to_keylist(ads, masklay, &anim_keys);
}
/* start from keyframe at root of BST,
* traversing until we find one within the range that was clicked on */
for (ak = anim_keys.root; ak; ak = akn) {
if (IN_RANGE(ak->cfra, rectf.xmin, rectf.xmax)) {
/* set the frame to use, and apply inverse-correction for NLA-mapping
* so that the frame will get selected by the selection functions without
* requiring to map each frame once again...
*/
selx = BKE_nla_tweakedit_remap(adt, ak->cfra, NLATIME_CONVERT_UNMAP);
frame = ak->cfra;
found = true;
break;
}
else if (ak->cfra < rectf.xmin) {
akn = ak->right;
}
else {
akn = ak->left;
}
}
/* Remove active channel from list of channels for separate treatment
* (since it's needed later on). */
BLI_remlink(&anim_data, ale);
ale->next = ale->prev = NULL;
/* cleanup temporary lists */
BLI_dlrbTree_free(&anim_keys);
}
/* free list of channels, since it's not used anymore */
ANIM_animdata_freelist(&anim_data);
actkeys_find_key_at_position(ac, filter, mval[0], mval[1], &ale, &selx, &frame, &found);
/* For replacing selection, if we have something to select, we have to clear existing selection.
* The same goes if we found nothing to select, and deselect_all is true
@ -1700,7 +1756,7 @@ static void mouse_action_keys(bAnimContext *ac,
/* flush tagged updates
* NOTE: We temporarily add this channel back to the list so that this can happen
*/
anim_data.first = anim_data.last = ale;
ListBase anim_data = {ale, ale};
ANIM_animdata_update(ac, &anim_data);
/* free this channel */