Outliner: Double-click on item icon to select contents/hierarchy #110151

Merged
Harley Acheson merged 12 commits from mgradysaunders/blender:temp-outliner-selection into main 2024-01-17 00:45:52 +01:00
4 changed files with 149 additions and 18 deletions

View File

@ -1246,6 +1246,14 @@ def km_outliner(params):
{"properties": [("extend_range", True), ("deselect_all", not params.legacy)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
{"properties": [("extend", True), ("extend_range", True), ("deselect_all", not params.legacy)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'},
{"properties": [("recurse", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True},
{"properties": [("recurse", True), ("extend", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "shift": True},
{"properties": [("recurse", True), ("extend_range", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True, "shift": True},
{"properties": [("recurse", True), ("extend", True), ("extend_range", True), ("deselect_all", True)]}),
("outliner.select_box", {"type": 'B', "value": 'PRESS'}, None),
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},

View File

@ -486,6 +486,14 @@ def km_outliner(params):
{"properties": [("extend", False), ("extend_range", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True, "shift": True},
{"properties": [("extend", True), ("extend_range", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'},
{"properties": [("recurse", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True},
{"properties": [("recurse", True), ("extend", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "shift": True},
{"properties": [("recurse", True), ("extend_range", True), ("deselect_all", True)]}),
("outliner.item_activate", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK', "ctrl": True, "shift": True},
{"properties": [("recurse", True), ("extend", True), ("extend_range", True), ("deselect_all", True)]}),
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG'}, {"properties": [("tweak", True)]}),
("outliner.select_box", {"type": 'LEFTMOUSE', "value": 'CLICK_DRAG', "shift": True},
{"properties": [("tweak", True), ("mode", 'ADD')]}),

View File

@ -413,7 +413,7 @@ static int outliner_item_rename_invoke(bContext *C, wmOperator *op, const wmEven
TreeElement *te = use_active ? outliner_item_rename_find_active(space_outliner, op->reports) :
outliner_item_rename_find_hovered(space_outliner, region, event);
if (!te) {
return OPERATOR_CANCELLED;
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
/* Force element into view. */

View File

@ -1569,30 +1569,84 @@ void outliner_item_select(bContext *C,
}
}
static bool can_select_recursive(TreeElement *te, Collection *in_collection)
{
if (te->store_elem->type == TSE_LAYER_COLLECTION) {
Harley marked this conversation as resolved Outdated

This comment doesn't really add much, would remove it.

This comment doesn't really add much, would remove it.
return true;
}
if (te->store_elem->type == TSE_SOME_ID && te->idcode == ID_OB) {
/* Only actually select the object if
* 1. We are not restricted to any collection, or
Harley marked this conversation as resolved Outdated

Code-style: Every multi-line comment should have a * in new lines, see https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2FC.2B.2B_Comments

Code-style: Every multi-line comment should have a `*` in new lines, see https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2FC.2B.2B_Comments
* 2. The object is in fact in the given collection. */
if (!in_collection || BKE_collection_has_object_recursive(
in_collection, reinterpret_cast<Object *>(te->store_elem->id)))
{
return true;
}
}
return false;
}
static void do_outliner_select_recursive(ListBase *lb, bool selecting, Collection *in_collection)
{
LISTBASE_FOREACH (TreeElement *, te, lb) {
PratikPB2123 marked this conversation as resolved
Review

for object ID, can we use do_outliner_object_select_recursive directly?

for object ID, can we use `do_outliner_object_select_recursive` directly?
TreeStoreElem *tselem = TREESTORE(te);
/* Recursive selection only on collections or objects. */
if (can_select_recursive(te, in_collection)) {
tselem->flag = selecting ? (tselem->flag | TSE_SELECTED) : (tselem->flag & ~TSE_SELECTED);
if (tselem->type == TSE_LAYER_COLLECTION) {
/* Restrict sub-tree selections to this collection. This prevents undesirable behavior in
* the edge-case where there is an object which is part of this collection, but which has
* children that are part of another collection. */
do_outliner_select_recursive(
&te->subtree, selecting, static_cast<LayerCollection *>(te->directdata)->collection);
}
else {
do_outliner_select_recursive(&te->subtree, selecting, in_collection);
}
}
else {
tselem->flag &= ~TSE_SELECTED;
}
}
}
static bool do_outliner_range_select_recursive(ListBase *lb,
TreeElement *active,
TreeElement *cursor,
bool selecting)
bool selecting,
const bool recurse,
Collection *in_collection)
{
LISTBASE_FOREACH (TreeElement *, te, lb) {
Harley marked this conversation as resolved Outdated

This logic is quite complicated. You could split this up into multiple bools.

However, I think it's good to move this into an own function, with a descriptive name. In there you can have multiple if statements, and explain each condition nicely.

This logic is quite complicated. You could split this up into multiple bools. However, I think it's good to move this into an own function, with a descriptive name. In there you can have multiple if statements, and explain each condition nicely.

@JulianEisel - I find this area and this logic a bit too mystifying to simplify this.

@JulianEisel - I find this area and this logic a bit too mystifying to simplify this.

This can be moved to one separate function (for example: can_select_recurse) where we can handle collection and object cases separately. I'll share the simplified version with the PR applied shortly

This can be moved to one separate function (for example: `can_select_recurse`) where we can handle collection and object cases separately. I'll share the simplified version with the PR applied shortly
TreeStoreElem *tselem = TREESTORE(te);
if (selecting) {
tselem->flag |= TSE_SELECTED;
}
bool can_select = !recurse || can_select_recursive(te, in_collection);
/* Remember if we are selecting before we potentially change the selecting state. */
bool selecting_before = selecting;
/* Set state for selection */
if (ELEM(te, active, cursor)) {
selecting = !selecting;
}
if (selecting) {
if (can_select && (selecting_before || selecting)) {
tselem->flag |= TSE_SELECTED;
}
/* Don't look inside closed elements */
if (!(tselem->flag & TSE_CLOSED)) {
selecting = do_outliner_range_select_recursive(&te->subtree, active, cursor, selecting);
/* Don't look inside closed elements, unless we're forcing the recursion all the way down. */
if (!(tselem->flag & TSE_CLOSED) || recurse) {
/* If this tree element is a collection, then it sets
Harley marked this conversation as resolved Outdated

There is code duplication.
in_collection is already found for active tree element in outliner_item_do_activate_from_cursor. So just pass it as argument to this function?

There is code duplication. `in_collection` is already found for active tree element in `outliner_item_do_activate_from_cursor`. So just pass it as argument to this function?
* the precedent for inclusion of its subobjects. */
Collection *child_collection = in_collection;
if (tselem->type == TSE_LAYER_COLLECTION) {
Harley marked this conversation as resolved
Review

Could use a better name, like nested_collection or child_collection.

Could use a better name, like `nested_collection` or `child_collection`.
child_collection = static_cast<LayerCollection *>(te->directdata)->collection;
Harley marked this conversation as resolved Outdated

Maybe we can wrap this inside if(can_select). Otherwise unnecessarily we will iterate the children of object that are not present in current collection

Maybe we can wrap this inside `if(can_select)`. Otherwise unnecessarily we will iterate the children of object that are not present in current collection
}
selecting = do_outliner_range_select_recursive(
&te->subtree, active, cursor, selecting, recurse, child_collection);
}
}
@ -1603,7 +1657,9 @@ static bool do_outliner_range_select_recursive(ListBase *lb,
static void do_outliner_range_select(bContext *C,
SpaceOutliner *space_outliner,
TreeElement *cursor,
const bool extend)
const bool extend,
const bool recurse,
Collection *in_collection)
{
TreeElement *active = outliner_find_element_with_flag(&space_outliner->tree, TSE_ACTIVE);
@ -1632,7 +1688,8 @@ static void do_outliner_range_select(bContext *C,
return;
}
do_outliner_range_select_recursive(&space_outliner->tree, active, cursor, false);
do_outliner_range_select_recursive(
&space_outliner->tree, active, cursor, false, recurse, in_collection);
}
static bool outliner_is_co_within_restrict_columns(const SpaceOutliner *space_outliner,
@ -1673,7 +1730,8 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
const int mval[2],
const bool extend,
const bool use_range,
const bool deselect_all)
const bool deselect_all,
const bool recurse)
{
ARegion *region = CTX_wm_region(C);
SpaceOutliner *space_outliner = CTX_wm_space_outliner(C);
@ -1716,21 +1774,73 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
TreeStoreElem *activate_tselem = TREESTORE(activate_te);
/* If we're recursing, we need to know the collection of the selected item in order
* to prevent selecting across collection boundaries. (Object hierarchies might cross
* collection boundaries, i.e., children may be in different collections from their
* parents.) */
Collection *parent_collection = nullptr;
if (recurse) {
Harley marked this conversation as resolved Outdated

in_collection sounds like a boolean variable. Maybe parent_collection?

`in_collection` sounds like a boolean variable. Maybe `parent_collection`?
if (activate_tselem->type == TSE_LAYER_COLLECTION) {
parent_collection = static_cast<LayerCollection *>(activate_te->directdata)->collection;
}
else if (activate_tselem->type == TSE_SOME_ID && activate_te->idcode == ID_OB) {
parent_collection = BKE_collection_object_find(
CTX_data_main(C),
CTX_data_scene(C),
nullptr,
reinterpret_cast<Object *>(activate_tselem->id));
}
}
/* If we're not recursing (not double clicking), and we are extending or range selecting by
* holding CTRL or SHIFT, ignore events when the cursor is over the icon. This disambiguates
* the case where we are recursing *and* holding CTRL or SHIFT in order to extend or range
* select recursively. */
if (!recurse && (extend || use_range) &&
outliner_item_is_co_over_icon(activate_te, view_mval[0]))
{
return OPERATOR_CANCELLED;
}
if (use_range) {
do_outliner_range_select(C, space_outliner, activate_te, extend);
do_outliner_range_select(C, space_outliner, activate_te, extend, recurse, parent_collection);
if (recurse) {
do_outliner_select_recursive(&activate_te->subtree, true, parent_collection);
}
}
else {
const bool is_over_name_icons = outliner_item_is_co_over_name_icons(activate_te,
view_mval[0]);
Harley marked this conversation as resolved Outdated

use brackets {}

use brackets {}
/* Always select unless already active and selected */
const bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE &&
activate_tselem->flag & TSE_SELECTED);
/* Always select unless already active and selected. */
bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE) ||
!(activate_tselem->flag & TSE_SELECTED);
/* If we're CTRL+double-clicking and the element is aleady
Harley marked this conversation as resolved Outdated

use brackets {}

use brackets {}

I think this reads easier:

bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE) || ! (activate_tselem->flag & TSE_SELECTED);
I think this reads easier: ``` bool select = !extend || !(activate_tselem->flag & TSE_ACTIVE) || ! (activate_tselem->flag & TSE_SELECTED); ```
* selected, skip the activation and go straight to deselection. */
if (extend && recurse && activate_tselem->flag & TSE_SELECTED) {
select = false;
}
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);
outliner_item_select(C, space_outliner, activate_te, select_flag);
/* The recurse flag is set when the user double-clicks
* to select everything in a collection or hierarchy. */
if (recurse) {
Harley marked this conversation as resolved Outdated

use brackets {}

use brackets {}
if (outliner_item_is_co_over_icon(activate_te, view_mval[0])) {
/* Select or deselect object hierarchy recursively. */
outliner_item_select(C, space_outliner, activate_te, select_flag);
do_outliner_select_recursive(&activate_te->subtree, select, parent_collection);
}
else {
/* Double-clicked, but it wasn't on the icon. */
return OPERATOR_CANCELLED;
}
}
else {
outliner_item_select(C, space_outliner, activate_te, select_flag);
}
/* Only switch properties editor tabs when icons are selected. */
if (is_over_icon) {
@ -1765,10 +1875,11 @@ static int outliner_item_activate_invoke(bContext *C, wmOperator *op, const wmEv
const bool extend = RNA_boolean_get(op->ptr, "extend");
const bool use_range = RNA_boolean_get(op->ptr, "extend_range");
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
const bool recurse = RNA_boolean_get(op->ptr, "recurse");
Review

If we do && outliner_item_is_co_over_icon() here then it would make more sense (since this is intended to work when double clicking on icon) but that's optional.

outliner_item_is_co_over_icon requires tree-element under cursor which is calculated in outliner_item_do_activate_from_cursor.

If we do `&& outliner_item_is_co_over_icon()` here then it would make more sense (since this is intended to work when double clicking on icon) but that's optional. `outliner_item_is_co_over_icon` requires tree-element under cursor which is calculated in `outliner_item_do_activate_from_cursor`.
int mval[2];
WM_event_drag_start_mval(event, region, mval);
return outliner_item_do_activate_from_cursor(C, mval, extend, use_range, deselect_all);
return outliner_item_do_activate_from_cursor(C, mval, extend, use_range, deselect_all, recurse);
}
void OUTLINER_OT_item_activate(wmOperatorType *ot)
@ -1796,6 +1907,10 @@ void OUTLINER_OT_item_activate(wmOperatorType *ot)
"Deselect On Nothing",
"Deselect all when nothing under the cursor");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "recurse", false, "Recurse", "Select objects recursively from active element");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */