diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index 8611a9ae881..9f037fa4793 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -748,7 +748,8 @@ const bTheme U_theme_default = { .outline_width = 1, .facedot_size = 4, .match = RGBA(0x337f334c), - .selected_highlight = RGBA(0x314e784c), + .selected_highlight = RGBA(0x223a5bff), + .active = RGBA(0x3b5689ff), .selected_object = RGBA(0xe96a00ff), .active_object = RGBA(0xffaf29ff), .edited_object = RGBA(0x00806266), diff --git a/release/scripts/presets/interface_theme/blender_light.xml b/release/scripts/presets/interface_theme/blender_light.xml index dc0bb6b629e..49b01ec3309 100644 --- a/release/scripts/presets/interface_theme/blender_light.xml +++ b/release/scripts/presets/interface_theme/blender_light.xml @@ -974,6 +974,7 @@ armatures) { arm->flag &= ~(ARM_FLAG_UNUSED_6); } + + /* Marks each outliner as dirty so a sync will occur as an outliner draws. */ + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) { + for (SpaceLink *space = sa->spacedata.first; space; space = space->next) { + if (space->spacetype == SPACE_OUTLINER) { + SpaceOutliner *soutliner = (SpaceOutliner *)space; + soutliner->sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_ALL; + soutliner->flag |= SO_SYNC_SELECT; + } + } + } + } } if (!MAIN_VERSION_ATLEAST(bmain, 281, 1)) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index e987a623d0b..b01d2765963 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -141,6 +141,9 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) FROM_DEFAULT_V4_UCHAR(space_outliner.row_alternate); } + FROM_DEFAULT_V4_UCHAR(space_outliner.selected_highlight); + FROM_DEFAULT_V4_UCHAR(space_outliner.active); + /** * Include next version bump. */ diff --git a/source/blender/editors/armature/armature_select.c b/source/blender/editors/armature/armature_select.c index 23ddf77e63d..eff621d7b71 100644 --- a/source/blender/editors/armature/armature_select.c +++ b/source/blender/editors/armature/armature_select.c @@ -46,6 +46,7 @@ #include "ED_armature.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_view3d.h" @@ -356,6 +357,8 @@ static int armature_select_linked_invoke(bContext *C, wmOperator *op, const wmEv } } + ED_outliner_select_sync_from_edit_bone_tag(C); + ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, base->object); @@ -1027,6 +1030,8 @@ static int armature_de_select_all_exec(bContext *C, wmOperator *op) } CTX_DATA_END; + ED_outliner_select_sync_from_edit_bone_tag(C); + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL); return OPERATOR_FINISHED; @@ -1148,6 +1153,8 @@ static int armature_de_select_more_exec(bContext *C, wmOperator *UNUSED(op)) WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); } MEM_freeN(objects); + + ED_outliner_select_sync_from_edit_bone_tag(C); return OPERATOR_FINISHED; } @@ -1178,6 +1185,8 @@ static int armature_de_select_less_exec(bContext *C, wmOperator *UNUSED(op)) WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); } MEM_freeN(objects); + + ED_outliner_select_sync_from_edit_bone_tag(C); return OPERATOR_FINISHED; } @@ -1569,6 +1578,8 @@ static int armature_select_similar_exec(bContext *C, wmOperator *op) #undef STRUCT_SIZE_AND_OFFSET + ED_outliner_select_sync_from_edit_bone_tag(C); + return OPERATOR_FINISHED; } @@ -1663,6 +1674,8 @@ static int armature_select_hierarchy_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_edit_bone_tag(C); + ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); @@ -1748,6 +1761,8 @@ static int armature_select_mirror_exec(bContext *C, wmOperator *op) arm->act_edbone = ebone_mirror_act; } + ED_outliner_select_sync_from_edit_bone_tag(C); + ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob); @@ -1876,6 +1891,7 @@ static int armature_shortest_path_pick_invoke(bContext *C, wmOperator *op, const if (changed) { arm->act_edbone = ebone_dst; + ED_outliner_select_sync_from_edit_bone_tag(C); ED_armature_edit_sync_selection(arm->edbo); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit); diff --git a/source/blender/editors/armature/pose_select.c b/source/blender/editors/armature/pose_select.c index 8434fee6e78..beec2f8358f 100644 --- a/source/blender/editors/armature/pose_select.c +++ b/source/blender/editors/armature/pose_select.c @@ -54,6 +54,7 @@ #include "ED_keyframing.h" #include "ED_mesh.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_view3d.h" @@ -449,6 +450,8 @@ static int pose_select_connected_invoke(bContext *C, wmOperator *op, const wmEve selectconnected_posebonechildren(base->object, curBone, extend); } + ED_outliner_select_sync_from_pose_bone_tag(C); + ED_pose_bone_select_tag_update(base->object); return OPERATOR_FINISHED; @@ -514,6 +517,8 @@ static int pose_de_select_all_exec(bContext *C, wmOperator *op) } CTX_DATA_END; + ED_outliner_select_sync_from_pose_bone_tag(C); + WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL); return OPERATOR_FINISHED; @@ -560,6 +565,8 @@ static int pose_select_parent_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_pose_bone_tag(C); + ED_pose_bone_select_tag_update(ob); return OPERATOR_FINISHED; } @@ -624,6 +631,8 @@ static int pose_select_constraint_target_exec(bContext *C, wmOperator *UNUSED(op return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_pose_bone_tag(C); + return OPERATOR_FINISHED; } @@ -712,6 +721,8 @@ static int pose_select_hierarchy_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_pose_bone_tag(C); + ED_pose_bone_select_tag_update(ob); return OPERATOR_FINISHED; @@ -1061,6 +1072,8 @@ static int pose_select_grouped_exec(bContext *C, wmOperator *op) /* report done status */ if (changed) { + ED_outliner_select_sync_from_pose_bone_tag(C); + return OPERATOR_FINISHED; } else { @@ -1172,6 +1185,8 @@ static int pose_select_mirror_exec(bContext *C, wmOperator *op) } MEM_freeN(objects); + ED_outliner_select_sync_from_pose_bone_tag(C); + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/include/ED_outliner.h b/source/blender/editors/include/ED_outliner.h index e94aedc2b2b..a28b1b8483a 100644 --- a/source/blender/editors/include/ED_outliner.h +++ b/source/blender/editors/include/ED_outliner.h @@ -30,4 +30,15 @@ bool ED_outliner_collections_editor_poll(struct bContext *C); void ED_outliner_selected_objects_get(const struct bContext *C, struct ListBase *objects); +void ED_outliner_select_sync_from_object_tag(struct bContext *C); +void ED_outliner_select_sync_from_edit_bone_tag(struct bContext *C); +void ED_outliner_select_sync_from_pose_bone_tag(struct bContext *C); +void ED_outliner_select_sync_from_sequence_tag(struct bContext *C); + +bool ED_outliner_select_sync_is_dirty(const struct bContext *C); + +void ED_outliner_select_sync_from_outliner(struct bContext *C, struct SpaceOutliner *soops); + +void ED_outliner_select_sync_flag_outliners(const struct bContext *C); + #endif /* __ED_OUTLINER_H__ */ diff --git a/source/blender/editors/include/UI_resources.h b/source/blender/editors/include/UI_resources.h index 3b080b6df95..29022adac6c 100644 --- a/source/blender/editors/include/UI_resources.h +++ b/source/blender/editors/include/UI_resources.h @@ -255,6 +255,7 @@ typedef enum ThemeColorID { TH_MATCH, /* highlight color for search matches */ TH_SELECT_HIGHLIGHT, /* highlight color for selected outliner item */ + TH_SELECT_ACTIVE, /* highlight color for active outliner item */ TH_SELECTED_OBJECT, /* selected object color for outliner */ TH_ACTIVE_OBJECT, /* active object color for outliner */ TH_EDITED_OBJECT, /* edited object color for outliner */ diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index c31de60c7ed..7c5d5401d08 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -794,6 +794,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) cp = ts->selected_highlight; break; + case TH_SELECT_ACTIVE: + cp = ts->active; + break; + case TH_SELECTED_OBJECT: cp = ts->selected_object; break; diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 7e031866dec..80d150506ad 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -103,6 +103,7 @@ #include "ED_mesh.h" #include "ED_node.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_physics.h" #include "ED_render.h" #include "ED_screen.h" @@ -502,6 +503,8 @@ Object *ED_object_add_type(bContext *C, /* TODO(sergey): Use proper flag for tagging here. */ DEG_id_tag_update(&scene->id, 0); + ED_outliner_select_sync_from_object_tag(C); + return ob; } diff --git a/source/blender/editors/object/object_select.c b/source/blender/editors/object/object_select.c index da06707ebac..28242b986f1 100644 --- a/source/blender/editors/object/object_select.c +++ b/source/blender/editors/object/object_select.c @@ -69,6 +69,7 @@ #include "ED_armature.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_keyframing.h" @@ -436,6 +437,8 @@ static int object_select_by_type_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } @@ -717,6 +720,7 @@ static int object_select_linked_exec(bContext *C, wmOperator *op) if (changed) { DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); return OPERATOR_FINISHED; } @@ -1100,6 +1104,7 @@ static int object_select_grouped_exec(bContext *C, wmOperator *op) if (changed) { DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); return OPERATOR_FINISHED; } @@ -1150,6 +1155,8 @@ static int object_select_all_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } else if (any_visible == false) { @@ -1218,6 +1225,8 @@ static int object_select_same_collection_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } @@ -1281,6 +1290,8 @@ static int object_select_mirror_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } @@ -1369,6 +1380,9 @@ static int object_select_more_exec(bContext *C, wmOperator *UNUSED(op)) Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } else { @@ -1399,6 +1413,9 @@ static int object_select_less_exec(bContext *C, wmOperator *UNUSED(op)) Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } else { @@ -1448,6 +1465,8 @@ static int object_select_random_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_outliner/CMakeLists.txt b/source/blender/editors/space_outliner/CMakeLists.txt index d235dd47136..616915dbc2c 100644 --- a/source/blender/editors/space_outliner/CMakeLists.txt +++ b/source/blender/editors/space_outliner/CMakeLists.txt @@ -41,6 +41,7 @@ set(SRC outliner_edit.c outliner_ops.c outliner_select.c + outliner_sync.c outliner_tools.c outliner_tree.c outliner_utils.c diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index e4881a6f13d..cab8e45b827 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -60,6 +60,7 @@ #include "ED_armature.h" #include "ED_keyframing.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "WM_api.h" @@ -3193,6 +3194,7 @@ static void outliner_draw_highlights_recursive(unsigned pos, const SpaceOutliner *soops, const ListBase *lb, const float col_selection[4], + const float col_active[4], const float col_highlight[4], const float col_searchmatch[4], int start_x, @@ -3206,7 +3208,11 @@ static void outliner_draw_highlights_recursive(unsigned pos, const int start_y = *io_start_y; /* selection status */ - if (tselem->flag & TSE_SELECTED) { + if ((tselem->flag & TSE_ACTIVE) && (tselem->flag & TSE_SELECTED)) { + immUniformColor4fv(col_active); + immRecti(pos, 0, start_y, (int)ar->v2d.cur.xmax, start_y + UI_UNIT_Y); + } + else if (tselem->flag & TSE_SELECTED) { immUniformColor4fv(col_selection); immRecti(pos, 0, start_y, (int)ar->v2d.cur.xmax, start_y + UI_UNIT_Y); } @@ -3260,6 +3266,7 @@ static void outliner_draw_highlights_recursive(unsigned pos, soops, &te->subtree, col_selection, + col_active, col_highlight, col_searchmatch, start_x + UI_UNIT_X, @@ -3271,10 +3278,12 @@ static void outliner_draw_highlights_recursive(unsigned pos, static void outliner_draw_highlights(ARegion *ar, SpaceOutliner *soops, int startx, int *starty) { const float col_highlight[4] = {1.0f, 1.0f, 1.0f, 0.13f}; - float col_selection[4], col_searchmatch[4]; + float col_selection[4], col_active[4], col_searchmatch[4]; UI_GetThemeColor3fv(TH_SELECT_HIGHLIGHT, col_selection); col_selection[3] = 1.0f; /* no alpha */ + UI_GetThemeColor3fv(TH_SELECT_ACTIVE, col_active); + col_active[3] = 1.0f; /* no alpha */ UI_GetThemeColor4fv(TH_MATCH, col_searchmatch); col_searchmatch[3] = 0.5f; @@ -3282,8 +3291,16 @@ static void outliner_draw_highlights(ARegion *ar, SpaceOutliner *soops, int star GPUVertFormat *format = immVertexFormat(); uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - outliner_draw_highlights_recursive( - pos, ar, soops, &soops->tree, col_selection, col_highlight, col_searchmatch, startx, starty); + outliner_draw_highlights_recursive(pos, + ar, + soops, + &soops->tree, + col_selection, + col_active, + col_highlight, + col_searchmatch, + startx, + starty); immUnbindProgram(); GPU_blend(false); } @@ -3439,6 +3456,17 @@ void draw_outliner(const bContext *C) outliner_build_tree(mainvar, scene, view_layer, soops, ar); // always + /* If global sync select is dirty, flag other outliners */ + if (ED_outliner_select_sync_is_dirty(C)) { + ED_outliner_select_sync_flag_outliners(C); + } + + /* Sync selection state from view layer */ + if (!ELEM(soops->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS) && + soops->flag & SO_SYNC_SELECT) { + outliner_sync_selection(C, soops); + } + /* force display to pixel coords */ v2d->flag |= (V2D_PIXELOFS_X | V2D_PIXELOFS_Y); /* set matrix for 2d-view controls */ diff --git a/source/blender/editors/space_outliner/outliner_edit.c b/source/blender/editors/space_outliner/outliner_edit.c index de6e89e47c4..57fea544c77 100644 --- a/source/blender/editors/space_outliner/outliner_edit.c +++ b/source/blender/editors/space_outliner/outliner_edit.c @@ -1103,6 +1103,10 @@ static int outliner_select_all_exec(bContext *C, wmOperator *op) break; } + if (soops->flag & SO_SYNC_SELECT) { + ED_outliner_select_sync_from_outliner(C, soops); + } + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); ED_region_tag_redraw_no_rebuild(ar); diff --git a/source/blender/editors/space_outliner/outliner_intern.h b/source/blender/editors/space_outliner/outliner_intern.h index fa28d119244..c12fdcc6d1f 100644 --- a/source/blender/editors/space_outliner/outliner_intern.h +++ b/source/blender/editors/space_outliner/outliner_intern.h @@ -253,6 +253,8 @@ void outliner_object_mode_toggle(struct bContext *C, ViewLayer *view_layer, Base *base); +void outliner_element_activate(struct SpaceOutliner *soops, struct TreeStoreElem *tselem); + /* outliner_edit.c ---------------------------------------------- */ typedef void (*outliner_operation_cb)(struct bContext *C, struct ReportList *, @@ -457,4 +459,8 @@ bool outliner_tree_traverse(const SpaceOutliner *soops, void *customdata); float outliner_restrict_columns_width(const struct SpaceOutliner *soops); +/* outliner_sync.c ---------------------------------------------- */ + +void outliner_sync_selection(const struct bContext *C, struct SpaceOutliner *soops); + #endif /* __OUTLINER_INTERN_H__ */ diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c index c932766ab93..38dfe439b9c 100644 --- a/source/blender/editors/space_outliner/outliner_select.c +++ b/source/blender/editors/space_outliner/outliner_select.c @@ -54,6 +54,7 @@ #include "ED_armature.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_sequencer.h" @@ -1083,6 +1084,13 @@ 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) +{ + outliner_flag_set(&soops->tree, TSE_ACTIVE, false); + tselem->flag |= TSE_ACTIVE; +} + /** * Action when clicking to activate an item (typically under the mouse cursor), * but don't do any cursor intersection checks. @@ -1114,7 +1122,8 @@ static void do_outliner_item_activate_tree_element(bContext *C, else if (tselem->type == TSE_POSE_BASE) { /* Support pose mode toggle, keeping the active object as is. */ } - else { + else if (soops->flag & SO_SYNC_SELECT) { + /* Only activate when synced selection is enabled */ tree_element_set_active_object(C, scene, view_layer, @@ -1125,6 +1134,9 @@ 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); + if (tselem->type == 0) { // the lib blocks /* editmode? */ if (te->idcode == ID_SCE) { @@ -1189,7 +1201,7 @@ static void do_outliner_item_activate_tree_element(bContext *C, tree_element_active(C, scene, view_layer, soops, te, OL_SETSEL_NORMAL, false); } } - else { + else if (soops->flag & SO_SYNC_SELECT) { tree_element_type_active(C, scene, view_layer, @@ -1211,7 +1223,8 @@ void outliner_item_select(SpaceOutliner *soops, const bool toggle) { TreeStoreElem *tselem = TREESTORE(te); - const short new_flag = toggle ? (tselem->flag ^ TSE_SELECTED) : (tselem->flag | TSE_SELECTED); + const short new_flag = (toggle && (tselem->flag & TSE_ACTIVE)) ? (tselem->flag ^ TSE_SELECTED) : + (tselem->flag | TSE_SELECTED); if (extend == false) { outliner_flag_set(&soops->tree, TSE_SELECTED, false); @@ -1318,6 +1331,10 @@ static int outliner_item_do_activate_from_cursor(bContext *C, ED_region_tag_redraw_no_rebuild(ar); } ED_undo_push(C, "Outliner selection change"); + + if (soops->flag & SO_SYNC_SELECT) { + ED_outliner_select_sync_from_outliner(C, soops); + } } return OPERATOR_FINISHED; @@ -1402,6 +1419,10 @@ static int outliner_box_select_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); ED_region_tag_redraw(ar); + if (soops->flag & SO_SYNC_SELECT) { + ED_outliner_select_sync_from_outliner(C, soops); + } + return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_outliner/outliner_sync.c b/source/blender/editors/space_outliner/outliner_sync.c new file mode 100644 index 00000000000..a8aeb7ea4e1 --- /dev/null +++ b/source/blender/editors/space_outliner/outliner_sync.c @@ -0,0 +1,548 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2004 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup spoutliner + */ + +#include + +#include "DNA_armature_types.h" +#include "DNA_layer_types.h" +#include "DNA_outliner_types.h" +#include "DNA_screen_types.h" +#include "DNA_sequence_types.h" +#include "DNA_space_types.h" + +#include "BLI_compiler_compat.h" +#include "BLI_ghash.h" + +#include "BKE_armature.h" +#include "BKE_context.h" +#include "BKE_layer.h" +#include "BKE_main.h" +#include "BKE_sequencer.h" + +#include "DEG_depsgraph.h" + +#include "ED_armature.h" +#include "ED_object.h" +#include "ED_outliner.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "outliner_intern.h" + +/* Functions for tagging outliner selection syncing is dirty from operators */ +void ED_outliner_select_sync_from_object_tag(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_OBJECT; +} + +void ED_outliner_select_sync_from_edit_bone_tag(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE; +} + +void ED_outliner_select_sync_from_pose_bone_tag(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE; +} + +void ED_outliner_select_sync_from_sequence_tag(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE; +} + +bool ED_outliner_select_sync_is_dirty(const bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + return wm->outliner_sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_ALL; +} + +/* Copy sync select dirty flag from window manager to all outliners to be synced lazily on draw */ +void ED_outliner_select_sync_flag_outliners(const bContext *C) +{ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) { + for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) { + if (sl->spacetype == SPACE_OUTLINER) { + SpaceOutliner *soutliner = (SpaceOutliner *)sl; + + soutliner->sync_select_dirty |= wm->outliner_sync_select_dirty; + } + } + } + } + + /* Clear global sync flag */ + wm->outliner_sync_select_dirty = 0; +} + +/** + * Outliner sync select dirty flags are not enough to determine which types to sync, + * outliner display mode also needs to be considered. This stores the types of data + * to sync to increase code clarity. + */ +typedef struct SyncSelectTypes { + bool object; + bool edit_bone; + bool pose_bone; + bool sequence; +} SyncSelectTypes; + +/** + * Set which types of data to sync when syncing selection from the outliner based on object + * interaction mode and outliner display mode + */ +static void outliner_sync_select_from_outliner_set_types(bContext *C, + SpaceOutliner *soops, + SyncSelectTypes *sync_types) +{ + Object *obact = CTX_data_active_object(C); + Object *obedit = CTX_data_edit_object(C); + + const bool sequence_view = soops->outlinevis == SO_SEQUENCE; + + sync_types->object = !sequence_view && (obact && obact->mode == OB_MODE_OBJECT); + sync_types->edit_bone = !sequence_view && (obedit && obedit->type == OB_ARMATURE); + sync_types->pose_bone = !sequence_view && (obact && obact->mode == OB_MODE_POSE); + sync_types->sequence = sequence_view; +} + +/** + * Current dirty flags and outliner display mode determine which type of syncing should occur. + * This is to ensure sync flag data is not lost on sync in the wrong display mode. + */ +static void outliner_sync_select_to_outliner_set_types(const bContext *C, + SpaceOutliner *soops, + SyncSelectTypes *sync_types) +{ + Object *obact = CTX_data_active_object(C); + Object *obedit = CTX_data_edit_object(C); + + const bool sequence_view = soops->outlinevis == SO_SEQUENCE; + + sync_types->object = !sequence_view && (obact && obact->mode == OB_MODE_OBJECT) && + (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_OBJECT); + sync_types->edit_bone = !sequence_view && (obedit && obedit->type == OB_ARMATURE) && + (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE); + sync_types->pose_bone = !sequence_view && (obact && obact->mode == OB_MODE_POSE) && + (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE); + sync_types->sequence = sequence_view && + (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE); +} + +/** + * Stores items selected from a sync from the outliner. Prevents syncing the selection + * state of the last instance of an object linked in multiple collections. + */ +typedef struct SelectedItems { + GSet *objects; + GSet *edit_bones; + GSet *pose_bones; +} SelectedItems; + +static void selected_items_init(SelectedItems *selected_items) +{ + selected_items->objects = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + selected_items->edit_bones = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + selected_items->pose_bones = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); +} + +static void selected_items_free(SelectedItems *selected_items) +{ + BLI_gset_free(selected_items->objects, NULL); + BLI_gset_free(selected_items->edit_bones, NULL); + BLI_gset_free(selected_items->pose_bones, NULL); +} + +/* Check if an instance of this object been selected by the sync */ +static bool is_object_selected(GSet *selected_objects, Base *base) +{ + return BLI_gset_haskey(selected_objects, base); +} + +/* Check if an instance of this edit bone been selected by the sync */ +static bool is_edit_bone_selected(GSet *selected_ebones, EditBone *ebone) +{ + return BLI_gset_haskey(selected_ebones, ebone); +} + +/* Check if an instance of this pose bone been selected by the sync */ +static bool is_pose_bone_selected(GSet *selected_pbones, bPoseChannel *pchan) +{ + return BLI_gset_haskey(selected_pbones, pchan); +} + +/* Add element's data to selected item set */ +static void add_selected_item(GSet *selected, void *data) +{ + BLI_gset_add(selected, data); +} + +static void outliner_select_sync_to_object(ViewLayer *view_layer, + TreeElement *te, + TreeStoreElem *tselem, + GSet *selected_objects) +{ + Object *ob = (Object *)tselem->id; + Base *base = (te->directdata) ? (Base *)te->directdata : + BKE_view_layer_base_find(view_layer, ob); + + if (base && (base->flag & BASE_SELECTABLE)) { + if (tselem->flag & TSE_SELECTED) { + ED_object_base_select(base, BA_SELECT); + + add_selected_item(selected_objects, base); + } + else if (!is_object_selected(selected_objects, base)) { + ED_object_base_select(base, BA_DESELECT); + } + } +} + +static void outliner_select_sync_to_edit_bone(ViewLayer *view_layer, + TreeElement *te, + TreeStoreElem *tselem, + GSet *selected_ebones) +{ + bArmature *arm = (bArmature *)tselem->id; + EditBone *ebone = (EditBone *)te->directdata; + + short bone_flag = ebone->flag; + + if (EBONE_SELECTABLE(arm, ebone)) { + if (tselem->flag & TSE_SELECTED) { + ebone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + + add_selected_item(selected_ebones, ebone); + } + else if (!is_edit_bone_selected(selected_ebones, ebone)) { + ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + } + } + + /* Tag if selection changed */ + if (bone_flag != ebone->flag) { + Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); + DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); + WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, obedit); + } +} + +static void outliner_select_sync_to_pose_bone(TreeElement *te, + TreeStoreElem *tselem, + GSet *selected_pbones) +{ + Object *ob = (Object *)tselem->id; + bArmature *arm = ob->data; + bPoseChannel *pchan = (bPoseChannel *)te->directdata; + + short bone_flag = pchan->bone->flag; + + if (PBONE_SELECTABLE(arm, pchan->bone)) { + if (tselem->flag & TSE_SELECTED) { + pchan->bone->flag |= BONE_SELECTED; + + add_selected_item(selected_pbones, pchan); + } + else if (!is_pose_bone_selected(selected_pbones, pchan)) { + pchan->bone->flag &= ~BONE_SELECTED; + } + } + + /* Tag if selection changed */ + if (bone_flag != pchan->bone->flag) { + DEG_id_tag_update(&arm->id, ID_RECALC_SELECT); + WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, ob); + } +} + +static void outliner_select_sync_to_sequence(Scene *scene, TreeStoreElem *tselem) +{ + Sequence *seq = (Sequence *)tselem->id; + + if (tselem->flag & TSE_ACTIVE) { + BKE_sequencer_active_set(scene, seq); + } + + if (tselem->flag & TSE_SELECTED) { + seq->flag |= SELECT; + } + else { + seq->flag &= ~SELECT; + } +} + +/** Sync select and active flags from outliner to active view layer, bones, and sequencer. */ +static void outliner_sync_selection_from_outliner(Scene *scene, + ViewLayer *view_layer, + ListBase *tree, + const SyncSelectTypes *sync_types, + SelectedItems *selected_items) +{ + + for (TreeElement *te = tree->first; te; te = te->next) { + TreeStoreElem *tselem = TREESTORE(te); + + if (tselem->type == 0 && te->idcode == ID_OB) { + if (sync_types->object) { + outliner_select_sync_to_object(view_layer, te, tselem, selected_items->objects); + } + } + else if (tselem->type == TSE_EBONE) { + if (sync_types->edit_bone) { + outliner_select_sync_to_edit_bone(view_layer, te, tselem, selected_items->edit_bones); + } + } + else if (tselem->type == TSE_POSE_CHANNEL) { + if (sync_types->pose_bone) { + outliner_select_sync_to_pose_bone(te, tselem, selected_items->pose_bones); + } + } + else if (tselem->type == TSE_SEQUENCE) { + if (sync_types->sequence) { + outliner_select_sync_to_sequence(scene, tselem); + } + } + + outliner_sync_selection_from_outliner( + scene, view_layer, &te->subtree, sync_types, selected_items); + } +} + +/* Set clean outliner and mark other outliners for syncing */ +void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *soops) +{ + /* Don't sync in certain outliner display modes */ + if (ELEM(soops->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS)) { + return; + } + + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + SyncSelectTypes sync_types; + outliner_sync_select_from_outliner_set_types(C, soops, &sync_types); + + /* To store elements that have been selected to prevent linked object sync errors */ + SelectedItems selected_items; + + selected_items_init(&selected_items); + + outliner_sync_selection_from_outliner( + scene, view_layer, &soops->tree, &sync_types, &selected_items); + + selected_items_free(&selected_items); + + /* Tag for updates */ + if (sync_types.object) { + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + } + if (sync_types.sequence) { + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); + } + + /* Clear outliner sync select dirty flag to prevent a sync to the outliner on draw */ + soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_ALL; +} + +static void outliner_select_sync_from_object(ViewLayer *view_layer, + SpaceOutliner *soops, + Object *obact, + TreeElement *te, + TreeStoreElem *tselem) +{ + Object *ob = (Object *)tselem->id; + Base *base = (te->directdata) ? (Base *)te->directdata : + BKE_view_layer_base_find(view_layer, ob); + const bool is_selected = (base != NULL) && ((base->flag & BASE_SELECTED) != 0); + + if (base && (ob == obact)) { + outliner_element_activate(soops, tselem); + } + + if (is_selected) { + tselem->flag |= TSE_SELECTED; + } + else { + tselem->flag &= ~TSE_SELECTED; + } +} + +static void outliner_select_sync_from_edit_bone(SpaceOutliner *soops, + EditBone *ebone_active, + TreeElement *te, + TreeStoreElem *tselem) +{ + EditBone *ebone = (EditBone *)te->directdata; + + if (ebone == ebone_active) { + outliner_element_activate(soops, tselem); + } + + if (ebone->flag & BONE_SELECTED) { + tselem->flag |= TSE_SELECTED; + } + else { + tselem->flag &= ~TSE_SELECTED; + } +} + +static void outliner_select_sync_from_pose_bone(SpaceOutliner *soops, + bPoseChannel *pchan_active, + TreeElement *te, + TreeStoreElem *tselem) +{ + bPoseChannel *pchan = (bPoseChannel *)te->directdata; + Bone *bone = pchan->bone; + + if (pchan == pchan_active) { + outliner_element_activate(soops, tselem); + } + + if (bone->flag & BONE_SELECTED) { + tselem->flag |= TSE_SELECTED; + } + else { + tselem->flag &= ~TSE_SELECTED; + } +} + +static void outliner_select_sync_from_sequence(SpaceOutliner *soops, + Sequence *sequence_active, + TreeStoreElem *tselem) +{ + Sequence *seq = (Sequence *)tselem->id; + + if (seq == sequence_active) { + outliner_element_activate(soops, tselem); + } + + if (seq->flag & SELECT) { + tselem->flag |= TSE_SELECTED; + } + else { + tselem->flag &= ~TSE_SELECTED; + } +} + +/** + * Contains active object, bones, and sequence for syncing to prevent getting active data + * repeatedly throughout syncing to the outliner. + */ +typedef struct SyncSelectActiveData { + Object *object; + EditBone *edit_bone; + bPoseChannel *pose_channel; + Sequence *sequence; +} SyncSelectActiveData; + +/** Sync select and active flags from active view layer, bones, and sequences to the outliner. */ +static void outliner_sync_selection_to_outliner(ViewLayer *view_layer, + SpaceOutliner *soops, + ListBase *tree, + SyncSelectActiveData *active_data, + const SyncSelectTypes *sync_types) +{ + for (TreeElement *te = tree->first; te; te = te->next) { + TreeStoreElem *tselem = TREESTORE(te); + + if (tselem->type == 0 && te->idcode == ID_OB) { + if (sync_types->object) { + outliner_select_sync_from_object(view_layer, soops, active_data->object, te, tselem); + } + } + else if (tselem->type == TSE_EBONE) { + if (sync_types->edit_bone) { + outliner_select_sync_from_edit_bone(soops, active_data->edit_bone, te, tselem); + } + } + else if (tselem->type == TSE_POSE_CHANNEL) { + if (sync_types->pose_bone) { + outliner_select_sync_from_pose_bone(soops, active_data->pose_channel, te, tselem); + } + } + else if (tselem->type == TSE_SEQUENCE) { + if (sync_types->sequence) { + outliner_select_sync_from_sequence(soops, active_data->sequence, tselem); + } + } + else { + tselem->flag &= ~TSE_SELECTED; + } + + /* Sync subtree elements */ + outliner_sync_selection_to_outliner(view_layer, soops, &te->subtree, active_data, sync_types); + } +} + +/* Get active data from context */ +static void get_sync_select_active_data(const bContext *C, SyncSelectActiveData *active_data) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + active_data->object = OBACT(view_layer); + active_data->edit_bone = CTX_data_active_bone(C); + active_data->pose_channel = CTX_data_active_pose_bone(C); + active_data->sequence = BKE_sequencer_active_get(scene); +} + +/* If outliner is dirty sync selection from view layer and sequwncer */ +void outliner_sync_selection(const bContext *C, SpaceOutliner *soops) +{ + if (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_ALL) { + ViewLayer *view_layer = CTX_data_view_layer(C); + + /* Set which types of data to sync from sync dirty flag and outliner display mode */ + SyncSelectTypes sync_types; + outliner_sync_select_to_outliner_set_types(C, soops, &sync_types); + + /* Store active object, bones, and sequence */ + SyncSelectActiveData active_data; + get_sync_select_active_data(C, &active_data); + + outliner_sync_selection_to_outliner( + view_layer, soops, &soops->tree, &active_data, &sync_types); + + /* Keep any unsynced data in the dirty flag */ + if (sync_types.object) { + soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_OBJECT; + } + if (sync_types.edit_bone) { + soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE; + } + if (sync_types.pose_bone) { + soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE; + } + if (sync_types.sequence) { + soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE; + } + } +} diff --git a/source/blender/editors/space_outliner/space_outliner.c b/source/blender/editors/space_outliner/space_outliner.c index 091efc56c09..48180888f62 100644 --- a/source/blender/editors/space_outliner/space_outliner.c +++ b/source/blender/editors/space_outliner/space_outliner.c @@ -304,6 +304,8 @@ static SpaceLink *outliner_new(const ScrArea *UNUSED(area), const Scene *UNUSED( soutliner->filter_id_type = ID_GR; soutliner->show_restrict_flags = SO_RESTRICT_ENABLE | SO_RESTRICT_HIDE; soutliner->outlinevis = SO_VIEW_LAYER; + soutliner->sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_ALL; + soutliner->flag |= SO_SYNC_SELECT; /* header */ ar = MEM_callocN(sizeof(ARegion), "header for outliner"); @@ -349,6 +351,9 @@ static SpaceLink *outliner_duplicate(SpaceLink *sl) soutlinern->treestore = NULL; soutlinern->treehash = NULL; + soutlinern->flag |= (soutliner->flag & SO_SYNC_SELECT); + soutlinern->sync_select_dirty = WM_OUTLINER_SYNC_SELECT_FROM_ALL; + return (SpaceLink *)soutlinern; } diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index 57f86059d9d..affb6d3fd88 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -41,6 +41,7 @@ /* for menu/popup icons etc etc*/ +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_sequencer.h" #include "ED_select_utils.h" @@ -49,6 +50,7 @@ /* own include */ #include "sequencer_intern.h" + static void *find_nearest_marker(int UNUSED(d1), int UNUSED(d2)) { return NULL; @@ -254,6 +256,8 @@ static int sequencer_de_select_all_exec(bContext *C, wmOperator *op) } } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -293,6 +297,8 @@ static int sequencer_select_inverse_exec(bContext *C, wmOperator *UNUSED(op)) } } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -542,6 +548,8 @@ static int sequencer_select_invoke(bContext *C, wmOperator *op, const wmEvent *e } } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); /* allowing tweaks */ @@ -668,6 +676,8 @@ static int sequencer_select_more_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -699,6 +709,8 @@ static int sequencer_select_less_exec(bContext *C, wmOperator *UNUSED(op)) return OPERATOR_CANCELLED; } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -750,6 +762,8 @@ static int sequencer_select_linked_pick_invoke(bContext *C, wmOperator *op, cons selected = select_more_less_seq__internal(scene, 1, 1); } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -784,6 +798,8 @@ static int sequencer_select_linked_exec(bContext *C, wmOperator *UNUSED(op)) selected = select_more_less_seq__internal(scene, true, true); } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -832,6 +848,8 @@ static int sequencer_select_handles_exec(bContext *C, wmOperator *op) } } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -876,6 +894,8 @@ static int sequencer_select_active_side_exec(bContext *C, wmOperator *op) select_active_side( ed->seqbasep, RNA_enum_get(op->ptr, "side"), seq_act->machine, seq_act->startdisp); + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -934,6 +954,8 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op) } } + ED_outliner_select_sync_from_sequence_tag(C); + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; @@ -1311,6 +1333,7 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op) } if (changed) { + ED_outliner_select_sync_from_sequence_tag(C); WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index 725f655c8ce..37ea10773bf 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -88,6 +88,7 @@ #include "ED_particle.h" #include "ED_mesh.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_sculpt.h" @@ -1280,9 +1281,15 @@ static bool view3d_lasso_select( } else if (ob && (ob->mode & OB_MODE_POSE)) { changed_multi |= do_lasso_select_pose(vc, mcords, moves, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } } else { changed_multi |= do_lasso_select_objects(vc, mcords, moves, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_object_tag(C); + } } } else { /* Edit Mode */ @@ -1303,6 +1310,9 @@ static bool view3d_lasso_select( break; case OB_ARMATURE: changed = do_lasso_select_armature(vc, mcords, moves, sel_op); + if (changed) { + ED_outliner_select_sync_from_edit_bone_tag(C); + } break; case OB_MBALL: changed = do_lasso_select_meta(vc, mcords, moves, sel_op); @@ -1488,6 +1498,9 @@ static int object_select_menu_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene); + + ED_outliner_select_sync_from_object_tag(C); + return OPERATOR_FINISHED; } else { @@ -2350,6 +2363,9 @@ static int view3d_select_exec(bContext *C, wmOperator *op) if (!retval && deselect_all) { retval = ED_armature_edit_deselect_all_visible_multi(C); } + if (retval) { + ED_outliner_select_sync_from_edit_bone_tag(C); + } } else if (obedit->type == OB_LATTICE) { retval = ED_lattice_select_pick(C, location, extend, deselect, toggle); @@ -2410,6 +2426,15 @@ static int view3d_select_exec(bContext *C, wmOperator *op) DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); } } + + if (retval) { + if (obact && obact->mode & OB_MODE_POSE) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } + else { + ED_outliner_select_sync_from_object_tag(C); + } + } } /* Pass-through allows tweaks @@ -3230,6 +3255,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) if (changed) { DEG_id_tag_update(&vc.obedit->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, vc.obedit); + ED_outliner_select_sync_from_edit_bone_tag(C); } break; case OB_LATTICE: @@ -3264,9 +3290,15 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) } else if (vc.obact && vc.obact->mode & OB_MODE_POSE) { changed_multi = do_pose_box_select(C, &vc, &rect, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_pose_bone_tag(C); + } } else { /* object mode with none active */ changed_multi = do_object_box_select(C, &vc, &rect, sel_op); + if (changed_multi) { + ED_outliner_select_sync_from_object_tag(C); + } } } @@ -3890,7 +3922,8 @@ static bool mball_circle_select(ViewContext *vc, /** Callbacks for circle selection in Editmode */ -static bool obedit_circle_select(ViewContext *vc, +static bool obedit_circle_select(bContext *C, + ViewContext *vc, wmGenericUserData *wm_userdata, const eSelectOp sel_op, const int mval[2], @@ -3911,6 +3944,9 @@ static bool obedit_circle_select(ViewContext *vc, break; case OB_ARMATURE: changed = armature_circle_select(vc, sel_op, mval, rad); + if (changed) { + ED_outliner_select_sync_from_edit_bone_tag(C); + } break; case OB_MBALL: changed = mball_circle_select(vc, sel_op, mval, rad); @@ -3999,7 +4035,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op) obedit = vc.obedit; if (obedit) { - obedit_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); + obedit_circle_select(C, &vc, wm_userdata, sel_op, mval, (float)radius); } else if (BKE_paint_select_face_test(obact)) { paint_facesel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius); @@ -4009,6 +4045,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op) } else if (obact->mode & OB_MODE_POSE) { pose_circle_select(&vc, sel_op, mval, (float)radius); + ED_outliner_select_sync_from_pose_bone_tag(C); } else { BLI_assert(0); @@ -4029,6 +4066,8 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op) if (object_circle_select(&vc, sel_op, mval, (float)radius)) { DEG_id_tag_update(&vc.scene->id, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc.scene); + + ED_outliner_select_sync_from_object_tag(C); } } diff --git a/source/blender/makesdna/DNA_outliner_types.h b/source/blender/makesdna/DNA_outliner_types.h index 1462d955f81..6e830987725 100644 --- a/source/blender/makesdna/DNA_outliner_types.h +++ b/source/blender/makesdna/DNA_outliner_types.h @@ -60,6 +60,8 @@ enum { TSE_DRAG_INTO = (1 << 6), TSE_DRAG_BEFORE = (1 << 7), TSE_DRAG_AFTER = (1 << 8), + /* Needed because outliner-only elements can be active */ + TSE_ACTIVE = (1 << 9), TSE_DRAG_ANY = (TSE_DRAG_INTO | TSE_DRAG_BEFORE | TSE_DRAG_AFTER), }; diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 505042432ed..707b312e967 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -244,7 +244,12 @@ typedef struct SpaceOutliner { char search_string[64]; struct TreeStoreElem search_tse; - short flag, outlinevis, storeflag, search_flags; + short flag, outlinevis, storeflag; + char search_flags; + + /** Selection syncing flag (#WM_OUTLINER_SYNC_SELECT_FROM_OBJECT and similar flags). */ + char sync_select_dirty; + int filter; char filter_state; char show_restrict_flags; @@ -263,6 +268,7 @@ typedef enum eSpaceOutliner_Flag { SO_FLAG_UNUSED_1 = (1 << 2), /* cleared */ SO_HIDE_KEYINGSETINFO = (1 << 3), SO_SKIP_SORT_ALPHA = (1 << 4), + SO_SYNC_SELECT = (1 << 5), } eSpaceOutliner_Flag; /* SpaceOutliner.filter */ diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index 8dcae41aaa2..cacc79608ad 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -131,12 +131,15 @@ typedef struct wmWindowManager { ListBase windows; /** Set on file read. */ - int initialized; + short initialized; /** Indicator whether data was saved. */ short file_saved; /** Operator stack depth to avoid nested undo pushes. */ short op_undo_depth; + /** Set after selection to notify outliner to sync. Stores type of selection */ + short outliner_sync_select_dirty; + /** Operator registry. */ ListBase operators; @@ -186,6 +189,18 @@ enum { WM_KEYCONFIG_IS_INITIALIZED = (1 << 1), }; +/* wmWindowManager.outliner_sync_select_dirty */ +enum { + WM_OUTLINER_SYNC_SELECT_FROM_OBJECT = (1 << 0), + WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE = (1 << 1), + WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE = (1 << 2), + WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE = (1 << 3), +}; + +#define WM_OUTLINER_SYNC_SELECT_FROM_ALL \ + (WM_OUTLINER_SYNC_SELECT_FROM_OBJECT | WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE | \ + WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE | WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE) + #define WM_KEYCONFIG_STR_DEFAULT "blender" /* IME is win32 only! */ diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 28f64e4e1f0..87472081f79 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -2815,6 +2815,12 @@ static void rna_def_space_outliner(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Sort Alphabetically", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL); + prop = RNA_def_property(srna, "use_sync_select", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SO_SYNC_SELECT); + RNA_def_property_ui_text( + prop, "Sync Outliner Selection", "Sync outliner selection with other editors"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL); + /* Granular restriction column option. */ prop = RNA_def_property(srna, "show_restrict_column_enable", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "show_restrict_flags", SO_RESTRICT_ENABLE); diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 77ca48fc05d..72a3455b120 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -2297,6 +2297,11 @@ static void rna_def_userdef_theme_space_outliner(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Selected Highlight", ""); RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + prop = RNA_def_property(srna, "active", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text(prop, "Active Highlight", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + prop = RNA_def_property(srna, "selected_object", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_array(prop, 3); RNA_def_property_ui_text(prop, "Selected Objects", "");