Changeset View
Standalone View
source/blender/editors/space_outliner/outliner_tree.c
| Show First 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | |||||
| #include "ED_armature.h" | #include "ED_armature.h" | ||||
| #include "ED_screen.h" | #include "ED_screen.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "UI_interface.h" | |||||
| #include "outliner_intern.h" | #include "outliner_intern.h" | ||||
| #ifdef WIN32 | #ifdef WIN32 | ||||
| # include "BLI_math_base.h" /* M_PI */ | # include "BLI_math_base.h" /* M_PI */ | ||||
| #endif | #endif | ||||
| /* prototypes */ | /* prototypes */ | ||||
| static void outliner_add_layer_collections_recursive( | static void outliner_add_layer_collections_recursive( | ||||
| ▲ Show 20 Lines • Show All 1,635 Lines • ▼ Show 20 Lines | static void outliner_sort(ListBase *lb) | ||||
| for (te = lb->first; te; te = te->next) { | for (te = lb->first; te; te = te->next) { | ||||
| outliner_sort(&te->subtree); | outliner_sort(&te->subtree); | ||||
| } | } | ||||
| } | } | ||||
| /* Filtering ----------------------------------------------- */ | /* Filtering ----------------------------------------------- */ | ||||
| typedef struct OutlinerTreeElementFocus { | |||||
| bool valid; | |||||
| const char *name; | |||||
| int idcode; | |||||
| int elem_type; | |||||
| int ys; | |||||
| } OutlinerTreeElementFocus; | |||||
| static TreeElement *outliner_find_equivalent_element_recursive(ListBase *lb, OutlinerTreeElementFocus *data) | |||||
| { | |||||
| TreeElement *te; | |||||
| for (te = lb->first; te; te = te->next) { | |||||
| if ((te->idcode == data->idcode) && | |||||
| (TREESTORE(te)->type == data->elem_type) && | |||||
| STREQ(te->name, data->name)) | |||||
| { | |||||
| return te; | |||||
| } | |||||
| TreeElement *te_sub = outliner_find_equivalent_element_recursive(&te->subtree, data); | |||||
| if (te_sub != NULL) { | |||||
| return te_sub; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| /** | |||||
| * Finds a TreeElement that matches the properties (name, idtype, ...) of the one previously stored | |||||
| */ | |||||
| static TreeElement *outliner_find_equivalent_element(SpaceOops *soops, OutlinerTreeElementFocus *focus) | |||||
| { | |||||
| return outliner_find_equivalent_element_recursive(&soops->tree, focus); | |||||
| } | |||||
Severin: This whole lookup seems redundant, the `TreeStoreElem` representing an entity (object… | |||||
| /** | |||||
| * Bring the outliner scrolling back to where it was in relation to the original focus element | |||||
| */ | |||||
| static void outliner_restore_scrolling_position(SpaceOops *soops, ARegion *ar, OutlinerTreeElementFocus *focus) | |||||
| { | |||||
| View2D *v2d = &ar->v2d; | |||||
| int ytop; | |||||
| if (focus->valid) { | |||||
| outliner_set_coordinates(ar, soops); | |||||
| TreeElement *te_new = outliner_find_equivalent_element(soops, focus); | |||||
| if (te_new != NULL) { | |||||
| int ys_new, ys_old; | |||||
| ys_new = te_new->ys; | |||||
| ys_old = focus->ys; | |||||
| ytop = v2d->cur.ymax + (ys_new - ys_old) -1; | |||||
| if (ytop > 0) ytop = 0; | |||||
| v2d->cur.ymax = (float)ytop; | |||||
| v2d->cur.ymin = (float)(ytop - BLI_rcti_size_y(&v2d->mask)); | |||||
| } | |||||
| else { | |||||
| return; | |||||
| } | |||||
| soops->storeflag |= SO_TREESTORE_REDRAW; | |||||
| ED_region_tag_redraw(ar); | |||||
SeverinUnsubmitted Done Inline ActionsI'd rather have the caller handle redrawing (Doxygen function comment can note that). This function is only called through outliner_build_tree right now, which again is only called in draw_outliner. Hence, we tag for redraw while we prepare a redraw. Shouldn't cause issues in practice, still better to avoid. Severin: I'd rather have the caller handle redrawing (Doxygen function comment can note that). This… | |||||
| } | |||||
| } | |||||
| /** | |||||
| * Store information of current outliner scrolling status to be restored later | |||||
| * | |||||
| * Finds the top-most collection visible in the outliner and populates the OutlinerTreeElementFocus | |||||
| * struct to retrieve this element later to make sure it is in the same original position as before filtering | |||||
| */ | |||||
| static void outliner_store_scrolling_position(SpaceOops *soops, ARegion *ar, OutlinerTreeElementFocus *focus) | |||||
| { | |||||
| float cur = ar->v2d.cur.ymax; /* 0.0 if no scroll, -MAX if scrolled down.*/ | |||||
| float limit = ar->v2d.cur.ymin; | |||||
| outliner_set_coordinates(ar, soops); | |||||
| TreeElement *te; | |||||
| TreeStoreElem *tselem; | |||||
| while (true) { | |||||
| te = outliner_find_item_at_y(soops, &soops->tree, cur); | |||||
SeverinUnsubmitted Not Done Inline ActionsFor each iteration of this while loop, this does an recursive lookup starting from the first element in the tree root. Lots of redundant operations there. Also I think this function should early exit if the view isn't of type SO_ACT_LAYER or SO_COLLECTIONS? Severin: For each iteration of this while loop, this does an recursive lookup starting from the first… | |||||
| if (te == NULL) { | |||||
| break; | |||||
| } | |||||
| tselem = TREESTORE(te); | |||||
| if (ELEM(tselem->type, TSE_LAYER_COLLECTION, TSE_SCENE_COLLECTION)) { | |||||
| break; | |||||
| } | |||||
| if (cur < limit) { | |||||
| break; | |||||
| } | |||||
| cur -= UI_UNIT_Y; | |||||
| } | |||||
| if (te != NULL) { | |||||
| focus->valid = true; | |||||
| focus->name = te->name; | |||||
| focus->idcode = te->idcode; | |||||
| focus->elem_type = TREESTORE(te)->type; | |||||
| focus->ys = te->ys; | |||||
| } | |||||
| else { | |||||
| focus->valid = false; | |||||
| } | |||||
| } | |||||
| static int outliner_exclude_filter_get(SpaceOops *soops) | |||||
| { | |||||
| int exclude_filter = soops->filter; | |||||
| if (!ELEM(soops->outlinevis, SO_ACT_LAYER, SO_COLLECTIONS)) { | |||||
| exclude_filter &= ~SO_FILTER_NO_OBJECT; | |||||
| } | |||||
| if (soops->filter & SO_FILTER_NO_SEARCH) { | |||||
| if (soops->search_string[0] == 0) { | |||||
| exclude_filter &= ~SO_FILTER_NO_SEARCH; | |||||
| } | |||||
| } | |||||
| return exclude_filter; | |||||
| } | |||||
| static bool outliner_element_visible_get(TreeElement *te, const int exclude_filter) | |||||
| { | |||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
| if ((tselem->type == 0) && (te->idcode == ID_OB)) { | |||||
| if ((exclude_filter & SO_FILTER_NO_OBJECT)) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| static bool outliner_filter_has_name(TreeElement *te, const char *name, int flags) | static bool outliner_filter_has_name(TreeElement *te, const char *name, int flags) | ||||
| { | { | ||||
| int fn_flag = 0; | int fn_flag = 0; | ||||
| if ((flags & SO_FIND_CASE_SENSITIVE) == 0) | if ((flags & SO_FIND_CASE_SENSITIVE) == 0) | ||||
| fn_flag |= FNM_CASEFOLD; | fn_flag |= FNM_CASEFOLD; | ||||
| return fnmatch(name, te->name, fn_flag) == 0; | return fnmatch(name, te->name, fn_flag) == 0; | ||||
| } | } | ||||
| static int outliner_filter_tree(SpaceOops *soops, ListBase *lb) | static void outliner_filter_subtree_free(ListBase *lb, TreeElement *te) | ||||
| { | |||||
| outliner_free_tree(&te->subtree); | |||||
| BLI_remlink(lb, te); | |||||
| if (te->flag & TE_FREE_NAME) MEM_freeN((void *)te->name); | |||||
| MEM_freeN(te); | |||||
| } | |||||
| static int outliner_filter_subtree( | |||||
| SpaceOops *soops, ListBase *lb, const char *search_string, const int exclude_filter) | |||||
| { | { | ||||
| TreeElement *te, *ten; | TreeElement *te, *ten; | ||||
| TreeStoreElem *tselem; | TreeStoreElem *tselem; | ||||
| char search_buff[sizeof(((struct SpaceOops *)NULL)->search_string) + 2]; | |||||
| char *search_string; | |||||
| /* although we don't have any search string, we return true | for (te = lb->first; te; te = ten) { | ||||
| * since the entire tree is ok then... | ten = te->next; | ||||
SeverinUnsubmitted Not Done Inline ActionsSince you touch most of this block already, please give this a more useful name like te_next :) Severin: Since you touch most of this block already, please give this a more useful name like `te_next`… | |||||
| */ | |||||
| if (soops->search_string[0] == 0) | |||||
| return 1; | |||||
| if (soops->search_flags & SO_FIND_COMPLETE) { | if ((outliner_element_visible_get(te, exclude_filter) == false)) { | ||||
| search_string = soops->search_string; | outliner_filter_subtree_free(lb, te); | ||||
| continue; | |||||
| } | } | ||||
| else { | else if((exclude_filter & SO_FILTER_NO_SEARCH) == 0) { | ||||
| /* Implicitly add heading/trailing wildcards if needed. */ | /* Filter subtree too. */ | ||||
| BLI_strncpy_ensure_pad(search_buff, soops->search_string, '*', sizeof(search_buff)); | outliner_filter_subtree(soops, &te->subtree, search_string, exclude_filter); | ||||
| search_string = search_buff; | continue; | ||||
| } | } | ||||
| for (te = lb->first; te; te = ten) { | |||||
| ten = te->next; | |||||
| if (!outliner_filter_has_name(te, search_string, soops->search_flags)) { | if (!outliner_filter_has_name(te, search_string, soops->search_flags)) { | ||||
| /* item isn't something we're looking for, but... | /* item isn't something we're looking for, but... | ||||
| * - if the subtree is expanded, check if there are any matches that can be easily found | * - if the subtree is expanded, check if there are any matches that can be easily found | ||||
| * so that searching for "cu" in the default scene will still match the Cube | * so that searching for "cu" in the default scene will still match the Cube | ||||
| * - otherwise, we can't see within the subtree and the item doesn't match, | * - otherwise, we can't see within the subtree and the item doesn't match, | ||||
| * so these can be safely ignored (i.e. the subtree can get freed) | * so these can be safely ignored (i.e. the subtree can get freed) | ||||
| */ | */ | ||||
| tselem = TREESTORE(te); | tselem = TREESTORE(te); | ||||
| /* flag as not a found item */ | /* flag as not a found item */ | ||||
| tselem->flag &= ~TSE_SEARCHMATCH; | tselem->flag &= ~TSE_SEARCHMATCH; | ||||
| if ((!TSELEM_OPEN(tselem, soops)) || outliner_filter_tree(soops, &te->subtree) == 0) { | if ((!TSELEM_OPEN(tselem, soops)) || | ||||
| outliner_free_tree(&te->subtree); | outliner_filter_subtree(soops, &te->subtree, search_string, exclude_filter) == 0) | ||||
| BLI_remlink(lb, te); | { | ||||
| outliner_filter_subtree_free(lb, te); | |||||
| if (te->flag & TE_FREE_NAME) MEM_freeN((void *)te->name); | |||||
| MEM_freeN(te); | |||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| tselem = TREESTORE(te); | tselem = TREESTORE(te); | ||||
| /* flag as a found item - we can then highlight it */ | /* flag as a found item - we can then highlight it */ | ||||
| tselem->flag |= TSE_SEARCHMATCH; | tselem->flag |= TSE_SEARCHMATCH; | ||||
| /* filter subtree too */ | /* filter subtree too */ | ||||
| outliner_filter_tree(soops, &te->subtree); | outliner_filter_subtree(soops, &te->subtree, search_string, exclude_filter); | ||||
| } | } | ||||
| } | } | ||||
| /* if there are still items in the list, that means that there were still some matches */ | /* if there are still items in the list, that means that there were still some matches */ | ||||
| return (BLI_listbase_is_empty(lb) == false); | return (BLI_listbase_is_empty(lb) == false); | ||||
| } | } | ||||
| static void outliner_filter_tree(SpaceOops *soops, ARegion *ar, OutlinerTreeElementFocus *focus) | |||||
| { | |||||
| char search_buff[sizeof(((struct SpaceOops *)NULL)->search_string) + 2]; | |||||
SeverinUnsubmitted Not Done Inline ActionsWhy is this + 2 needed? (Better explain in code comment). Also, no need to cast NULL, you have a SpaceOops pointer is passed as argument. Severin: Why is this `+ 2` needed? (Better explain in code comment). Also, no need to cast `NULL`, you… | |||||
| char *search_string; | |||||
| const int exclude_filter = outliner_exclude_filter_get(soops); | |||||
| if (exclude_filter == 0) { | |||||
| outliner_restore_scrolling_position(soops, ar, focus); | |||||
| return; | |||||
| } | |||||
| if (soops->search_flags & SO_FIND_COMPLETE) { | |||||
| search_string = soops->search_string; | |||||
| } | |||||
| else { | |||||
| /* Implicitly add heading/trailing wildcards if needed. */ | |||||
| BLI_strncpy_ensure_pad(search_buff, soops->search_string, '*', sizeof(search_buff)); | |||||
| search_string = search_buff; | |||||
| } | |||||
| outliner_filter_subtree(soops, &soops->tree, search_string, exclude_filter); | |||||
| outliner_restore_scrolling_position(soops, ar, focus); | |||||
SeverinUnsubmitted Done Inline ActionsWhat does the scrolling position have to do with the filtering itself? What I mean is, scrolling shouldn't be handled in a function called outliner_filter_tree, either separately or the function should be called outliner_filter_tree_and_adjust_view. Severin: What does the scrolling position have to do with the filtering itself? What I mean is… | |||||
| } | |||||
| /* ======================================================= */ | /* ======================================================= */ | ||||
| /* Main Tree Building API */ | /* Main Tree Building API */ | ||||
| /* Main entry point for building the tree data-structure that the outliner represents */ | /* Main entry point for building the tree data-structure that the outliner represents */ | ||||
| // TODO: split each mode into its own function? | // TODO: split each mode into its own function? | ||||
| void outliner_build_tree(Main *mainvar, Scene *scene, ViewLayer *view_layer, SpaceOops *soops) | void outliner_build_tree(Main *mainvar, Scene *scene, ViewLayer *view_layer, SpaceOops *soops, ARegion *ar) | ||||
| { | { | ||||
| TreeElement *te = NULL, *ten; | TreeElement *te = NULL, *ten; | ||||
| TreeStoreElem *tselem; | TreeStoreElem *tselem; | ||||
| int show_opened = !soops->treestore || !BLI_mempool_count(soops->treestore); /* on first view, we open scenes */ | int show_opened = !soops->treestore || !BLI_mempool_count(soops->treestore); /* on first view, we open scenes */ | ||||
| /* Are we looking for something - we want to tag parents to filter child matches | /* Are we looking for something - we want to tag parents to filter child matches | ||||
| * - NOT in datablocks view - searching all datablocks takes way too long to be useful | * - NOT in datablocks view - searching all datablocks takes way too long to be useful | ||||
| * - this variable is only set once per tree build */ | * - this variable is only set once per tree build */ | ||||
| if (soops->search_string[0] != 0 && soops->outlinevis != SO_DATABLOCKS) | if (soops->search_string[0] != 0 && soops->outlinevis != SO_DATABLOCKS) | ||||
| soops->search_flags |= SO_SEARCH_RECURSIVE; | soops->search_flags |= SO_SEARCH_RECURSIVE; | ||||
| else | else | ||||
| soops->search_flags &= ~SO_SEARCH_RECURSIVE; | soops->search_flags &= ~SO_SEARCH_RECURSIVE; | ||||
| if (soops->treehash && (soops->storeflag & SO_TREESTORE_REBUILD)) { | if (soops->treehash && (soops->storeflag & SO_TREESTORE_REBUILD)) { | ||||
| soops->storeflag &= ~SO_TREESTORE_REBUILD; | soops->storeflag &= ~SO_TREESTORE_REBUILD; | ||||
| BKE_outliner_treehash_rebuild_from_treestore(soops->treehash, soops->treestore); | BKE_outliner_treehash_rebuild_from_treestore(soops->treehash, soops->treestore); | ||||
| } | } | ||||
| if (soops->tree.first && (soops->storeflag & SO_TREESTORE_REDRAW)) | if (soops->tree.first && (soops->storeflag & SO_TREESTORE_REDRAW)) | ||||
| return; | return; | ||||
| OutlinerTreeElementFocus focus; | |||||
| outliner_store_scrolling_position(soops, ar, &focus); | |||||
| outliner_free_tree(&soops->tree); | outliner_free_tree(&soops->tree); | ||||
| outliner_storage_cleanup(soops); | outliner_storage_cleanup(soops); | ||||
| /* options */ | /* options */ | ||||
| if (soops->outlinevis == SO_LIBRARIES) { | if (soops->outlinevis == SO_LIBRARIES) { | ||||
| Library *lib; | Library *lib; | ||||
| /* current file first - mainvar provides tselem with unique pointer - not used */ | /* current file first - mainvar provides tselem with unique pointer - not used */ | ||||
| ▲ Show 20 Lines • Show All 178 Lines • ▼ Show 20 Lines | if (BASACT(view_layer)) { | ||||
| ten = outliner_add_element(soops, &soops->tree, OBACT(view_layer), NULL, 0, 0); | ten = outliner_add_element(soops, &soops->tree, OBACT(view_layer), NULL, 0, 0); | ||||
| ten->directdata = BASACT(view_layer); | ten->directdata = BASACT(view_layer); | ||||
| } | } | ||||
| } | } | ||||
| if ((soops->flag & SO_SKIP_SORT_ALPHA) == 0) { | if ((soops->flag & SO_SKIP_SORT_ALPHA) == 0) { | ||||
| outliner_sort(&soops->tree); | outliner_sort(&soops->tree); | ||||
| } | } | ||||
| outliner_filter_tree(soops, &soops->tree); | |||||
| outliner_filter_tree(soops, ar, &focus); | |||||
| BKE_main_id_clear_newpoins(mainvar); | BKE_main_id_clear_newpoins(mainvar); | ||||
| } | } | ||||
This whole lookup seems redundant, the TreeStoreElem representing an entity (object, collection, whatever) shouldn't change over redraws. So you'd just have to store the TreeStoreElem of the collection you want to keep on screen, and use outliner_find_tree_element to find its current TreeElement wrapper.