diff options
author | Nathan Craddock <nzcraddock@gmail.com> | 2019-08-09 00:37:32 +0300 |
---|---|---|
committer | Nathan Craddock <nzcraddock@gmail.com> | 2019-08-16 21:30:54 +0300 |
commit | 01006aa45d1afc9f45d2f6a42f6981f5c92af051 (patch) | |
tree | e536491fb252a2728363e65d6aea55ebc5ec8f67 /source/blender | |
parent | bf95ab6bb24103e67856e76f9c3e058bf3e9e6de (diff) |
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.
Diffstat (limited to 'source/blender')
6 files changed, 211 insertions, 28 deletions
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index a2fe131198d..a3b1c03a1b8 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -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; diff --git a/source/blender/editors/space_outliner/outliner_edit.c b/source/blender/editors/space_outliner/outliner_edit.c index 57fea544c77..842502ec82f 100644 --- a/source/blender/editors/space_outliner/outliner_edit.c +++ b/source/blender/editors/space_outliner/outliner_edit.c @@ -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)) { diff --git a/source/blender/editors/space_outliner/outliner_intern.h b/source/blender/editors/space_outliner/outliner_intern.h index c12fdcc6d1f..72cd8dc6424 100644 --- a/source/blender/editors/space_outliner/outliner_intern.h +++ b/source/blender/editors/space_outliner/outliner_intern.h @@ -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, diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c index 38dfe439b9c..ddb943d6c66 100644 --- a/source/blender/editors/space_outliner/outliner_select.c +++ b/source/blender/editors/space_outliner/outliner_select.c @@ -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); diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index f9905cc4fcd..582a9cad312 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -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), diff --git a/source/blender/editors/space_outliner/outliner_utils.c b/source/blender/editors/space_outliner/outliner_utils.c index 727d9866793..d39a9387a6a 100644 --- a/source/blender/editors/space_outliner/outliner_utils.c +++ b/source/blender/editors/space_outliner/outliner_utils.c @@ -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]) { |