Outliner: Allow select on row icons

Allow selection of subtree elements on a collapsed element's
row. Because subtree elements are aggregated by type, a select on
an icon that represents multiple subtree elements will invoke a
popup menu to select the specific subtree element.

Also draws highlights on cursor hover over a row icon.

Any child elements that are linked to multiple collections will
only be listed in the popup one time, and selection from the
popup will select the first instance in the subtree.
This commit is contained in:
Nathan Craddock 2019-08-08 15:37:32 -06:00
parent bf95ab6bb2
commit 01006aa45d
6 changed files with 211 additions and 28 deletions

View File

@ -2590,7 +2590,11 @@ static void tselem_draw_icon(uiBlock *block,
return;
}
/* Icon is covered by restrict buttons */
if (!is_clickable || x >= xmax) {
/* Reduce alpha to match icon buttons */
alpha *= 0.8f;
/* placement of icons, copied from interface_widgets.c */
float aspect = (0.8f * UI_UNIT_Y) / ICON_DEFAULT_HEIGHT;
x += 2.0f * aspect;
@ -2698,7 +2702,6 @@ static void outliner_draw_iconrow_doit(uiBlock *block,
float ufac = UI_UNIT_X / 20.0f;
float icon_color[4], icon_border[4];
outliner_icon_background_colors(icon_color, icon_border);
icon_color[3] *= alpha_fac;
if (active == OL_DRAWSEL_ACTIVE) {
UI_GetThemeColor4fv(TH_EDITED_OBJECT, icon_color);
icon_border[3] = 0.3f;
@ -2723,6 +2726,9 @@ static void outliner_draw_iconrow_doit(uiBlock *block,
GPU_blend(true); /* Roundbox disables. */
}
if (tselem->flag & TSE_HIGHLIGHTED) {
alpha_fac += 0.5;
}
tselem_draw_icon(block, xmax, (float)*offsx, (float)ys, tselem, te, alpha_fac, false);
te->xs = *offsx;
te->ys = ys;
@ -2730,7 +2736,12 @@ static void outliner_draw_iconrow_doit(uiBlock *block,
if (num_elements > 1) {
outliner_draw_iconrow_number(fstyle, *offsx, ys, num_elements);
te->flag |= TE_ICONROW_MERGED;
}
else {
te->flag |= TE_ICONROW;
}
(*offsx) += UI_UNIT_X;
}
@ -2740,7 +2751,7 @@ static void outliner_draw_iconrow_doit(uiBlock *block,
* We use a continuum of indices until we get to the object data-blocks
* and we then make room for the object types.
*/
static int tree_element_id_type_to_index(TreeElement *te)
int tree_element_id_type_to_index(TreeElement *te)
{
TreeStoreElem *tselem = TREESTORE(te);
@ -2870,7 +2881,7 @@ static void outliner_set_coord_tree_element(TreeElement *te, int startx, int sta
TreeElement *ten;
/* closed items may be displayed in row of parent, don't change their coordinate! */
if ((te->flag & TE_ICONROW) == 0) {
if ((te->flag & TE_ICONROW) == 0 && (te->flag & TE_ICONROW_MERGED) == 0) {
/* store coord and continue, we need coordinates for elements outside view too */
te->xs = startx;
te->ys = starty;

View File

@ -101,9 +101,15 @@ static int outliner_highlight_update(bContext *C, wmOperator *UNUSED(op), const
ARegion *ar = CTX_wm_region(C);
SpaceOutliner *soops = CTX_wm_space_outliner(C);
const float my = UI_view2d_region_to_view_y(&ar->v2d, event->mval[1]);
TreeElement *hovered_te = outliner_find_item_at_y(soops, &soops->tree, my);
float view_mval[2];
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &view_mval[0], &view_mval[1]);
TreeElement *hovered_te = outliner_find_item_at_y(soops, &soops->tree, view_mval[1]);
if (hovered_te) {
hovered_te = outliner_find_item_at_x_in_row(soops, hovered_te, view_mval[0], NULL);
}
bool changed = false;
if (!hovered_te || !(hovered_te->store_elem->flag & TSE_HIGHLIGHTED)) {

View File

@ -131,6 +131,9 @@ enum {
TE_DISABLED = (1 << 4),
TE_DRAGGING = (1 << 5),
TE_CHILD_NOT_IN_COLLECTION = (1 << 6),
/* Child elements of the same type in the iconrow are drawn merged as one icon.
* TE_ICONROW_MERGED is set for an element that is part of these merged child icons. */
TE_ICONROW_MERGED = (1 << 7),
};
/* button events */
@ -223,6 +226,8 @@ void outliner_collection_isolate_flag(struct Scene *scene,
const char *propname,
const bool value);
int tree_element_id_type_to_index(TreeElement *te);
/* outliner_select.c -------------------------------------------- */
eOLDrawState tree_element_type_active(struct bContext *C,
struct Scene *scene,
@ -255,6 +260,8 @@ void outliner_object_mode_toggle(struct bContext *C,
void outliner_element_activate(struct SpaceOutliner *soops, struct TreeStoreElem *tselem);
bool outliner_item_is_co_within_close_toggle(TreeElement *te, float view_co_x);
/* outliner_edit.c ---------------------------------------------- */
typedef void (*outliner_operation_cb)(struct bContext *C,
struct ReportList *,
@ -382,6 +389,10 @@ void OUTLINER_OT_orphans_purge(struct wmOperatorType *ot);
/* outliner_tools.c ---------------------------------------------- */
void merged_element_search_menu_invoke(struct bContext *C,
TreeElement *parent_te,
TreeElement *activate_te);
void OUTLINER_OT_operation(struct wmOperatorType *ot);
void OUTLINER_OT_scene_operation(struct wmOperatorType *ot);
void OUTLINER_OT_object_operation(struct wmOperatorType *ot);
@ -441,7 +452,8 @@ TreeElement *outliner_find_item_at_y(const SpaceOutliner *soops,
float view_co_y);
TreeElement *outliner_find_item_at_x_in_row(const SpaceOutliner *soops,
const TreeElement *parent_te,
float view_co_x);
float view_co_x,
bool *multiple_objects);
TreeElement *outliner_find_tse(struct SpaceOutliner *soops, const TreeStoreElem *tse);
TreeElement *outliner_find_tree_element(ListBase *lb, const TreeStoreElem *store_elem);
TreeElement *outliner_find_parent_element(ListBase *lb,

View File

@ -1246,12 +1246,6 @@ static void outliner_item_toggle_closed(TreeElement *te, const bool toggle_child
}
}
static bool outliner_item_is_co_within_close_toggle(TreeElement *te, float view_co_x)
{
return ((te->flag & TE_ICONROW) == 0) && (view_co_x > te->xs) &&
(view_co_x < te->xs + UI_UNIT_X);
}
static bool outliner_is_co_within_restrict_columns(const SpaceOutliner *soops,
const ARegion *ar,
float view_co_x)
@ -1313,8 +1307,18 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
else {
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
/* the row may also contain children, if one is hovered we want this instead of current te */
TreeElement *activate_te = outliner_find_item_at_x_in_row(soops, te, view_mval[0]);
/* The row may also contain children, if one is hovered we want this instead of current te */
bool merged_elements = false;
TreeElement *activate_te = outliner_find_item_at_x_in_row(
soops, te, view_mval[0], &merged_elements);
/* If the selected icon was an aggregate of multiple elements, run the search popup */
if (merged_elements) {
merged_element_search_menu_invoke(C, te, activate_te);
return OPERATOR_CANCELLED;
}
TreeStoreElem *activate_tselem = TREESTORE(activate_te);
outliner_item_select(soops, activate_te, extend, extend);

View File

@ -63,6 +63,7 @@
#include "ED_armature.h"
#include "ED_object.h"
#include "ED_outliner.h"
#include "ED_scene.h"
#include "ED_screen.h"
#include "ED_sequencer.h"
@ -478,6 +479,127 @@ void OUTLINER_OT_scene_operation(wmOperatorType *ot)
}
/* ******************************************** */
/* Stores the parent and a child element of a merged iconrow icon for
* the merged select popup menu. The subtree of the parent is searched and
* the child is needed to only show elements of the same type in the popup. */
typedef struct MergedSearchData {
TreeElement *parent_element;
TreeElement *select_element;
} MergedSearchData;
static void merged_element_search_cb_recursive(
const ListBase *tree, short tselem_type, short type, const char *str, uiSearchItems *items)
{
char name[64];
int iconid;
for (TreeElement *te = tree->first; te; te = te->next) {
TreeStoreElem *tselem = TREESTORE(te);
if (tree_element_id_type_to_index(te) == type && tselem_type == tselem->type) {
if (BLI_strcasestr(te->name, str)) {
BLI_strncpy(name, te->name, 64);
iconid = tree_element_get_icon(tselem, te).icon;
/* Don't allow duplicate named items */
if (UI_search_items_find_index(items, name) == -1) {
if (!UI_search_item_add(items, name, te, iconid)) {
break;
}
}
}
}
merged_element_search_cb_recursive(&te->subtree, tselem_type, type, str, items);
}
}
/* Get a list of elements that match the search string */
static void merged_element_search_cb(const bContext *UNUSED(C),
void *data,
const char *str,
uiSearchItems *items)
{
MergedSearchData *search_data = (MergedSearchData *)data;
TreeElement *parent = search_data->parent_element;
TreeElement *te = search_data->select_element;
int type = tree_element_id_type_to_index(te);
merged_element_search_cb_recursive(&parent->subtree, TREESTORE(te)->type, type, str, items);
}
/* Activate an element from the merged element search menu */
static void merged_element_search_call_cb(struct bContext *C, void *UNUSED(arg1), void *element)
{
SpaceOutliner *soops = CTX_wm_space_outliner(C);
TreeElement *te = (TreeElement *)element;
outliner_item_select(soops, te, false, false);
outliner_item_do_activate_from_tree_element(C, te, te->store_elem, false, false);
if (soops->flag & SO_SYNC_SELECT) {
ED_outliner_select_sync_from_outliner(C, soops);
}
}
/** Merged element search menu
* Created on activation of a merged or aggregated iconrow icon.
*/
static uiBlock *merged_element_search_menu(bContext *C, ARegion *ar, void *data)
{
static char search[64] = "";
uiBlock *block;
uiBut *but;
/* Clear search on each menu creation */
*search = '\0';
block = UI_block_begin(C, ar, __func__, UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_SEARCH_MENU);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
short menu_width = 10 * UI_UNIT_X;
but = uiDefSearchBut(
block, search, 0, ICON_VIEWZOOM, sizeof(search), 10, 10, menu_width, UI_UNIT_Y, 0, 0, "");
UI_but_func_search_set(
but, NULL, merged_element_search_cb, data, false, merged_element_search_call_cb, NULL);
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
/* Fake button to hold space for search items */
uiDefBut(block,
UI_BTYPE_LABEL,
0,
"",
10,
10 - UI_searchbox_size_y(),
menu_width,
UI_searchbox_size_y(),
NULL,
0,
0,
0,
0,
NULL);
/* Center the menu on the cursor */
UI_block_bounds_set_popup(block, 6, (const int[2]){-(menu_width / 2), 0});
return block;
}
void merged_element_search_menu_invoke(bContext *C,
TreeElement *parent_te,
TreeElement *activate_te)
{
MergedSearchData *select_data = MEM_callocN(sizeof(MergedSearchData), "merge_search_data");
select_data->parent_element = parent_te;
select_data->select_element = activate_te;
UI_popup_block_invoke(C, merged_element_search_menu, select_data, MEM_freeN);
}
static void object_select_cb(bContext *C,
ReportList *UNUSED(reports),
Scene *UNUSED(scene),

View File

@ -66,6 +66,38 @@ TreeElement *outliner_find_item_at_y(const SpaceOutliner *soops,
return NULL;
}
static TreeElement *outliner_find_item_at_x_in_row_recursive(const TreeElement *parent_te,
float view_co_x,
bool *r_merged)
{
TreeElement *child_te = parent_te->subtree.first;
bool over_element = false;
while (child_te) {
over_element = (view_co_x > child_te->xs) && (view_co_x < child_te->xend);
if ((child_te->flag & TE_ICONROW) && over_element) {
return child_te;
}
else if ((child_te->flag & TE_ICONROW_MERGED) && over_element) {
if (r_merged) {
*r_merged = true;
}
return child_te;
}
TreeElement *te = outliner_find_item_at_x_in_row_recursive(child_te, view_co_x, r_merged);
if (te != child_te) {
return te;
}
child_te = child_te->next;
}
/* return parent if no child is hovered */
return (TreeElement *)parent_te;
}
/**
* Collapsed items can show their children as click-able icons. This function tries to find
* such an icon that represents the child item at x-coordinate \a view_co_x (view-space).
@ -74,24 +106,14 @@ TreeElement *outliner_find_item_at_y(const SpaceOutliner *soops,
*/
TreeElement *outliner_find_item_at_x_in_row(const SpaceOutliner *soops,
const TreeElement *parent_te,
float view_co_x)
float view_co_x,
bool *r_merged)
{
/* if parent_te is opened, it doesn't show childs in row */
/* if parent_te is opened, it doesn't show children in row */
if (!TSELEM_OPEN(TREESTORE(parent_te), soops)) {
/* no recursion, items can only display their direct children in the row */
for (TreeElement *child_te = parent_te->subtree.first;
/* don't look further if co_x is smaller than child position*/
child_te && view_co_x >= child_te->xs;
child_te = child_te->next) {
if ((child_te->flag & TE_ICONROW) && (view_co_x > child_te->xs) &&
(view_co_x < child_te->xend)) {
return child_te;
}
}
return outliner_find_item_at_x_in_row_recursive(parent_te, view_co_x, r_merged);
}
/* return parent if no child is hovered */
return (TreeElement *)parent_te;
}
@ -305,6 +327,12 @@ float outliner_restrict_columns_width(const SpaceOutliner *soops)
return (num_columns * UI_UNIT_X + V2D_SCROLL_WIDTH);
}
/* Find if x coordinate is over element disclosure toggle */
bool outliner_item_is_co_within_close_toggle(TreeElement *te, float view_co_x)
{
return (view_co_x > te->xs) && (view_co_x < te->xs + UI_UNIT_X);
}
/* Get base of object under cursor. Used for eyedropper tool */
Base *ED_outliner_give_base_under_cursor(bContext *C, const int mval[2])
{