Outliner: Selection cleanup

No functional changes. The outliner selection operators shared many
different functions for selection, activation, mode toggling, and other
actions, but the code paths were not always clear, making any changes
difficult.

This cleans up the code and uses outliner_item_select() as the base
function for selection, activation, mode toggling between the different
selection operators. It also prepares for future features and fixes.

Reviewed By: Severin

Differential Revision: https://developer.blender.org/D5817
This commit is contained in:
Nathan Craddock 2020-06-19 12:35:09 -06:00
parent ec963d9d7d
commit 702e00f910
Notes: blender-bot 2023-02-14 08:45:12 +01:00
Referenced by commit 8ce1a60262, Fix T71812: Outliner walk navigation not activating
4 changed files with 129 additions and 97 deletions

View File

@ -216,6 +216,16 @@ typedef struct TreeViewContext {
Object *ob_pose;
} TreeViewContext;
typedef enum TreeItemSelectAction {
OL_ITEM_DESELECT = 0, /* Deselect the item */
OL_ITEM_SELECT = (1 << 0), /* Select the item */
OL_ITEM_SELECT_DATA = (1 << 1), /* Select object data */
OL_ITEM_ACTIVATE = (1 << 2), /* Activate the item */
OL_ITEM_EXTEND = (1 << 3), /* Extend the current selection */
OL_ITEM_RECURSIVE = (1 << 4), /* Select recursively */
OL_ITEM_TOGGLE_MODE = (1 << 5) /* Temporary */
} TreeItemSelectAction;
/* outliner_tree.c ----------------------------------------------- */
void outliner_free_tree(ListBase *tree);
@ -268,20 +278,17 @@ eOLDrawState tree_element_active(struct bContext *C,
const eOLSetState set,
const bool handle_all_types);
void outliner_item_do_activate_from_tree_element(
struct bContext *C, TreeElement *te, TreeStoreElem *tselem, bool extend, bool recursive);
void outliner_item_select(struct SpaceOutliner *soops,
const struct TreeElement *te,
const bool extend,
const bool toggle);
void outliner_item_select(struct bContext *C,
struct SpaceOutliner *soops,
struct TreeElement *te,
const short select_flag);
void outliner_object_mode_toggle(struct bContext *C,
Scene *scene,
ViewLayer *view_layer,
Base *base);
void outliner_element_activate(struct SpaceOutliner *soops, struct TreeStoreElem *tselem);
void outliner_set_walk_element(struct SpaceOutliner *soops, struct TreeStoreElem *tselem);
bool outliner_item_is_co_over_name_icons(const TreeElement *te, float view_co_x);
bool outliner_item_is_co_within_close_toggle(const TreeElement *te, float view_co_x);

View File

@ -111,7 +111,7 @@ static bool do_outliner_activate_common(bContext *C,
* If extend is used, we try to have the other compatible selected objects in the new mode as well.
* Otherwise only the new object will be active, selected and in the edit mode.
*/
static void do_outliner_activate_obdata(
static void do_outliner_item_editmode_toggle(
bContext *C, Scene *scene, ViewLayer *view_layer, Base *base, const bool extend)
{
Main *bmain = CTX_data_main(C);
@ -159,7 +159,7 @@ static void do_outliner_activate_obdata(
}
}
static void do_outliner_activate_pose(
static void do_outliner_item_posemode_toggle(
bContext *C, Scene *scene, ViewLayer *view_layer, Base *base, const bool extend)
{
Main *bmain = CTX_data_main(C);
@ -214,10 +214,42 @@ void outliner_object_mode_toggle(bContext *C, Scene *scene, ViewLayer *view_laye
{
Object *obact = OBACT(view_layer);
if (obact->mode & OB_MODE_EDIT) {
do_outliner_activate_obdata(C, scene, view_layer, base, true);
do_outliner_item_editmode_toggle(C, scene, view_layer, base, true);
}
else if (obact->mode & OB_MODE_POSE) {
do_outliner_activate_pose(C, scene, view_layer, base, true);
do_outliner_item_posemode_toggle(C, scene, view_layer, base, true);
}
}
/* Toggle the item's interaction mode if supported */
static void outliner_item_mode_toggle(bContext *C,
TreeViewContext *tvc,
TreeElement *te,
const bool extend)
{
TreeStoreElem *tselem = TREESTORE(te);
if (tselem->type == 0) {
if (OB_DATA_SUPPORT_EDITMODE(te->idcode)) {
Object *ob = (Object *)outliner_search_back(te, ID_OB);
if ((ob != NULL) && (ob->data == tselem->id)) {
Base *base = BKE_view_layer_base_find(tvc->view_layer, ob);
if ((base != NULL) && (base->flag & BASE_VISIBLE_DEPSGRAPH)) {
do_outliner_item_editmode_toggle(C, tvc->scene, tvc->view_layer, base, extend);
}
}
}
else if (ELEM(te->idcode, ID_GD)) {
/* set grease pencil to object mode */
WM_operator_name_call(C, "GPENCIL_OT_editmode_toggle", WM_OP_INVOKE_REGION_WIN, NULL);
}
}
else if (tselem->type == TSE_POSE_BASE) {
Object *ob = (Object *)tselem->id;
Base *base = BKE_view_layer_base_find(tvc->view_layer, ob);
if (base != NULL) {
do_outliner_item_posemode_toggle(C, tvc->scene, tvc->view_layer, base, extend);
}
}
}
@ -862,8 +894,8 @@ static eOLDrawState tree_element_active_text(bContext *UNUSED(C),
return OL_DRAWSEL_NONE;
}
static eOLDrawState tree_element_active_pose(bContext *C,
Scene *scene,
static eOLDrawState tree_element_active_pose(bContext *UNUSED(C),
Scene *UNUSED(scene),
ViewLayer *view_layer,
TreeElement *UNUSED(te),
TreeStoreElem *tselem,
@ -878,7 +910,6 @@ static eOLDrawState tree_element_active_pose(bContext *C,
}
if (set != OL_SETSEL_NONE) {
do_outliner_activate_pose(C, scene, view_layer, base, (set == OL_SETSEL_EXTEND));
}
else {
if (ob->mode & OB_MODE_POSE) {
@ -1112,11 +1143,11 @@ eOLDrawState tree_element_type_active(bContext *C,
/* ================================================ */
/* Activate a tree store element and set the walk navigation start element */
void outliner_element_activate(SpaceOutliner *soops, TreeStoreElem *tselem)
/* Set the walk navigation start element */
void outliner_set_walk_element(SpaceOutliner *soops, TreeStoreElem *tselem)
{
outliner_flag_set(&soops->tree, TSE_ACTIVE | TSE_ACTIVE_WALK, false);
tselem->flag |= TSE_ACTIVE | TSE_ACTIVE_WALK;
outliner_flag_set(&soops->tree, TSE_ACTIVE_WALK, false);
tselem->flag |= TSE_ACTIVE_WALK;
}
/**
@ -1132,9 +1163,8 @@ static void do_outliner_item_activate_tree_element(bContext *C,
TreeStoreElem *tselem,
const bool extend,
const bool recursive,
const bool is_over_name_icons)
const bool do_activate_data)
{
bool do_activate_data = soops->flag & SO_SYNC_SELECT || is_over_name_icons;
/* Always makes active object, except for some specific types. */
if (ELEM(tselem->type,
TSE_SEQUENCE,
@ -1152,7 +1182,6 @@ static void do_outliner_item_activate_tree_element(bContext *C,
/* Support pose mode toggle, keeping the active object as is. */
}
else if (do_activate_data) {
/* Only activate when synced selection is enabled */
tree_element_set_active_object(C,
tvc->scene,
tvc->view_layer,
@ -1163,8 +1192,7 @@ static void do_outliner_item_activate_tree_element(bContext *C,
recursive && tselem->type == 0);
}
/* Mark as active in the outliner */
outliner_element_activate(soops, tselem);
outliner_set_walk_element(soops, tselem);
if (tselem->type == 0) { // the lib blocks
if (do_activate_data == false) {
@ -1215,19 +1243,6 @@ static void do_outliner_item_activate_tree_element(bContext *C,
DEG_id_tag_update(&tvc->scene->id, ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, tvc->scene);
}
else if (OB_DATA_SUPPORT_EDITMODE(te->idcode)) {
Object *ob = (Object *)outliner_search_back(te, ID_OB);
if ((ob != NULL) && (ob->data == tselem->id)) {
Base *base = BKE_view_layer_base_find(tvc->view_layer, ob);
if ((base != NULL) && (base->flag & BASE_VISIBLE_DEPSGRAPH)) {
do_outliner_activate_obdata(C, tvc->scene, tvc->view_layer, base, extend);
}
}
}
else if (ELEM(te->idcode, ID_GD)) {
/* set grease pencil to object mode */
WM_operator_name_call(C, "GPENCIL_OT_editmode_toggle", WM_OP_INVOKE_REGION_WIN, NULL);
}
else { // rest of types
tree_element_active(C, tvc, soops, te, OL_SETSEL_NORMAL, false);
}
@ -1238,23 +1253,49 @@ static void do_outliner_item_activate_tree_element(bContext *C,
}
}
/**
* \param extend: Don't deselect other items, only modify \a te.
* \param toggle: Select \a te when not selected, deselect when selected.
*/
void outliner_item_select(SpaceOutliner *soops,
const TreeElement *te,
const bool extend,
const bool toggle)
/* Select the item using the set flags */
void outliner_item_select(bContext *C,
SpaceOutliner *soops,
TreeElement *te,
const short select_flag)
{
TreeStoreElem *tselem = TREESTORE(te);
const short new_flag = (toggle && (tselem->flag & TSE_ACTIVE)) ? (tselem->flag ^ TSE_SELECTED) :
(tselem->flag | TSE_SELECTED);
const bool activate = select_flag & OL_ITEM_ACTIVATE;
const bool extend = select_flag & OL_ITEM_EXTEND;
const bool activate_data = select_flag & OL_ITEM_SELECT_DATA;
if (extend == false) {
outliner_flag_set(&soops->tree, TSE_SELECTED, false);
/* Clear previous active when activating and clear selection when not extending selection */
const short clear_flag = (activate ? TSE_ACTIVE : 0) | (extend ? 0 : TSE_SELECTED);
if (clear_flag) {
outliner_flag_set(&soops->tree, clear_flag, false);
}
if (select_flag & OL_ITEM_SELECT) {
tselem->flag |= TSE_SELECTED;
}
else {
tselem->flag &= ~TSE_SELECTED;
}
if (activate) {
TreeViewContext tvc;
outliner_viewcontext_init(C, &tvc);
tselem->flag |= TSE_ACTIVE;
do_outliner_item_activate_tree_element(C,
&tvc,
soops,
te,
tselem,
extend,
select_flag & OL_ITEM_RECURSIVE,
activate_data || soops->flag & SO_SYNC_SELECT);
/* Mode toggle on data activate for now, but move later */
if (select_flag & OL_ITEM_TOGGLE_MODE) {
outliner_item_mode_toggle(C, &tvc, te, extend);
}
}
tselem->flag = new_flag;
}
static bool do_outliner_range_select_recursive(ListBase *lb,
@ -1297,8 +1338,7 @@ static void do_outliner_range_select(bContext *C,
/* If no active element exists, activate the element under the cursor */
if (!active) {
outliner_item_select(soops, cursor, false, false);
outliner_item_do_activate_from_tree_element(C, cursor, TREESTORE(cursor), false, false);
outliner_item_select(C, soops, cursor, OL_ITEM_SELECT | OL_ITEM_ACTIVATE);
return;
}
@ -1311,14 +1351,13 @@ static void do_outliner_range_select(bContext *C,
/* Select active if under cursor */
if (active == cursor) {
TREESTORE(cursor)->flag |= TSE_SELECTED;
outliner_item_select(C, soops, cursor, OL_ITEM_SELECT);
return;
}
/* If active is not selected, select the element under the cursor */
/* If active is not selected or visible, select and activate the element under the cursor */
if (!active_selected || !outliner_is_element_visible(active)) {
outliner_item_select(soops, cursor, false, false);
outliner_item_do_activate_from_tree_element(C, cursor, TREESTORE(cursor), false, false);
outliner_item_select(C, soops, cursor, OL_ITEM_SELECT | OL_ITEM_ACTIVATE);
return;
}
@ -1332,23 +1371,6 @@ static bool outliner_is_co_within_restrict_columns(const SpaceOutliner *soops,
return (view_co_x > region->v2d.cur.xmax - outliner_restrict_columns_width(soops));
}
/**
* A version of #outliner_item_do_activate_from_cursor that takes the tree element directly.
* and doesn't depend on the pointer position.
*
* This allows us to simulate clicking on an item without dealing with the mouse cursor.
*/
void outliner_item_do_activate_from_tree_element(
bContext *C, TreeElement *te, TreeStoreElem *tselem, bool extend, bool recursive)
{
SpaceOutliner *soops = CTX_wm_space_outliner(C);
TreeViewContext tvc;
outliner_viewcontext_init(C, &tvc);
do_outliner_item_activate_tree_element(C, &tvc, soops, te, tselem, extend, recursive, false);
}
/**
* Action to run when clicking in the outliner,
*
@ -1401,14 +1423,17 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
do_outliner_range_select(C, soops, activate_te, extend);
}
else {
TreeViewContext tvc;
outliner_viewcontext_init(C, &tvc);
const bool is_over_name_icons = outliner_item_is_co_over_name_icons(activate_te,
view_mval[0]);
outliner_item_select(soops, activate_te, extend, extend);
do_outliner_item_activate_tree_element(
C, &tvc, soops, activate_te, activate_tselem, extend, false, is_over_name_icons);
/* Always select unless already active and selected */
const bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE &&
activate_tselem->flag & TSE_SELECTED);
const short select_flag = OL_ITEM_ACTIVATE | (select ? OL_ITEM_SELECT : OL_ITEM_DESELECT) |
(is_over_name_icons ? OL_ITEM_SELECT_DATA : 0) |
(extend ? OL_ITEM_EXTEND : 0) | OL_ITEM_TOGGLE_MODE;
outliner_item_select(C, soops, activate_te, select_flag);
}
changed = true;
@ -1467,23 +1492,19 @@ void OUTLINER_OT_item_activate(wmOperatorType *ot)
/* **************** Box Select Tool ****************** */
static void outliner_item_box_select(
SpaceOutliner *soops, Scene *scene, rctf *rectf, TreeElement *te, bool select)
bContext *C, SpaceOutliner *soops, Scene *scene, rctf *rectf, TreeElement *te, bool select)
{
TreeStoreElem *tselem = TREESTORE(te);
if (te->ys <= rectf->ymax && te->ys + UI_UNIT_Y >= rectf->ymin) {
if (select) {
tselem->flag |= TSE_SELECTED;
}
else {
tselem->flag &= ~TSE_SELECTED;
}
outliner_item_select(
C, soops, te, (select ? OL_ITEM_SELECT : OL_ITEM_DESELECT) | OL_ITEM_EXTEND);
}
/* Look at its children. */
if (TSELEM_OPEN(tselem, soops)) {
for (te = te->subtree.first; te; te = te->next) {
outliner_item_box_select(soops, scene, rectf, te, select);
outliner_item_box_select(C, soops, scene, rectf, te, select);
}
}
}
@ -1505,7 +1526,7 @@ static int outliner_box_select_exec(bContext *C, wmOperator *op)
UI_view2d_region_to_view_rctf(&region->v2d, &rectf, &rectf);
LISTBASE_FOREACH (TreeElement *, te, &soops->tree) {
outliner_item_box_select(soops, scene, &rectf, te, select);
outliner_item_box_select(C, soops, scene, &rectf, te, select);
}
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);

View File

@ -403,7 +403,8 @@ static void outliner_select_sync_from_object(ViewLayer *view_layer,
const bool is_selected = (base != NULL) && ((base->flag & BASE_SELECTED) != 0);
if (base && (ob == obact)) {
outliner_element_activate(soops, tselem);
tselem->flag |= TSE_ACTIVE;
outliner_set_walk_element(soops, tselem);
}
else {
tselem->flag &= ~TSE_ACTIVE;
@ -425,7 +426,8 @@ static void outliner_select_sync_from_edit_bone(SpaceOutliner *soops,
EditBone *ebone = (EditBone *)te->directdata;
if (ebone == ebone_active) {
outliner_element_activate(soops, tselem);
tselem->flag |= TSE_ACTIVE;
outliner_set_walk_element(soops, tselem);
}
else {
tselem->flag &= ~TSE_ACTIVE;
@ -448,7 +450,8 @@ static void outliner_select_sync_from_pose_bone(SpaceOutliner *soops,
Bone *bone = pchan->bone;
if (pchan == pchan_active) {
outliner_element_activate(soops, tselem);
tselem->flag |= TSE_ACTIVE;
outliner_set_walk_element(soops, tselem);
}
else {
tselem->flag &= ~TSE_ACTIVE;
@ -469,7 +472,8 @@ static void outliner_select_sync_from_sequence(SpaceOutliner *soops,
Sequence *seq = (Sequence *)tselem->id;
if (seq == sequence_active) {
outliner_element_activate(soops, tselem);
tselem->flag |= TSE_ACTIVE;
outliner_set_walk_element(soops, tselem);
}
else {
tselem->flag &= ~TSE_ACTIVE;
@ -525,7 +529,7 @@ static void outliner_sync_selection_to_outliner(ViewLayer *view_layer,
}
}
else {
tselem->flag &= ~TSE_SELECTED;
tselem->flag &= ~(TSE_SELECTED | TSE_ACTIVE);
}
/* Sync subtree elements */

View File

@ -574,8 +574,7 @@ static void merged_element_search_exec_fn(struct bContext *C, void *UNUSED(arg1)
SpaceOutliner *soops = CTX_wm_space_outliner(C);
TreeElement *te = (TreeElement *)element;
outliner_item_select(soops, te, false, false);
outliner_item_do_activate_from_tree_element(C, te, te->store_elem, false, false);
outliner_item_select(C, soops, te, OL_ITEM_SELECT | OL_ITEM_ACTIVATE);
ED_outliner_select_sync_from_outliner(C, soops);
}
@ -665,12 +664,13 @@ static void object_select_hierarchy_cb(bContext *C,
Scene *UNUSED(scene),
TreeElement *te,
TreeStoreElem *UNUSED(tsep),
TreeStoreElem *tselem,
TreeStoreElem *UNUSED(tselem),
void *UNUSED(user_data))
{
/* Don't extend because this toggles, which is nice for Ctrl-Click but not for a menu item.
* it's especially confusing when multiple items are selected since some toggle on/off. */
outliner_item_do_activate_from_tree_element(C, te, tselem, false, true);
SpaceOutliner *soops = CTX_wm_space_outliner(C);
outliner_item_select(C, soops, te, OL_ITEM_SELECT | OL_ITEM_ACTIVATE | OL_ITEM_RECURSIVE);
}
static void object_deselect_cb(bContext *C,