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,649 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 { | |||||
| TreeStoreElem *tselem; | |||||
| int ys; | |||||
| } OutlinerTreeElementFocus; | |||||
| /** | |||||
| * Bring the outliner scrolling back to where it was in relation to the original focus element | |||||
| * Caller is expected to handle redrawing of ARegion. | |||||
| */ | |||||
| static void outliner_restore_scrolling_position(SpaceOops *soops, ARegion *ar, OutlinerTreeElementFocus *focus) | |||||
| { | |||||
| View2D *v2d = &ar->v2d; | |||||
| int ytop; | |||||
| if (focus->tselem != NULL) { | |||||
| outliner_set_coordinates(ar, soops); | |||||
| TreeElement *te_new = outliner_find_tree_element(&soops->tree, focus->tselem); | |||||
| 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; | |||||
Severin: This whole lookup seems redundant, the `TreeStoreElem` representing an entity (object… | |||||
| } | |||||
| soops->storeflag |= SO_TREESTORE_REDRAW; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * See if TreeElement or any of its children pass the callback_test. | |||||
| */ | |||||
| static TreeElement *outliner_find_first_desired_element_at_y_recursive( | |||||
| const SpaceOops *soops, | |||||
| TreeElement *te, | |||||
| const float limit, | |||||
| bool (*callback_test)(TreeElement *)) | |||||
| { | |||||
| if (callback_test(te)) { | |||||
| return te; | |||||
| } | |||||
| if (TSELEM_OPEN(te->store_elem, soops)) { | |||||
| TreeElement *te_iter, *te_sub; | |||||
| for (te_iter = te->subtree.first; te_iter; te_iter = te_iter->next) { | |||||
| te_sub = outliner_find_first_desired_element_at_y_recursive(soops, te_iter, limit, callback_test); | |||||
| if (te_sub != NULL) { | |||||
| return te_sub; | |||||
| } | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
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… | |||||
| /** | |||||
| * Find the first element that passes a test starting from a reference vertical coordinate | |||||
| * | |||||
| * If the element that is in the position is not what we are looking for, keep looking for its | |||||
| * children, siblings, and eventually, aunts, cousins, disntant families, ... | |||||
| * | |||||
| * Basically we keep going up and down the outliner tree from that point forward, until we find | |||||
| * what we are looking for. If we are past the visible range and we can't find a valid element | |||||
| * we return NULL. | |||||
| */ | |||||
| static TreeElement *outliner_find_first_desired_element_at_y( | |||||
| const SpaceOops *soops, | |||||
| const float view_co, | |||||
| const float view_co_limit, | |||||
| bool (*callback_test)(TreeElement *)) | |||||
| { | |||||
| TreeElement *te, *te_sub; | |||||
| te = outliner_find_item_at_y(soops, &soops->tree, view_co); | |||||
| while (te != NULL) { | |||||
| te_sub = outliner_find_first_desired_element_at_y_recursive(soops, te, view_co_limit, callback_test); | |||||
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_sub != NULL) { | |||||
| /* Skip the element if it was not visible to start with. */ | |||||
| if (te->ys + UI_UNIT_Y > view_co_limit) { | |||||
| return te_sub; | |||||
| } | |||||
| else { | |||||
| return NULL; | |||||
| } | |||||
| } | |||||
| if (te->next) { | |||||
| te = te->next; | |||||
| continue; | |||||
| } | |||||
| if (te->parent == NULL) { | |||||
| break; | |||||
| } | |||||
| while (te->parent) { | |||||
| if (te->parent->next) { | |||||
| te = te->parent->next; | |||||
| break; | |||||
| } | |||||
| te = te->parent; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static bool test_collection_callback(TreeElement *te) | |||||
| { | |||||
| TreeStoreElem *tselem = TREESTORE(te); | |||||
| return ELEM(tselem->type, TSE_LAYER_COLLECTION, TSE_SCENE_COLLECTION); | |||||
| } | |||||
| /** | |||||
| * 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) | |||||
| { | |||||
| TreeElement *te; | |||||
| float limit = ar->v2d.cur.ymin; | |||||
| outliner_set_coordinates(ar, soops); | |||||
| te = outliner_find_first_desired_element_at_y(soops, ar->v2d.cur.ymax, limit, test_collection_callback); | |||||
| if (te != NULL) { | |||||
| focus->tselem = TREESTORE(te); | |||||
| focus->ys = te->ys; | |||||
| } | |||||
| else { | |||||
| focus->tselem = NULL; | |||||
| } | |||||
| } | |||||
| static int outliner_exclude_filter_get(SpaceOops *soops) | |||||
| { | |||||
| int exclude_filter = soops->filter; | |||||
| if (soops->filter & SO_FILTER_SEARCH) { | |||||
| if (soops->search_string[0] == 0) { | |||||
| exclude_filter &= ~SO_FILTER_SEARCH; | |||||
| } | |||||
| } | |||||
| /* Let's have this for the collection options at first. */ | |||||
| if (!SUPPORT_FILTER_OUTLINER(soops)) { | |||||
| exclude_filter = exclude_filter & SO_FILTER_SEARCH; | |||||
| } | |||||
| if ((soops->filter & SO_FILTER_OB_TYPE) && (soops->filter & SO_FILTER_NO_OB_ALL) == 0) { | |||||
| exclude_filter &= ~SO_FILTER_OB_TYPE; | |||||
| } | |||||
| 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; | |||||
| } | |||||
| if (exclude_filter & SO_FILTER_OB_TYPE) { | |||||
| switch (((Object *)tselem->id)->type) { | |||||
| case OB_MESH: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_MESH) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| case OB_ARMATURE: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_ARMATURE) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| case OB_EMPTY: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_EMPTY) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| case OB_LAMP: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_LAMP) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| case OB_CAMERA: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_CAMERA) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| default: | |||||
| if (exclude_filter & SO_FILTER_NO_OB_OTHERS) { | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| if ((te->parent != NULL) && | |||||
| (TREESTORE(te->parent)->type == 0) && (te->parent->idcode == ID_OB)) | |||||
| { | |||||
| if (exclude_filter & SO_FILTER_NO_CHILDREN) { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| else if (te->parent != NULL && | |||||
| TREESTORE(te->parent)->type == 0 && te->parent->idcode == ID_OB) | |||||
| { | |||||
| if (exclude_filter & SO_FILTER_NO_OB_CONTENT) { | |||||
| 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 int outliner_filter_subtree( | ||||
| SpaceOops *soops, ListBase *lb, const char *search_string, const int exclude_filter) | |||||
| { | { | ||||
| TreeElement *te, *ten; | TreeElement *te, *te_next; | ||||
| 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 = te_next) { | ||||
| * since the entire tree is ok then... | te_next = 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_free_tree_element(te, lb); | ||||
| continue; | |||||
| } | } | ||||
| else { | else if((exclude_filter & SO_FILTER_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)) { | ||||
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`… | |||||
| /* 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_filter_subtree(soops, &te->subtree, search_string, exclude_filter) == 0) | |||||
| { | |||||
| outliner_free_tree_element(te, lb); | outliner_free_tree_element(te, lb); | ||||
| } | } | ||||
| } | } | ||||
| 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) | |||||
| { | |||||
| char search_buff[sizeof(((struct SpaceOops *)NULL)->search_string) + 2]; | |||||
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) { | |||||
| 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); | |||||
| } | |||||
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); | |||||
| outliner_restore_scrolling_position(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.