Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Craddock <nzcraddock@gmail.com>2019-08-08 07:27:07 +0300
committerNathan Craddock <nzcraddock@gmail.com>2019-08-16 21:30:53 +0300
commit71eb65328078d3b3ca440a9a23a7aa08238a6710 (patch)
treeeae7a6c557af86911d867f4644a24a618394dc9a /source/blender/editors/space_outliner
parent7ba2720a70c83eb41f577aae4eb5a3403f6bb6b9 (diff)
Outliner: Synced selection and active element highlighting
Adds a toggle to the filter menu for outliner synced selection. Enabled by default, this ensures selection is synced between objects, bones, and sequences. An active outliner element theme color is added to indicate which element is active. Synced selection is controlled on the operator level. Each operator that modifies selection for objects, bones, sequences, or outliner elements needs to call the respective ED_outliner_select_sync_from.. function to tag outliners to be synced. Syncing is done lazily on outliner draw.
Diffstat (limited to 'source/blender/editors/space_outliner')
-rw-r--r--source/blender/editors/space_outliner/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c36
-rw-r--r--source/blender/editors/space_outliner/outliner_edit.c4
-rw-r--r--source/blender/editors/space_outliner/outliner_intern.h6
-rw-r--r--source/blender/editors/space_outliner/outliner_select.c27
-rw-r--r--source/blender/editors/space_outliner/outliner_sync.c548
-rw-r--r--source/blender/editors/space_outliner/space_outliner.c5
7 files changed, 620 insertions, 7 deletions
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 <stdio.h>
+
+#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;
}