diff options
Diffstat (limited to 'source/blender/editors/space_spreadsheet')
21 files changed, 2079 insertions, 149 deletions
diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index b2a0905d4a2..e903feeec1b 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -20,12 +20,14 @@ set(INC ../../blenfont ../../blenkernel ../../blenlib + ../../blentranslation ../../bmesh ../../depsgraph ../../functions ../../gpu ../../makesdna ../../makesrna + ../../nodes ../../windowmanager ../../../../intern/glew-mx ../../../../intern/guardedalloc @@ -33,23 +35,31 @@ set(INC set(SRC space_spreadsheet.cc - spreadsheet_context.cc spreadsheet_column.cc + spreadsheet_context.cc spreadsheet_data_source.cc spreadsheet_data_source_geometry.cc + spreadsheet_dataset_draw.cc + spreadsheet_dataset_layout.cc spreadsheet_draw.cc spreadsheet_layout.cc spreadsheet_ops.cc + spreadsheet_row_filter.cc + spreadsheet_row_filter_ui.cc - spreadsheet_context.hh spreadsheet_cell_value.hh spreadsheet_column.hh spreadsheet_column_values.hh + spreadsheet_context.hh spreadsheet_data_source.hh spreadsheet_data_source_geometry.hh + spreadsheet_dataset_draw.hh + spreadsheet_dataset_layout.hh spreadsheet_draw.hh spreadsheet_intern.hh spreadsheet_layout.hh + spreadsheet_row_filter.hh + spreadsheet_row_filter_ui.hh ) set(LIB diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 1f0b5d5d13e..fcc92345bea 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -47,8 +47,11 @@ #include "spreadsheet_context.hh" #include "spreadsheet_data_source_geometry.hh" +#include "spreadsheet_dataset_draw.hh" #include "spreadsheet_intern.hh" #include "spreadsheet_layout.hh" +#include "spreadsheet_row_filter.hh" +#include "spreadsheet_row_filter_ui.hh" using namespace blender; using namespace blender::ed::spreadsheet; @@ -59,6 +62,8 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U "spreadsheet space"); spreadsheet_space->spacetype = SPACE_SPREADSHEET; + spreadsheet_space->filter_flag = SPREADSHEET_FILTER_ENABLE; + { /* Header. */ ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet header"); @@ -76,6 +81,24 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U } { + /* Dataset Region */ + ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet dataset region"); + BLI_addtail(&spreadsheet_space->regionbase, region); + region->regiontype = RGN_TYPE_CHANNELS; + region->alignment = RGN_ALIGN_LEFT; + region->v2d.scroll = (V2D_SCROLL_RIGHT | V2D_SCROLL_BOTTOM); + } + + { + /* Properties region. */ + ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet right region"); + BLI_addtail(&spreadsheet_space->regionbase, region); + region->regiontype = RGN_TYPE_UI; + region->alignment = RGN_ALIGN_RIGHT; + region->flag = RGN_FLAG_HIDDEN; + } + + { /* Main window. */ ARegion *region = (ARegion *)MEM_callocN(sizeof(ARegion), "spreadsheet main region"); BLI_addtail(&spreadsheet_space->regionbase, region); @@ -88,8 +111,12 @@ static SpaceLink *spreadsheet_create(const ScrArea *UNUSED(area), const Scene *U static void spreadsheet_free(SpaceLink *sl) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + MEM_SAFE_FREE(sspreadsheet->runtime); + LISTBASE_FOREACH_MUTABLE (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { + spreadsheet_row_filter_free(row_filter); + } LISTBASE_FOREACH_MUTABLE (SpreadsheetColumn *, column, &sspreadsheet->columns) { spreadsheet_column_free(column); } @@ -113,6 +140,11 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) SpaceSpreadsheet *sspreadsheet_new = (SpaceSpreadsheet *)MEM_dupallocN(sspreadsheet_old); sspreadsheet_new->runtime = (SpaceSpreadsheet_Runtime *)MEM_dupallocN(sspreadsheet_old->runtime); + BLI_listbase_clear(&sspreadsheet_new->row_filters); + LISTBASE_FOREACH (const SpreadsheetRowFilter *, src_filter, &sspreadsheet_old->row_filters) { + SpreadsheetRowFilter *new_filter = spreadsheet_row_filter_copy(src_filter); + BLI_addtail(&sspreadsheet_new->row_filters, new_filter); + } BLI_listbase_clear(&sspreadsheet_new->columns); LISTBASE_FOREACH (SpreadsheetColumn *, src_column, &sspreadsheet_old->columns) { SpreadsheetColumn *new_column = spreadsheet_column_copy(src_column); @@ -128,8 +160,10 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) return (SpaceLink *)sspreadsheet_new; } -static void spreadsheet_keymap(wmKeyConfig *UNUSED(keyconf)) +static void spreadsheet_keymap(wmKeyConfig *keyconf) { + /* Entire editor only. */ + WM_keymap_ensure(keyconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); } static void spreadsheet_id_remap(ScrArea *UNUSED(area), SpaceLink *slink, ID *old_id, ID *new_id) @@ -160,11 +194,18 @@ static void spreadsheet_main_region_init(wmWindowManager *wm, ARegion *region) UI_view2d_region_reinit(®ion->v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy); - wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); - WM_event_add_keymap_handler(®ion->handlers, keymap); + { + wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "View2D Buttons List", 0, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + } + { + wmKeyMap *keymap = WM_keymap_ensure( + wm->defaultconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); + } } -ID *ED_spreadsheet_get_current_id(struct SpaceSpreadsheet *sspreadsheet) +ID *ED_spreadsheet_get_current_id(const struct SpaceSpreadsheet *sspreadsheet) { if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { return nullptr; @@ -181,24 +222,11 @@ ID *ED_spreadsheet_get_current_id(struct SpaceSpreadsheet *sspreadsheet) static void update_pinned_context_path_if_outdated(const bContext *C) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); - - /* Currently, this only checks if the object has been deleted. In the future we can have a more - * sophisticated check for the entire context (including modifier and nodes). */ - LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { - if (context->type == SPREADSHEET_CONTEXT_OBJECT) { - SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context; - if (object_context->object == nullptr) { - ED_spreadsheet_context_path_clear(sspreadsheet); - break; - } - } - } - if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { - Object *active_object = CTX_data_active_object(C); - if (active_object != nullptr) { - SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT); - ((SpreadsheetContextObject *)new_context)->object = active_object; - BLI_addtail(&sspreadsheet->context_path, new_context); + Main *bmain = CTX_data_main(C); + if (!ED_spreadsheet_context_path_exists(bmain, sspreadsheet)) { + ED_spreadsheet_context_path_guess(C, sspreadsheet); + if (ED_spreadsheet_context_path_update_tag(sspreadsheet)) { + ED_area_tag_redraw(CTX_wm_area(C)); } } @@ -211,28 +239,15 @@ static void update_pinned_context_path_if_outdated(const bContext *C) static void update_context_path_from_context(const bContext *C) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); - Object *active_object = CTX_data_active_object(C); - if (active_object == nullptr) { - ED_spreadsheet_context_path_clear(sspreadsheet); - return; - } - if (!BLI_listbase_is_empty(&sspreadsheet->context_path)) { - SpreadsheetContext *root_context = (SpreadsheetContext *)sspreadsheet->context_path.first; - if (root_context->type == SPREADSHEET_CONTEXT_OBJECT) { - SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)root_context; - if (object_context->object != active_object) { - ED_spreadsheet_context_path_clear(sspreadsheet); - } + if (!ED_spreadsheet_context_path_is_active(C, sspreadsheet)) { + ED_spreadsheet_context_path_guess(C, sspreadsheet); + if (ED_spreadsheet_context_path_update_tag(sspreadsheet)) { + ED_area_tag_redraw(CTX_wm_area(C)); } } - if (BLI_listbase_is_empty(&sspreadsheet->context_path)) { - SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT); - ((SpreadsheetContextObject *)new_context)->object = active_object; - BLI_addtail(&sspreadsheet->context_path, new_context); - } } -static void update_context_path(const bContext *C) +void spreadsheet_update_context_path(const bContext *C) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); if (sspreadsheet->flag & SPREADSHEET_FLAG_PINNED) { @@ -243,28 +258,40 @@ static void update_context_path(const bContext *C) } } -static std::unique_ptr<DataSource> get_data_source(const bContext *C) +Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet, + const Depsgraph *depsgraph) { - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); ID *used_id = ED_spreadsheet_get_current_id(sspreadsheet); if (used_id == nullptr) { - return {}; + return nullptr; } const ID_Type id_type = GS(used_id->name); if (id_type != ID_OB) { - return {}; + return nullptr; } Object *object_orig = (Object *)used_id; if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD, OB_VOLUME)) { - return {}; + return nullptr; } + Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig); if (object_eval == nullptr) { - return {}; + return nullptr; } - return data_source_from_geometry(C, object_eval); + return object_eval; +} + +static std::unique_ptr<DataSource> get_data_source(const bContext *C) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + Object *object_eval = spreadsheet_get_object_eval(sspreadsheet, depsgraph); + if (object_eval) { + return data_source_from_geometry(C, object_eval); + } + return {}; } static float get_column_width(const ColumnValues &values) @@ -327,7 +354,7 @@ static void update_visible_columns(ListBase &columns, DataSource &data_source) static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); - update_context_path(C); + spreadsheet_update_context_path(C); std::unique_ptr<DataSource> data_source = get_data_source(C); if (!data_source) { @@ -346,24 +373,14 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) const ColumnValues *values = scope.add(std::move(values_ptr), __func__); const int width = get_column_width_in_pixels(*values); spreadsheet_layout.columns.append({values, width}); + + spreadsheet_column_assign_runtime_data(column, values->type(), values->name()); } const int tot_rows = data_source->tot_rows(); spreadsheet_layout.index_column_width = get_index_column_width(tot_rows); - spreadsheet_layout.row_indices = IndexRange(tot_rows).as_span(); - - if (const GeometryDataSource *geometry_data_source = dynamic_cast<const GeometryDataSource *>( - data_source.get())) { - Object *object_eval = geometry_data_source->object_eval(); - Object *object_orig = DEG_get_original_object(object_eval); - if (object_orig->type == OB_MESH) { - if (object_orig->mode == OB_MODE_EDIT) { - if (sspreadsheet->filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY) { - spreadsheet_layout.row_indices = geometry_data_source->get_selected_element_indices(); - } - } - } - } + spreadsheet_layout.row_indices = spreadsheet_filter_rows( + *sspreadsheet, spreadsheet_layout, *data_source, scope); sspreadsheet->runtime->tot_columns = spreadsheet_layout.columns.size(); sspreadsheet->runtime->tot_rows = tot_rows; @@ -372,9 +389,11 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) std::unique_ptr<SpreadsheetDrawer> drawer = spreadsheet_drawer_from_layout(spreadsheet_layout); draw_spreadsheet_in_region(C, region, *drawer); - /* Tag footer for redraw, because the main region updates data for the footer. */ + /* Tag other regions for redraw, because the main region updates data for them. */ ARegion *footer = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_FOOTER); ED_region_tag_redraw(footer); + ARegion *sidebar = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_UI); + ED_region_tag_redraw(sidebar); } static void spreadsheet_main_region_listener(const wmRegionListenerParams *params) @@ -419,7 +438,7 @@ static void spreadsheet_header_region_init(wmWindowManager *UNUSED(wm), ARegion static void spreadsheet_header_region_draw(const bContext *C, ARegion *region) { - update_context_path(C); + spreadsheet_update_context_path(C); ED_region_header(C, region); } @@ -511,6 +530,77 @@ static void spreadsheet_footer_region_listener(const wmRegionListenerParams *UNU { } +static void spreadsheet_dataset_region_listener(const wmRegionListenerParams *params) +{ + ARegion *region = params->region; + wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_SCENE: { + switch (wmn->data) { + case ND_FRAME: + ED_region_tag_redraw(region); + break; + } + break; + } + case NC_TEXTURE: + ED_region_tag_redraw(region); + break; + } + + spreadsheet_header_region_listener(params); +} + +static void spreadsheet_dataset_region_init(wmWindowManager *wm, ARegion *region) +{ + region->v2d.scroll |= V2D_SCROLL_RIGHT; + region->v2d.scroll &= ~(V2D_SCROLL_LEFT | V2D_SCROLL_TOP | V2D_SCROLL_BOTTOM); + region->v2d.scroll |= V2D_SCROLL_HORIZONTAL_HIDE; + region->v2d.scroll |= V2D_SCROLL_VERTICAL_HIDE; + + UI_view2d_region_reinit(®ion->v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy); + + wmKeyMap *keymap = WM_keymap_ensure( + wm->defaultconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); +} + +static void spreadsheet_dataset_region_draw(const bContext *C, ARegion *region) +{ + spreadsheet_update_context_path(C); + + View2D *v2d = ®ion->v2d; + UI_view2d_view_ortho(v2d); + UI_ThemeClearColor(TH_BACK); + + draw_dataset_in_region(C, region); + + /* reset view matrix */ + UI_view2d_view_restore(C); + + /* scrollers */ + UI_view2d_scrollers_draw(v2d, nullptr); +} + +static void spreadsheet_sidebar_init(wmWindowManager *wm, ARegion *region) +{ + UI_panel_category_active_set_default(region, "Filters"); + ED_region_panels_init(wm, region); + + wmKeyMap *keymap = WM_keymap_ensure( + wm->defaultconf, "Spreadsheet Generic", SPACE_SPREADSHEET, 0); + WM_event_add_keymap_handler(®ion->handlers, keymap); +} + +static void spreadsheet_right_region_free(ARegion *UNUSED(region)) +{ +} + +static void spreadsheet_right_region_listener(const wmRegionListenerParams *UNUSED(params)) +{ +} + void ED_spacetype_spreadsheet(void) { SpaceType *st = (SpaceType *)MEM_callocN(sizeof(SpaceType), "spacetype spreadsheet"); @@ -563,5 +653,30 @@ void ED_spacetype_spreadsheet(void) art->listener = spreadsheet_footer_region_listener; BLI_addhead(&st->regiontypes, art); + /* regions: right panel buttons */ + art = (ARegionType *)MEM_callocN(sizeof(ARegionType), "spacetype spreadsheet right region"); + art->regionid = RGN_TYPE_UI; + art->prefsizex = UI_SIDEBAR_PANEL_WIDTH; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_FRAMES; + + art->init = spreadsheet_sidebar_init; + art->layout = ED_region_panels_layout; + art->draw = ED_region_panels_draw; + art->free = spreadsheet_right_region_free; + art->listener = spreadsheet_right_region_listener; + BLI_addhead(&st->regiontypes, art); + + register_row_filter_panels(*art); + + /* regions: channels */ + art = (ARegionType *)MEM_callocN(sizeof(ARegionType), "spreadsheet dataset region"); + art->regionid = RGN_TYPE_CHANNELS; + art->prefsizex = 200 + V2D_SCROLL_WIDTH; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D; + art->init = spreadsheet_dataset_region_init; + art->draw = spreadsheet_dataset_region_draw; + art->listener = spreadsheet_dataset_region_listener; + BLI_addhead(&st->regiontypes, art); + BKE_spacetype_register(st); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh index d1e80f1d87e..c9b73aabf96 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cell_value.hh @@ -50,7 +50,7 @@ class CellValue { std::optional<bool> value_bool; std::optional<float2> value_float2; std::optional<float3> value_float3; - std::optional<Color4f> value_color; + std::optional<ColorGeometry4f> value_color; std::optional<ObjectCellValue> value_object; std::optional<CollectionCellValue> value_collection; }; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.cc b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc index de40545fdae..ee08c86b29f 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.cc @@ -56,16 +56,29 @@ SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id) return column; } +void spreadsheet_column_assign_runtime_data(SpreadsheetColumn *column, + const eSpreadsheetColumnValueType data_type, + const StringRefNull display_name) +{ + column->data_type = data_type; + MEM_SAFE_FREE(column->display_name); + column->display_name = BLI_strdup(display_name.c_str()); +} + SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column) { SpreadsheetColumnID *new_column_id = spreadsheet_column_id_copy(src_column->id); SpreadsheetColumn *new_column = spreadsheet_column_new(new_column_id); + if (src_column->display_name != nullptr) { + new_column->display_name = BLI_strdup(src_column->display_name); + } return new_column; } void spreadsheet_column_free(SpreadsheetColumn *column) { spreadsheet_column_id_free(column->id); + MEM_SAFE_FREE(column->display_name); MEM_freeN(column); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh index bb245851d55..1a03278acad 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column.hh @@ -43,6 +43,9 @@ void spreadsheet_column_id_free(SpreadsheetColumnID *column_id); SpreadsheetColumn *spreadsheet_column_new(SpreadsheetColumnID *column_id); SpreadsheetColumn *spreadsheet_column_copy(const SpreadsheetColumn *src_column); +void spreadsheet_column_assign_runtime_data(SpreadsheetColumn *column, + const eSpreadsheetColumnValueType data_type, + const StringRefNull display_name); void spreadsheet_column_free(SpreadsheetColumn *column); } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh index 373c988a41c..68370cf6a44 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh @@ -16,6 +16,8 @@ #pragma once +#include "DNA_space_types.h" + #include "BLI_string_ref.hh" #include "spreadsheet_cell_value.hh" @@ -28,11 +30,13 @@ namespace blender::ed::spreadsheet { */ class ColumnValues { protected: + eSpreadsheetColumnValueType type_; std::string name_; int size_; public: - ColumnValues(std::string name, const int size) : name_(std::move(name)), size_(size) + ColumnValues(const eSpreadsheetColumnValueType type, std::string name, const int size) + : type_(type), name_(std::move(name)), size_(size) { } @@ -40,6 +44,11 @@ class ColumnValues { virtual void get_value(int index, CellValue &r_cell_value) const = 0; + eSpreadsheetColumnValueType type() const + { + return type_; + } + StringRefNull name() const { return name_; @@ -60,8 +69,11 @@ template<typename GetValueF> class LambdaColumnValues : public ColumnValues { GetValueF get_value_; public: - LambdaColumnValues(std::string name, int size, GetValueF get_value) - : ColumnValues(std::move(name), size), get_value_(std::move(get_value)) + LambdaColumnValues(const eSpreadsheetColumnValueType type, + std::string name, + int size, + GetValueF get_value) + : ColumnValues(type, std::move(name), size), get_value_(std::move(get_value)) { } @@ -73,13 +85,14 @@ template<typename GetValueF> class LambdaColumnValues : public ColumnValues { /* Utility function that simplifies creating a spreadsheet column from a lambda function. */ template<typename GetValueF> -std::unique_ptr<ColumnValues> column_values_from_function(std::string name, +std::unique_ptr<ColumnValues> column_values_from_function(const eSpreadsheetColumnValueType type, + std::string name, const int size, GetValueF get_value, const float default_width = 0.0f) { std::unique_ptr<ColumnValues> column_values = std::make_unique<LambdaColumnValues<GetValueF>>( - std::move(name), size, std::move(get_value)); + type, std::move(name), size, std::move(get_value)); column_values->default_width = default_width; return column_values; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_context.cc b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc index 3eb43338908..1ac2075e281 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_context.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc @@ -24,16 +24,28 @@ #include "BLI_utildefines.h" #include "BLI_vector.hh" +#include "ED_screen.h" #include "ED_spreadsheet.h" #include "DEG_depsgraph.h" +#include "BKE_context.h" #include "BKE_main.h" #include "BKE_modifier.h" +#include "BKE_node.h" #include "BKE_object.h" +#include "BKE_workspace.h" + +#include "DNA_modifier_types.h" +#include "DNA_windowmanager_types.h" #include "spreadsheet_context.hh" +using blender::IndexRange; +using blender::Span; +using blender::StringRef; +using blender::Vector; + namespace blender::ed::spreadsheet { static SpreadsheetContextObject *spreadsheet_context_object_new() @@ -206,28 +218,30 @@ void spreadsheet_context_free(SpreadsheetContext *context) /** * Tag any data relevant to the spreadsheet's context for recalculation in order to collect * information to display in the editor, which may be cached during evaluation. + * \return True when any data has been tagged for update. */ -static void spreadsheet_context_update_tag(SpaceSpreadsheet *sspreadsheet) +static bool spreadsheet_context_update_tag(SpaceSpreadsheet *sspreadsheet) { using namespace blender; Vector<const SpreadsheetContext *> context_path = sspreadsheet->context_path; if (context_path.is_empty()) { - return; + return false; } if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { - return; + return false; } SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0]; Object *object = object_context->object; if (object == nullptr) { - return; + return false; } if (context_path.size() == 1) { /* No need to reevaluate, when the final or original object is viewed. */ - return; + return false; } DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY); + return true; } } // namespace blender::ed::spreadsheet @@ -250,30 +264,47 @@ void ED_spreadsheet_context_path_clear(struct SpaceSpreadsheet *sspreadsheet) BLI_listbase_clear(&sspreadsheet->context_path); } -void ED_spreadsheet_context_path_update_tag(SpaceSpreadsheet *sspreadsheet) +bool ED_spreadsheet_context_path_update_tag(SpaceSpreadsheet *sspreadsheet) { - blender::ed::spreadsheet::spreadsheet_context_update_tag(sspreadsheet); + return blender::ed::spreadsheet::spreadsheet_context_update_tag(sspreadsheet); } -uint64_t ED_spreadsheet_context_path_hash(SpaceSpreadsheet *sspreadsheet) +uint64_t ED_spreadsheet_context_path_hash(const SpaceSpreadsheet *sspreadsheet) { BLI_HashMurmur2A mm2; BLI_hash_mm2a_init(&mm2, 1234); - LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) { + LISTBASE_FOREACH (const SpreadsheetContext *, context, &sspreadsheet->context_path) { blender::ed::spreadsheet::spreadsheet_context_hash(context, &mm2); } return BLI_hash_mm2a_end(&mm2); } -void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsheet, - struct SpaceNode *snode, - struct bNode *node) +void ED_spreadsheet_context_path_set_geometry_node(struct SpaceSpreadsheet *sspreadsheet, + struct SpaceNode *snode, + struct bNode *node) { using namespace blender::ed::spreadsheet; - ED_spreadsheet_context_path_clear(sspreadsheet); Object *object = (Object *)snode->id; + /* Try to find the modifier the node tree belongs to. */ ModifierData *modifier = BKE_object_active_modifier(object); + if (modifier && modifier->type != eModifierType_Nodes) { + modifier = nullptr; + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + if (nmd->node_group == snode->nodetree) { + modifier = md; + break; + } + } + } + } + if (modifier == nullptr) { + return; + } + + ED_spreadsheet_context_path_clear(sspreadsheet); { SpreadsheetContextObject *context = spreadsheet_context_object_new(); @@ -302,5 +333,251 @@ void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsh BLI_addtail(&sspreadsheet->context_path, context); } - sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED; + sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE; +} + +void ED_spreadsheet_context_paths_set_geometry_node(Main *bmain, SpaceNode *snode, bNode *node) +{ + wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + if (wm == nullptr) { + return; + } + LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl->spacetype == SPACE_SPREADSHEET) { + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; + if ((sspreadsheet->flag & SPREADSHEET_FLAG_PINNED) == 0) { + const uint64_t context_hash_before = ED_spreadsheet_context_path_hash(sspreadsheet); + ED_spreadsheet_context_path_set_geometry_node(sspreadsheet, snode, node); + const uint64_t context_hash_after = ED_spreadsheet_context_path_hash(sspreadsheet); + if (context_hash_before != context_hash_after) { + ED_spreadsheet_context_path_update_tag(sspreadsheet); + } + ED_area_tag_redraw(area); + } + } + } + } +} + +void ED_spreadsheet_context_path_set_evaluated_object(SpaceSpreadsheet *sspreadsheet, + Object *object) +{ + using namespace blender::ed::spreadsheet; + ED_spreadsheet_context_path_clear(sspreadsheet); + + SpreadsheetContextObject *context = spreadsheet_context_object_new(); + context->object = object; + BLI_addtail(&sspreadsheet->context_path, context); +} + +void ED_spreadsheet_context_path_guess(const bContext *C, SpaceSpreadsheet *sspreadsheet) +{ + ED_spreadsheet_context_path_clear(sspreadsheet); + + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + if (wm == nullptr) { + return; + } + + if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { + LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl->spacetype == SPACE_NODE) { + SpaceNode *snode = (SpaceNode *)sl; + if (snode->edittree != nullptr) { + if (snode->edittree->type == NTREE_GEOMETRY) { + LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) { + if (node->type == GEO_NODE_VIEWER) { + if (node->flag & NODE_DO_OUTPUT) { + ED_spreadsheet_context_path_set_geometry_node(sspreadsheet, snode, node); + return; + } + } + } + } + } + } + } + } + } + + Object *active_object = CTX_data_active_object(C); + if (active_object != nullptr) { + ED_spreadsheet_context_path_set_evaluated_object(sspreadsheet, active_object); + return; + } +} + +bool ED_spreadsheet_context_path_is_active(const bContext *C, SpaceSpreadsheet *sspreadsheet) +{ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + if (wm == nullptr) { + return false; + } + Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path; + if (context_path.is_empty()) { + return false; + } + if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { + return false; + } + Object *object = ((SpreadsheetContextObject *)context_path[0])->object; + if (object == nullptr) { + return false; + } + if (context_path.size() == 1) { + if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { + return false; + } + Object *active_object = CTX_data_active_object(C); + return object == active_object; + } + if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { + return false; + } + if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) { + return false; + } + const char *modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name; + const ModifierData *modifier = BKE_modifiers_findby_name(object, modifier_name); + if (modifier == nullptr) { + return false; + } + if (!(modifier->flag & eModifierFlag_Active)) { + return false; + } + if (modifier->type != eModifierType_Nodes) { + return false; + } + bNodeTree *root_node_tree = ((NodesModifierData *)modifier)->node_group; + if (root_node_tree == nullptr) { + return false; + } + const Span<SpreadsheetContext *> node_context_path = context_path.as_span().drop_front(2); + if (node_context_path.is_empty()) { + return false; + } + + LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl->spacetype != SPACE_NODE) { + continue; + } + SpaceNode *snode = (SpaceNode *)sl; + if (snode->nodetree != root_node_tree) { + continue; + } + if (snode->id != &object->id) { + continue; + } + Vector<bNodeTreePath *> tree_path = snode->treepath; + if (node_context_path.size() != tree_path.size()) { + continue; + } + int valid_count = 0; + for (const int i : IndexRange(tree_path.size() - 1)) { + if (node_context_path[i]->type != SPREADSHEET_CONTEXT_NODE) { + break; + } + SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)node_context_path[i]; + if (!STREQ(node_context->node_name, tree_path[i]->node_name)) { + break; + } + valid_count++; + } + if (valid_count != tree_path.size() - 1) { + continue; + } + SpreadsheetContext *last_context = node_context_path.last(); + if (last_context->type != SPREADSHEET_CONTEXT_NODE) { + return false; + } + const char *node_name = ((SpreadsheetContextNode *)last_context)->node_name; + bNode *node = nodeFindNodebyName(snode->edittree, node_name); + if (node == nullptr) { + return false; + } + if (node->type != GEO_NODE_VIEWER) { + return false; + } + if (!(node->flag & NODE_DO_OUTPUT)) { + return false; + } + return true; + } + } + return false; +} + +bool ED_spreadsheet_context_path_exists(Main *UNUSED(bmain), SpaceSpreadsheet *sspreadsheet) +{ + Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path; + if (context_path.is_empty()) { + return false; + } + if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { + return false; + } + Object *object = ((SpreadsheetContextObject *)context_path[0])->object; + if (object == nullptr) { + return false; + } + if (context_path.size() == 1) { + return true; + } + if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) { + return false; + } + const char *modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name; + const ModifierData *modifier = BKE_modifiers_findby_name(object, modifier_name); + if (modifier == nullptr) { + return false; + } + if (modifier->type != eModifierType_Nodes) { + return false; + } + bNodeTree *root_node_tree = ((NodesModifierData *)modifier)->node_group; + if (root_node_tree == nullptr) { + return false; + } + const Span<SpreadsheetContext *> node_context_path = context_path.as_span().drop_front(2); + if (node_context_path.is_empty()) { + return false; + } + bNodeTree *node_tree = root_node_tree; + for (const int i : node_context_path.index_range()) { + if (node_context_path[i]->type != SPREADSHEET_CONTEXT_NODE) { + return false; + } + const char *node_name = ((SpreadsheetContextNode *)node_context_path[i])->node_name; + bNode *node = nodeFindNodebyName(node_tree, node_name); + if (node == nullptr) { + return false; + } + if (node->type == GEO_NODE_VIEWER) { + if (i == node_context_path.index_range().last()) { + return true; + } + return false; + } + if (node->id != nullptr) { + if (GS(node->id->name) != ID_NT) { + return false; + } + node_tree = (bNodeTree *)node->id; + } + else { + return false; + } + } + return false; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh index de47109a144..2ea7fb5809f 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh @@ -54,6 +54,15 @@ class DataSource { } /** + * Returns true if the data source has the ability to limit visible rows + * by user interface selection status. + */ + virtual bool has_selection_filter() const + { + return false; + } + + /** * Returns the number of rows in columns returned by #get_column_values. */ virtual int tot_rows() const diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 02ffa1259fc..e38c70afd0f 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -31,11 +31,15 @@ #include "ED_spreadsheet.h" +#include "NOD_geometry_nodes_eval_log.hh" + #include "bmesh.h" #include "spreadsheet_data_source_geometry.hh" #include "spreadsheet_intern.hh" +namespace geo_log = blender::nodes::geometry_nodes_eval_log; + namespace blender::ed::spreadsheet { void GeometryDataSource::foreach_default_column_ids( @@ -69,28 +73,35 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( const CustomDataType type = bke::cpp_type_to_custom_data_type(varray->type()); switch (type) { case CD_PROP_FLOAT: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - float value; - varray->get(index, &value); - r_cell_value.value_float = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float value; + varray->get(index, &value); + r_cell_value.value_float = value; + }); case CD_PROP_INT32: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - int value; - varray->get(index, &value); - r_cell_value.value_int = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_INT32, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + int value; + varray->get(index, &value); + r_cell_value.value_int = value; + }); case CD_PROP_BOOL: - return column_values_from_function( - column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - bool value; - varray->get(index, &value); - r_cell_value.value_bool = value; - }); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_BOOL, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + bool value; + varray->get(index, &value); + r_cell_value.value_bool = value; + }); case CD_PROP_FLOAT2: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT2, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { @@ -102,6 +113,7 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( } case CD_PROP_FLOAT3: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { @@ -113,10 +125,11 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( } case CD_PROP_COLOR: { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_COLOR, column_id.name, domain_size, [varray](int index, CellValue &r_cell_value) { - Color4f value; + ColorGeometry4f value; varray->get(index, &value); r_cell_value.value_color = value; }, @@ -137,55 +150,63 @@ using IsVertexSelectedFn = FunctionRef<bool(int vertex_index)>; static void get_selected_vertex_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_vertex_indices) + MutableSpan<bool> selection) { for (const int i : IndexRange(mesh.totvert)) { - if (is_vertex_selected_fn(i)) { - r_vertex_indices.append(i); + if (!selection[i]) { + continue; + } + if (!is_vertex_selected_fn(i)) { + selection[i] = false; } } } static void get_selected_corner_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_corner_indices) + MutableSpan<bool> selection) { for (const int i : IndexRange(mesh.totloop)) { const MLoop &loop = mesh.mloop[i]; - if (is_vertex_selected_fn(loop.v)) { - r_corner_indices.append(i); + if (!selection[i]) { + continue; + } + if (!is_vertex_selected_fn(loop.v)) { + selection[i] = false; } } } static void get_selected_face_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_face_indices) + MutableSpan<bool> selection) { for (const int poly_index : IndexRange(mesh.totpoly)) { + if (!selection[poly_index]) { + continue; + } const MPoly &poly = mesh.mpoly[poly_index]; - bool is_selected = true; for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { const MLoop &loop = mesh.mloop[loop_index]; if (!is_vertex_selected_fn(loop.v)) { - is_selected = false; + selection[poly_index] = false; break; } } - if (is_selected) { - r_face_indices.append(poly_index); - } } } static void get_selected_edge_indices(const Mesh &mesh, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_edge_indices) + MutableSpan<bool> selection) { for (const int i : IndexRange(mesh.totedge)) { + if (!selection[i]) { + continue; + } const MEdge &edge = mesh.medge[i]; - if (is_vertex_selected_fn(edge.v1) && is_vertex_selected_fn(edge.v2)) { - r_edge_indices.append(i); + if (!is_vertex_selected_fn(edge.v1) || !is_vertex_selected_fn(edge.v2)) { + selection[i] = false; } } } @@ -193,30 +214,48 @@ static void get_selected_edge_indices(const Mesh &mesh, static void get_selected_indices_on_domain(const Mesh &mesh, const AttributeDomain domain, const IsVertexSelectedFn is_vertex_selected_fn, - Vector<int64_t> &r_indices) + MutableSpan<bool> selection) { switch (domain) { case ATTR_DOMAIN_POINT: - return get_selected_vertex_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_vertex_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_FACE: - return get_selected_face_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_face_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_CORNER: - return get_selected_corner_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_corner_indices(mesh, is_vertex_selected_fn, selection); case ATTR_DOMAIN_EDGE: - return get_selected_edge_indices(mesh, is_vertex_selected_fn, r_indices); + return get_selected_edge_indices(mesh, is_vertex_selected_fn, selection); default: return; } } -Span<int64_t> GeometryDataSource::get_selected_element_indices() const +/** + * Only data sets corresponding to mesh objects in edit mode currently support selection filtering. + */ +bool GeometryDataSource::has_selection_filter() const +{ + Object *object_orig = DEG_get_original_object(object_eval_); + if (object_orig->type != OB_MESH) { + return false; + } + if (object_orig->mode != OB_MODE_EDIT) { + return false; + } + if (component_->type() != GEO_COMPONENT_TYPE_MESH) { + return false; + } + + return true; +} + +void GeometryDataSource::apply_selection_filter(MutableSpan<bool> rows_included) const { std::lock_guard lock{mutex_}; BLI_assert(object_eval_->mode == OB_MODE_EDIT); BLI_assert(component_->type() == GEO_COMPONENT_TYPE_MESH); Object *object_orig = DEG_get_original_object(object_eval_); - Vector<int64_t> &indices = scope_.construct<Vector<int64_t>>(__func__); const MeshComponent *mesh_component = static_cast<const MeshComponent *>(component_); const Mesh *mesh_eval = mesh_component->get_for_read(); Mesh *mesh_orig = (Mesh *)object_orig->data; @@ -237,7 +276,7 @@ Span<int64_t> GeometryDataSource::get_selected_element_indices() const BMVert *vert = bm->vtable[i_orig]; return BM_elem_flag_test(vert, BM_ELEM_SELECT); }; - get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, indices); + get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included); } else if (mesh_eval->totvert == bm->totvert) { /* Use a simple heuristic to match original vertices to evaluated ones. */ @@ -245,10 +284,8 @@ Span<int64_t> GeometryDataSource::get_selected_element_indices() const BMVert *vert = bm->vtable[vertex_index]; return BM_elem_flag_test(vert, BM_ELEM_SELECT); }; - get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, indices); + get_selected_indices_on_domain(*mesh_eval, domain_, is_vertex_selected, rows_included); } - - return indices; } void InstancesDataSource::foreach_default_column_ids( @@ -279,7 +316,10 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( Span<int> reference_handles = component_->instance_reference_handles(); Span<InstanceReference> references = component_->references(); std::unique_ptr<ColumnValues> values = column_values_from_function( - "Name", size, [reference_handles, references](int index, CellValue &r_cell_value) { + SPREADSHEET_VALUE_TYPE_INSTANCES, + "Name", + size, + [reference_handles, references](int index, CellValue &r_cell_value) { const InstanceReference &reference = references[reference_handles[index]]; switch (reference.type()) { case InstanceReference::Type::Object: { @@ -303,6 +343,7 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( Span<float4x4> transforms = component_->instance_transforms(); if (STREQ(column_id.name, "Position")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -312,6 +353,7 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( } if (STREQ(column_id.name, "Rotation")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -321,6 +363,7 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( } if (STREQ(column_id.name, "Scale")) { return column_values_from_function( + SPREADSHEET_VALUE_TYPE_FLOAT3, column_id.name, size, [transforms](int index, CellValue &r_cell_value) { @@ -332,6 +375,7 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( if (STREQ(column_id.name, "ID")) { /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ return column_values_from_function( + SPREADSHEET_VALUE_TYPE_INT32, column_id.name, size, [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, @@ -345,9 +389,9 @@ int InstancesDataSource::tot_rows() const return component_->instances_amount(); } -static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet, - Object *object_eval, - const GeometryComponentType used_component_type) +GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspreadsheet, + Object *object_eval, + const GeometryComponentType used_component_type) { GeometrySet geometry_set; if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) { @@ -370,7 +414,6 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet, Mesh *mesh = (Mesh *)object_orig->data; mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly); } - mesh_component.copy_vertex_group_names_from_object(*object_orig); } else if (object_orig->type == OB_POINTCLOUD) { PointCloud *pointcloud = (PointCloud *)object_orig->data; @@ -379,7 +422,7 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet, pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly); } } - else if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED) { + else { if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) { Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false); if (mesh == nullptr) { @@ -388,7 +431,6 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet, BKE_mesh_wrapper_ensure_mdata(mesh); MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); mesh_component.replace(mesh, GeometryOwnershipType::ReadOnly); - mesh_component.copy_vertex_group_names_from_object(*object_eval); } else { if (BLI_listbase_count(&sspreadsheet->context_path) == 1) { @@ -398,13 +440,18 @@ static GeometrySet get_display_geometry_set(SpaceSpreadsheet *sspreadsheet, } } else { - if (object_eval->runtime.geometry_set_previews != nullptr) { - GHash *ghash = (GHash *)object_eval->runtime.geometry_set_previews; - const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet); - GeometrySet *geometry_set_preview = (GeometrySet *)BLI_ghash_lookup_default( - ghash, POINTER_FROM_UINT(key), nullptr); - if (geometry_set_preview != nullptr) { - geometry_set = *geometry_set_preview; + const geo_log::NodeLog *node_log = + geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(*sspreadsheet); + if (node_log != nullptr) { + for (const geo_log::SocketLog &input_log : node_log->input_logs()) { + if (const geo_log::GeometryValueLog *geo_value_log = + dynamic_cast<const geo_log::GeometryValueLog *>(input_log.value())) { + const GeometrySet *full_geometry = geo_value_log->full_geometry(); + if (full_geometry != nullptr) { + geometry_set = *full_geometry; + break; + } + } } } } @@ -430,7 +477,8 @@ std::unique_ptr<DataSource> data_source_from_geometry(const bContext *C, Object SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); const AttributeDomain domain = (AttributeDomain)sspreadsheet->attribute_domain; const GeometryComponentType component_type = get_display_component_type(C, object_eval); - GeometrySet geometry_set = get_display_geometry_set(sspreadsheet, object_eval, component_type); + GeometrySet geometry_set = spreadsheet_get_display_geometry_set( + sspreadsheet, object_eval, component_type); if (!geometry_set.has(component_type)) { return {}; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index 273d39f27bf..d1b5dc6845e 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -58,7 +58,8 @@ class GeometryDataSource : public DataSource { return object_eval_; } - Span<int64_t> get_selected_element_indices() const; + bool has_selection_filter() const override; + void apply_selection_filter(MutableSpan<bool> rows_included) const; void foreach_default_column_ids( FunctionRef<void(const SpreadsheetColumnID &)> fn) const override; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc new file mode 100644 index 00000000000..2b31ce7d03c --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -0,0 +1,287 @@ +/* + * 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. + */ + +#include <array> + +#include "DNA_space_types.h" +#include "DNA_windowmanager_types.h" + +#include "BKE_context.h" + +#include "BLF_api.h" + +#include "BLI_rect.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "WM_types.h" + +#include "spreadsheet_dataset_draw.hh" +#include "spreadsheet_draw.hh" +#include "spreadsheet_intern.hh" + +static int is_component_row_selected(struct uiBut *but, const void *arg) +{ + SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)arg; + + GeometryComponentType component = (GeometryComponentType)UI_but_datasetrow_component_get(but); + AttributeDomain domain = (AttributeDomain)UI_but_datasetrow_domain_get(but); + + const bool is_component_selected = (GeometryComponentType) + sspreadsheet->geometry_component_type == component; + const bool is_domain_selected = (AttributeDomain)sspreadsheet->attribute_domain == domain; + bool is_selected = is_component_selected && is_domain_selected; + + if (component == GEO_COMPONENT_TYPE_INSTANCES) { + is_selected = is_component_selected; + } + + return is_selected; +} + +namespace blender::ed::spreadsheet { + +/* -------------------------------------------------------------------- */ +/* Draw Context */ + +class DatasetDrawContext { + std::array<int, 2> mval_; + + public: + const SpaceSpreadsheet *sspreadsheet; + Object *object_eval; + /* Current geometry set, changes per component. */ + GeometrySet current_geometry_set; + + DatasetDrawContext(const bContext *C); + + GeometrySet geometry_set_from_component(GeometryComponentType component); + const std::array<int, 2> &cursor_mval() const; +}; + +DatasetDrawContext::DatasetDrawContext(const bContext *C) + : sspreadsheet(CTX_wm_space_spreadsheet(C)), + object_eval(spreadsheet_get_object_eval(sspreadsheet, CTX_data_depsgraph_pointer(C))) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + mval_ = {win->eventstate->x - region->winrct.xmin, win->eventstate->y - region->winrct.ymin}; +} + +GeometrySet DatasetDrawContext::geometry_set_from_component(GeometryComponentType component) +{ + return spreadsheet_get_display_geometry_set(sspreadsheet, object_eval, component); +} + +const std::array<int, 2> &DatasetDrawContext::cursor_mval() const +{ + return mval_; +} + +/* -------------------------------------------------------------------- */ +/* Drawer */ + +DatasetRegionDrawer::DatasetRegionDrawer(const ARegion *region, + uiBlock &block, + DatasetDrawContext &draw_context) + : row_height(UI_UNIT_Y), + xmin(region->v2d.cur.xmin), + xmax(region->v2d.cur.xmax), + block(block), + v2d(region->v2d), + draw_context(draw_context) +{ +} + +void DatasetRegionDrawer::draw_hierarchy(const DatasetLayoutHierarchy &layout) +{ + for (const DatasetComponentLayoutInfo &component : layout.components) { + draw_context.current_geometry_set = draw_context.geometry_set_from_component(component.type); + + draw_component_row(component); + + /* Iterate attribute domains, skip unset ones (storage has to be in a enum-based, fixed size + * array so uses optionals to support skipping enum values that shouldn't be displayed for a + * component). */ + for (const auto &optional_domain : component.attr_domains) { + if (!optional_domain) { + continue; + } + + const DatasetAttrDomainLayoutInfo &domain_info = *optional_domain; + draw_attribute_domain_row(component, domain_info); + } + } +} + +static int element_count_from_instances(const GeometrySet &geometry_set) +{ + if (geometry_set.has_instances()) { + const InstancesComponent *instances_component = + geometry_set.get_component_for_read<InstancesComponent>(); + return instances_component->instances_amount(); + } + return 0; +} + +static int element_count_from_component_domain(const GeometrySet &geometry_set, + GeometryComponentType component, + AttributeDomain domain) +{ + if (geometry_set.has_mesh() && component == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>(); + return mesh_component->attribute_domain_size(domain); + } + + if (geometry_set.has_pointcloud() && component == GEO_COMPONENT_TYPE_POINT_CLOUD) { + const PointCloudComponent *point_cloud_component = + geometry_set.get_component_for_read<PointCloudComponent>(); + return point_cloud_component->attribute_domain_size(domain); + } + + if (geometry_set.has_volume() && component == GEO_COMPONENT_TYPE_VOLUME) { + const VolumeComponent *volume_component = + geometry_set.get_component_for_read<VolumeComponent>(); + return volume_component->attribute_domain_size(domain); + } + + if (geometry_set.has_curve() && component == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + return curve_component->attribute_domain_size(domain); + } + + return 0; +} + +void DatasetRegionDrawer::draw_dataset_row(const int indentation, + const GeometryComponentType component, + const std::optional<AttributeDomain> domain, + BIFIconID icon, + const char *label, + const bool is_active) +{ + + const float row_height = UI_UNIT_Y; + const float padding_x = UI_UNIT_X * 0.25f; + + const rctf rect = {float(xmin) + padding_x, + float(xmax) - V2D_SCROLL_HANDLE_WIDTH, + ymin_offset - row_height, + ymin_offset}; + + char element_count[7]; + if (component == GEO_COMPONENT_TYPE_INSTANCES) { + BLI_str_format_attribute_domain_size( + element_count, element_count_from_instances(draw_context.current_geometry_set)); + } + else { + BLI_str_format_attribute_domain_size( + element_count, + domain ? element_count_from_component_domain( + draw_context.current_geometry_set, component, *domain) : + 0); + } + + std::string label_and_element_count = label; + label_and_element_count += UI_SEP_CHAR; + label_and_element_count += element_count; + + uiBut *bt = uiDefIconTextButO(&block, + UI_BTYPE_DATASETROW, + "SPREADSHEET_OT_change_spreadsheet_data_source", + 0, + icon, + label, + rect.xmin, + rect.ymin, + BLI_rctf_size_x(&rect), + BLI_rctf_size_y(&rect), + nullptr); + + UI_but_datasetrow_indentation_set(bt, indentation); + + if (is_active) { + UI_but_hint_drawstr_set(bt, element_count); + UI_but_datasetrow_component_set(bt, component); + if (domain) { + UI_but_datasetrow_domain_set(bt, *domain); + } + UI_but_func_pushed_state_set(bt, &is_component_row_selected, draw_context.sspreadsheet); + + PointerRNA *but_ptr = UI_but_operator_ptr_get((uiBut *)bt); + RNA_int_set(but_ptr, "component_type", component); + if (domain) { + RNA_int_set(but_ptr, "attribute_domain_type", *domain); + } + } + + ymin_offset -= row_height; +} + +void DatasetRegionDrawer::draw_component_row(const DatasetComponentLayoutInfo &component_info) +{ + if (component_info.type == GEO_COMPONENT_TYPE_INSTANCES) { + draw_dataset_row( + 0, component_info.type, std::nullopt, component_info.icon, component_info.label, true); + } + else { + draw_dataset_row( + 0, component_info.type, std::nullopt, component_info.icon, component_info.label, false); + } +} + +void DatasetRegionDrawer::draw_attribute_domain_row( + const DatasetComponentLayoutInfo &component_info, + const DatasetAttrDomainLayoutInfo &domain_info) +{ + draw_dataset_row( + 1, component_info.type, domain_info.type, domain_info.icon, domain_info.label, true); +} + +/* -------------------------------------------------------------------- */ +/* Drawer */ + +void draw_dataset_in_region(const bContext *C, ARegion *region) +{ + DatasetDrawContext draw_context{C}; + if (!draw_context.object_eval) { + /* No object means nothing to display. Keep the region empty. */ + return; + } + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + + DatasetRegionDrawer drawer{region, *block, draw_context}; + + /* Start with an offset to align buttons to spreadsheet rows. Use spreadsheet drawing info for + * that. */ + drawer.ymin_offset = -SpreadsheetDrawer().top_row_height + drawer.row_height; + + const DatasetLayoutHierarchy hierarchy = dataset_layout_hierarchy(); + drawer.draw_hierarchy(hierarchy); +#ifndef NDEBUG + dataset_layout_hierarchy_sanity_check(hierarchy); +#endif + + UI_block_end(C, block); + UI_view2d_totRect_set(®ion->v2d, region->winx, abs(drawer.ymin_offset)); + UI_block_draw(C, block); +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.hh b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.hh new file mode 100644 index 00000000000..d9e6d882c2a --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.hh @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#pragma once + +#include <array> + +#include "BKE_geometry_set.hh" +#include "UI_interface.h" +#include "spreadsheet_dataset_layout.hh" + +struct ARegion; +struct uiBlock; +struct View2D; +struct bContext; + +namespace blender::ed::spreadsheet { + +class DatasetDrawContext; + +class DatasetRegionDrawer { + public: + const int row_height; + float ymin_offset = 0; + + int xmin; + int xmax; + uiBlock █ + const View2D &v2d; + DatasetDrawContext &draw_context; + + DatasetRegionDrawer(const ARegion *region, uiBlock &block, DatasetDrawContext &draw_context); + + void draw_hierarchy(const DatasetLayoutHierarchy &layout); + + void draw_attribute_domain_row(const DatasetComponentLayoutInfo &component, + const DatasetAttrDomainLayoutInfo &domain_info); + void draw_component_row(const DatasetComponentLayoutInfo &component_info); + + private: + void draw_dataset_row(const int indentation, + const GeometryComponentType component, + const std::optional<AttributeDomain> domain, + const BIFIconID icon, + const char *label, + const bool is_active); +}; + +void draw_dataset_in_region(const bContext *C, ARegion *region); + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.cc new file mode 100644 index 00000000000..abbad8c7088 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.cc @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#include <optional> + +#include "BLI_span.hh" + +#include "BLT_translation.h" + +#include "spreadsheet_dataset_layout.hh" + +namespace blender::ed::spreadsheet { + +#define ATTR_INFO(type, label, icon) \ + std::optional<DatasetAttrDomainLayoutInfo> \ + { \ + std::in_place, type, label, icon \ + } +#define ATTR_INFO_NONE(type) \ + { \ + std::nullopt \ + } + +/** + * Definition for the component->attribute-domain hierarchy. + * Constructed at compile time. + * + * \warning Order of attribute-domains matters! It __must__ match the #AttributeDomain + * definition and fill gaps with unset optionals (i.e. `std::nullopt`). Would be nice to use + * array designators for this (which C++ doesn't support). + */ +constexpr DatasetComponentLayoutInfo DATASET_layout_hierarchy[] = { + { + GEO_COMPONENT_TYPE_MESH, + N_("Mesh"), + ICON_MESH_DATA, + { + ATTR_INFO(ATTR_DOMAIN_POINT, N_("Vertex"), ICON_VERTEXSEL), + ATTR_INFO(ATTR_DOMAIN_EDGE, N_("Edge"), ICON_EDGESEL), + ATTR_INFO(ATTR_DOMAIN_FACE, N_("Face"), ICON_FACESEL), + ATTR_INFO(ATTR_DOMAIN_CORNER, N_("Face Corner"), ICON_NODE_CORNER), + }, + }, + { + GEO_COMPONENT_TYPE_CURVE, + N_("Curves"), + ICON_CURVE_DATA, + { + ATTR_INFO(ATTR_DOMAIN_POINT, N_("Control Point"), ICON_CURVE_BEZCIRCLE), + ATTR_INFO_NONE(ATTR_DOMAIN_EDGE), + ATTR_INFO_NONE(ATTR_DOMAIN_CORNER), + ATTR_INFO_NONE(ATTR_DOMAIN_FACE), + ATTR_INFO(ATTR_DOMAIN_CURVE, N_("Spline"), ICON_CURVE_PATH), + }, + }, + { + GEO_COMPONENT_TYPE_POINT_CLOUD, + N_("Point Cloud"), + ICON_POINTCLOUD_DATA, + { + ATTR_INFO(ATTR_DOMAIN_POINT, N_("Point"), ICON_PARTICLE_POINT), + }, + }, + { + GEO_COMPONENT_TYPE_INSTANCES, + N_("Instances"), + ICON_EMPTY_AXIS, + {}, + }, +}; + +#undef ATTR_INFO +#undef ATTR_INFO_LABEL + +DatasetLayoutHierarchy dataset_layout_hierarchy() +{ + return DatasetLayoutHierarchy{ + Span{DATASET_layout_hierarchy, ARRAY_SIZE(DATASET_layout_hierarchy)}}; +} + +#ifndef NDEBUG +/** + * Debug-only sanity check for correct attribute domain initialization (order/indices must + * match AttributeDomain). This doesn't check for all possible missuses, but should catch the most + * likely mistakes. + */ +void dataset_layout_hierarchy_sanity_check(const DatasetLayoutHierarchy &hierarchy) +{ + for (const DatasetComponentLayoutInfo &component : hierarchy.components) { + for (uint i = 0; i < component.attr_domains.size(); i++) { + if (component.attr_domains[i]) { + BLI_assert(component.attr_domains[i]->type == static_cast<AttributeDomain>(i)); + } + } + } +} +#endif + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.hh b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.hh new file mode 100644 index 00000000000..d463739a0fa --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_layout.hh @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#pragma once + +#include <array> +#include <optional> + +/* Enum definitions... */ +#include "BKE_attribute.h" +#include "BKE_geometry_set.h" + +#include "BLI_span.hh" + +/* More enum definitions... */ +#include "UI_resources.h" + +#pragma once + +namespace blender::ed::spreadsheet { + +struct DatasetAttrDomainLayoutInfo { + AttributeDomain type; + const char *label; + BIFIconID icon; + + constexpr DatasetAttrDomainLayoutInfo(AttributeDomain type, const char *label, BIFIconID icon) + : type(type), label(label), icon(icon) + { + } +}; + +struct DatasetComponentLayoutInfo { + GeometryComponentType type; + const char *label; + BIFIconID icon; + /** Array of attribute-domains. Has to be fixed size based on #AttributeDomain enum, but not all + * values need displaying for all parent components. Hence the optional use. */ + using AttrDomainArray = std::array<std::optional<DatasetAttrDomainLayoutInfo>, ATTR_DOMAIN_NUM>; + const AttrDomainArray attr_domains; +}; + +struct DatasetLayoutHierarchy { + /** The components for display (with layout info like icon and label). Each component stores + * the attribute domains it wants to display (also with layout info like icon and label). */ + const Span<DatasetComponentLayoutInfo> components; +}; + +DatasetLayoutHierarchy dataset_layout_hierarchy(); + +#ifndef NDEBUG +void dataset_layout_hierarchy_sanity_check(const DatasetLayoutHierarchy &hierarchy); +#endif + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh b/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh index 7e3b79a6706..8be5283fd63 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh @@ -16,10 +16,23 @@ #pragma once +#include "BKE_geometry_set.hh" + typedef struct SpaceSpreadsheet_Runtime { int visible_rows; int tot_rows; int tot_columns; } SpaceSpreadsheet_Runtime; +struct bContext; + void spreadsheet_operatortypes(void); +void spreadsheet_update_context_path(const bContext *C); +Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet, + const Depsgraph *depsgraph); + +namespace blender::ed::spreadsheet { +GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspreadsheet, + Object *object_eval, + const GeometryComponentType used_component_type); +} diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index f1ca65817f6..8079763a339 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -170,7 +170,7 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { this->draw_float_vector(params, Span(&value.x, 3)); } else if (cell_value.value_color.has_value()) { - const Color4f value = *cell_value.value_color; + const ColorGeometry4f value = *cell_value.value_color; this->draw_float_vector(params, Span(&value.r, 4)); } else if (cell_value.value_object.has_value()) { diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc b/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc index 770bd207e8d..11ed88b7dc9 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_ops.cc @@ -13,9 +13,137 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_screen.h" + +#include "DNA_space_types.h" + +#include "ED_screen.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "BLI_listbase.h" + +#include "MEM_guardedalloc.h" + +#include "BKE_context.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" #include "spreadsheet_intern.hh" +#include "spreadsheet_row_filter.hh" + +using namespace blender::ed::spreadsheet; + +static int row_filter_add_exec(bContext *C, wmOperator *UNUSED(op)) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + SpreadsheetRowFilter *row_filter = spreadsheet_row_filter_new(); + BLI_addtail(&sspreadsheet->row_filters, row_filter); + + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SPREADSHEET, sspreadsheet); + + return OPERATOR_FINISHED; +} + +static void SPREADSHEET_OT_add_row_filter_rule(wmOperatorType *ot) +{ + ot->name = "Add Row Filter"; + ot->description = "Add a filter to remove rows from the displayed data"; + ot->idname = "SPREADSHEET_OT_add_row_filter_rule"; + + ot->exec = row_filter_add_exec; + ot->poll = ED_operator_spreadsheet_active; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int row_filter_remove_exec(bContext *C, wmOperator *op) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + + SpreadsheetRowFilter *row_filter = (SpreadsheetRowFilter *)BLI_findlink( + &sspreadsheet->row_filters, RNA_int_get(op->ptr, "index")); + if (row_filter == nullptr) { + return OPERATOR_CANCELLED; + } + + BLI_remlink(&sspreadsheet->row_filters, row_filter); + spreadsheet_row_filter_free(row_filter); + + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_SPREADSHEET, sspreadsheet); + + return OPERATOR_FINISHED; +} + +static void SPREADSHEET_OT_remove_row_filter_rule(wmOperatorType *ot) +{ + ot->name = "Remove Row Filter"; + ot->description = "Remove a row filter from the rules"; + ot->idname = "SPREADSHEET_OT_remove_row_filter_rule"; + + ot->exec = row_filter_remove_exec; + ot->poll = ED_operator_spreadsheet_active; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, INT_MAX); +} + +static int select_component_domain_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ + GeometryComponentType component_type = static_cast<GeometryComponentType>( + RNA_int_get(op->ptr, "component_type")); + AttributeDomain attribute_domain = static_cast<AttributeDomain>( + RNA_int_get(op->ptr, "attribute_domain_type")); + + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + sspreadsheet->geometry_component_type = component_type; + sspreadsheet->attribute_domain = attribute_domain; + + /* Refresh header and main region. */ + WM_main_add_notifier(NC_SPACE | ND_SPACE_SPREADSHEET, nullptr); + + return OPERATOR_FINISHED; +} + +static void SPREADSHEET_OT_change_spreadsheet_data_source(wmOperatorType *ot) +{ + ot->name = "Change Visible Data Source"; + ot->description = "Change visible data source in the spreadsheet"; + ot->idname = "SPREADSHEET_OT_change_spreadsheet_data_source"; + + ot->invoke = select_component_domain_invoke; + + RNA_def_int(ot->srna, "component_type", 0, 0, INT16_MAX, "Component Type", "", 0, INT16_MAX); + RNA_def_int(ot->srna, + "attribute_domain_type", + 0, + 0, + INT16_MAX, + "Attribute Domain Type", + "", + 0, + INT16_MAX); + + ot->flag = OPTYPE_INTERNAL; +} void spreadsheet_operatortypes() { + WM_operatortype_append(SPREADSHEET_OT_add_row_filter_rule); + WM_operatortype_append(SPREADSHEET_OT_remove_row_filter_rule); + WM_operatortype_append(SPREADSHEET_OT_change_spreadsheet_data_source); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc new file mode 100644 index 00000000000..ae336edfead --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.cc @@ -0,0 +1,366 @@ +/* + * 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. + */ + +#include <cstring> + +#include "BLI_listbase.h" + +#include "DNA_collection_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "DEG_depsgraph_query.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "spreadsheet_intern.hh" + +#include "spreadsheet_data_source_geometry.hh" +#include "spreadsheet_intern.hh" +#include "spreadsheet_layout.hh" +#include "spreadsheet_row_filter.hh" + +namespace blender::ed::spreadsheet { + +template<typename OperationFn> +static void apply_filter_operation(const ColumnValues &values, + OperationFn check_fn, + MutableSpan<bool> rows_included) +{ + for (const int i : rows_included.index_range()) { + if (!rows_included[i]) { + continue; + } + CellValue cell_value; + values.get_value(i, cell_value); + if (!check_fn(cell_value)) { + rows_included[i] = false; + } + } +} + +static void apply_row_filter(const SpreadsheetLayout &spreadsheet_layout, + const SpreadsheetRowFilter &row_filter, + MutableSpan<bool> rows_included) +{ + for (const ColumnLayout &column : spreadsheet_layout.columns) { + const ColumnValues &values = *column.values; + if (values.name() != row_filter.column_name) { + continue; + } + + switch (values.type()) { + case SPREADSHEET_VALUE_TYPE_INT32: { + const int value = row_filter.value_int; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int == value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int > value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_int < value; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT: { + const float value = row_filter.value_float; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold = row_filter.threshold; + apply_filter_operation( + values, + [value, threshold](const CellValue &cell_value) -> bool { + return std::abs(*cell_value.value_float - value) < threshold; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_float > value; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_float < value; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT2: { + const float2 value = row_filter.value_float2; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return float2::distance_squared(*cell_value.value_float2, value) < + threshold_squared; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float2->x > value.x && + cell_value.value_float2->y > value.y; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float2->x < value.x && + cell_value.value_float2->y < value.y; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_FLOAT3: { + const float3 value = row_filter.value_float3; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return float3::distance_squared(*cell_value.value_float3, value) < + threshold_squared; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_GREATER: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float3->x > value.x && + cell_value.value_float3->y > value.y && + cell_value.value_float3->z > value.z; + }, + rows_included); + break; + } + case SPREADSHEET_ROW_FILTER_LESS: { + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return cell_value.value_float3->x < value.x && + cell_value.value_float3->y < value.y && + cell_value.value_float3->z < value.z; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_COLOR: { + const ColorGeometry4f value = row_filter.value_color; + switch (row_filter.operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: { + const float threshold_squared = row_filter.threshold * row_filter.threshold; + apply_filter_operation( + values, + [value, threshold_squared](const CellValue &cell_value) -> bool { + return len_squared_v4v4(value, *cell_value.value_color) < threshold_squared; + }, + rows_included); + break; + } + } + break; + } + case SPREADSHEET_VALUE_TYPE_BOOL: { + const bool value = (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) != 0; + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + return *cell_value.value_bool == value; + }, + rows_included); + break; + } + case SPREADSHEET_VALUE_TYPE_INSTANCES: { + const StringRef value = row_filter.value_string; + apply_filter_operation( + values, + [value](const CellValue &cell_value) -> bool { + const ID *id = nullptr; + if (cell_value.value_object) { + id = &cell_value.value_object->object->id; + } + else if (cell_value.value_collection) { + id = &cell_value.value_collection->collection->id; + } + if (id == nullptr) { + return false; + } + + return value == id->name + 2; + }, + rows_included); + break; + } + default: + break; + } + + /* Only one column should have this name. */ + break; + } +} + +static void index_vector_from_bools(Span<bool> selection, Vector<int64_t> &indices) +{ + for (const int i : selection.index_range()) { + if (selection[i]) { + indices.append(i); + } + } +} + +static bool use_row_filters(const SpaceSpreadsheet &sspreadsheet) +{ + if (!(sspreadsheet.filter_flag & SPREADSHEET_FILTER_ENABLE)) { + return false; + } + if (BLI_listbase_is_empty(&sspreadsheet.row_filters)) { + return false; + } + return true; +} + +static bool use_selection_filter(const SpaceSpreadsheet &sspreadsheet, + const DataSource &data_source) +{ + if (!(sspreadsheet.filter_flag & SPREADSHEET_FILTER_SELECTED_ONLY)) { + return false; + } + if (!data_source.has_selection_filter()) { + return false; + } + return true; +} + +Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, + const SpreadsheetLayout &spreadsheet_layout, + const DataSource &data_source, + ResourceScope &scope) +{ + const int tot_rows = data_source.tot_rows(); + + const bool use_selection = use_selection_filter(sspreadsheet, data_source); + const bool use_filters = use_row_filters(sspreadsheet); + + /* Avoid allocating an array if no row filtering is necessary. */ + if (!(use_filters || use_selection)) { + return IndexRange(tot_rows).as_span(); + } + + Array<bool> rows_included(tot_rows, true); + + if (use_filters) { + LISTBASE_FOREACH (const SpreadsheetRowFilter *, row_filter, &sspreadsheet.row_filters) { + if (row_filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) { + apply_row_filter(spreadsheet_layout, *row_filter, rows_included); + } + } + } + + if (use_selection) { + const GeometryDataSource *geometry_data_source = dynamic_cast<const GeometryDataSource *>( + &data_source); + geometry_data_source->apply_selection_filter(rows_included); + } + + Vector<int64_t> &indices = scope.construct<Vector<int64_t>>(__func__); + index_vector_from_bools(rows_included, indices); + + return indices; +} + +SpreadsheetRowFilter *spreadsheet_row_filter_new() +{ + SpreadsheetRowFilter *row_filter = (SpreadsheetRowFilter *)MEM_callocN( + sizeof(SpreadsheetRowFilter), __func__); + row_filter->flag = (SPREADSHEET_ROW_FILTER_UI_EXPAND | SPREADSHEET_ROW_FILTER_ENABLED); + row_filter->operation = SPREADSHEET_ROW_FILTER_LESS; + row_filter->threshold = 0.01f; + row_filter->column_name[0] = '\0'; + + return row_filter; +} + +SpreadsheetRowFilter *spreadsheet_row_filter_copy(const SpreadsheetRowFilter *src_row_filter) +{ + SpreadsheetRowFilter *new_filter = spreadsheet_row_filter_new(); + + memcpy(new_filter, src_row_filter, sizeof(SpreadsheetRowFilter)); + new_filter->next = nullptr; + new_filter->prev = nullptr; + + return new_filter; +} + +void spreadsheet_row_filter_free(SpreadsheetRowFilter *row_filter) +{ + MEM_SAFE_FREE(row_filter->value_string); + MEM_freeN(row_filter); +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh new file mode 100644 index 00000000000..0a5783e318d --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter.hh @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +#include "BLI_resource_scope.hh" + +#include "spreadsheet_data_source.hh" +#include "spreadsheet_layout.hh" + +namespace blender::ed::spreadsheet { + +Span<int64_t> spreadsheet_filter_rows(const SpaceSpreadsheet &sspreadsheet, + const SpreadsheetLayout &spreadsheet_layout, + const DataSource &data_source, + ResourceScope &scope); + +SpreadsheetRowFilter *spreadsheet_row_filter_new(); +SpreadsheetRowFilter *spreadsheet_row_filter_copy(const SpreadsheetRowFilter *src_row_filter); +void spreadsheet_row_filter_free(SpreadsheetRowFilter *row_filter); + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc new file mode 100644 index 00000000000..219d03c1dcd --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.cc @@ -0,0 +1,347 @@ +/* + * 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. + */ + +#include <cstring> + +#include "BLI_listbase.h" +#include "BLI_string.h" +#include "BLI_string_ref.hh" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLT_translation.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "spreadsheet_column.hh" +#include "spreadsheet_intern.hh" +#include "spreadsheet_row_filter.hh" +#include "spreadsheet_row_filter_ui.hh" + +using namespace blender; +using namespace blender::ed::spreadsheet; + +static void filter_panel_id_fn(void *UNUSED(row_filter_v), char *r_name) +{ + /* All row filters use the same panel ID. */ + BLI_snprintf(r_name, BKE_ST_MAXNAME, "SPREADSHEET_PT_filter"); +} + +static std::string operation_string(const eSpreadsheetColumnValueType data_type, + const eSpreadsheetFilterOperation operation) +{ + if (ELEM(data_type, + SPREADSHEET_VALUE_TYPE_BOOL, + SPREADSHEET_VALUE_TYPE_INSTANCES, + SPREADSHEET_VALUE_TYPE_COLOR)) { + return "="; + } + + switch (operation) { + case SPREADSHEET_ROW_FILTER_EQUAL: + return "="; + case SPREADSHEET_ROW_FILTER_GREATER: + return ">"; + case SPREADSHEET_ROW_FILTER_LESS: + return "<"; + } + BLI_assert_unreachable(); + return ""; +} + +static std::string value_string(const SpreadsheetRowFilter &row_filter, + const eSpreadsheetColumnValueType data_type) +{ + switch (data_type) { + case SPREADSHEET_VALUE_TYPE_INT32: + return std::to_string(row_filter.value_int); + case SPREADSHEET_VALUE_TYPE_FLOAT: { + std::ostringstream result; + result.precision(3); + result << std::fixed << row_filter.value_float; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_FLOAT2: { + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_float2[0] << ", " + << row_filter.value_float2[1] << ")"; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_FLOAT3: { + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_float3[0] << ", " + << row_filter.value_float3[1] << ", " << row_filter.value_float3[2] << ")"; + return result.str(); + } + case SPREADSHEET_VALUE_TYPE_BOOL: + return (row_filter.flag & SPREADSHEET_ROW_FILTER_BOOL_VALUE) ? IFACE_("True") : + IFACE_("False"); + case SPREADSHEET_VALUE_TYPE_INSTANCES: + if (row_filter.value_string != nullptr) { + return row_filter.value_string; + } + return ""; + case SPREADSHEET_VALUE_TYPE_COLOR: + std::ostringstream result; + result.precision(3); + result << std::fixed << "(" << row_filter.value_color[0] << ", " << row_filter.value_color[1] + << ", " << row_filter.value_color[2] << ", " << row_filter.value_color[3] << ")"; + return result.str(); + } + BLI_assert_unreachable(); + return ""; +} + +static SpreadsheetColumn *lookup_visible_column_for_filter(const SpaceSpreadsheet &sspreadsheet, + const StringRef column_name) +{ + LISTBASE_FOREACH (SpreadsheetColumn *, column, &sspreadsheet.columns) { + if (column->display_name == column_name) { + return column; + } + } + return nullptr; +} + +static void spreadsheet_filter_panel_draw_header(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + const SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + const StringRef column_name = filter->column_name; + const eSpreadsheetFilterOperation operation = (eSpreadsheetFilterOperation)filter->operation; + + const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name); + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) || + (column == nullptr && !column_name.is_empty())) { + uiLayoutSetActive(layout, false); + } + + uiLayout *row = uiLayoutRow(layout, true); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + uiItemR(row, filter_ptr, "enabled", UI_ITEM_R_ICON_ONLY, "", ICON_NONE); + + if (column_name.is_empty()) { + uiItemL(row, IFACE_("Filter"), ICON_NONE); + } + else if (column == nullptr) { + uiItemL(row, column_name.data(), ICON_NONE); + } + else { + const eSpreadsheetColumnValueType data_type = (eSpreadsheetColumnValueType)column->data_type; + std::stringstream ss; + ss << column_name; + ss << " "; + ss << operation_string(data_type, operation); + ss << " "; + ss << value_string(*filter, data_type); + uiItemL(row, ss.str().c_str(), ICON_NONE); + } + + row = uiLayoutRow(layout, true); + uiLayoutSetEmboss(row, UI_EMBOSS_NONE); + const int current_index = BLI_findindex(&sspreadsheet->row_filters, filter); + uiItemIntO(row, "", ICON_X, "SPREADSHEET_OT_remove_row_filter_rule", "index", current_index); + + /* Some padding so the X isn't too close to the drag icon. */ + uiItemS_ex(layout, 0.25f); +} + +static void spreadsheet_filter_panel_draw(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + const StringRef column_name = filter->column_name; + const eSpreadsheetFilterOperation operation = (eSpreadsheetFilterOperation)filter->operation; + + const SpreadsheetColumn *column = lookup_visible_column_for_filter(*sspreadsheet, column_name); + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE) || + !(filter->flag & SPREADSHEET_ROW_FILTER_ENABLED) || + (column == nullptr && !column_name.is_empty())) { + uiLayoutSetActive(layout, false); + } + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + + uiItemR(layout, filter_ptr, "column_name", 0, IFACE_("Column"), ICON_NONE); + + /* Don't draw settings for filters with no corresponding visible column. */ + if (column == nullptr || column_name.is_empty()) { + return; + } + + switch (static_cast<eSpreadsheetColumnValueType>(column->data_type)) { + case SPREADSHEET_VALUE_TYPE_INT32: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_int", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_FLOAT: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_FLOAT2: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float2", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_FLOAT3: + uiItemR(layout, filter_ptr, "operation", 0, nullptr, ICON_NONE); + uiItemR(layout, filter_ptr, "value_float3", 0, IFACE_("Value"), ICON_NONE); + if (operation == SPREADSHEET_ROW_FILTER_EQUAL) { + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + } + break; + case SPREADSHEET_VALUE_TYPE_BOOL: + uiItemR(layout, filter_ptr, "value_boolean", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_INSTANCES: + uiItemR(layout, filter_ptr, "value_string", 0, IFACE_("Value"), ICON_NONE); + break; + case SPREADSHEET_VALUE_TYPE_COLOR: + uiItemR(layout, filter_ptr, "value_color", 0, IFACE_("Value"), ICON_NONE); + uiItemR(layout, filter_ptr, "threshold", 0, nullptr, ICON_NONE); + break; + } +} + +static void spreadsheet_row_filters_layout(const bContext *C, Panel *panel) +{ + uiLayout *layout = panel->layout; + ARegion *region = CTX_wm_region(C); + bScreen *screen = CTX_wm_screen(C); + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + ListBase *row_filters = &sspreadsheet->row_filters; + + if (!(sspreadsheet->filter_flag & SPREADSHEET_FILTER_ENABLE)) { + uiLayoutSetActive(layout, false); + } + + uiItemO(layout, nullptr, ICON_ADD, "SPREADSHEET_OT_add_row_filter_rule"); + + const bool panels_match = UI_panel_list_matches_data(region, row_filters, filter_panel_id_fn); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) { + char panel_idname[MAX_NAME]; + filter_panel_id_fn(row_filter, panel_idname); + + PointerRNA *filter_ptr = (PointerRNA *)MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&screen->id, &RNA_SpreadsheetRowFilter, row_filter, filter_ptr); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, filter_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel_iter = (Panel *)region->panels.first; + LISTBASE_FOREACH (SpreadsheetRowFilter *, row_filter, row_filters) { + + /* Move to the next instanced panel corresponding to the next filter. */ + while ((panel_iter->type == nullptr) || !(panel_iter->type->flag & PANEL_TYPE_INSTANCED)) { + panel_iter = panel_iter->next; + BLI_assert(panel_iter != nullptr); /* There shouldn't be fewer panels than filters. */ + } + + PointerRNA *filter_ptr = (PointerRNA *)MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&screen->id, &RNA_SpreadsheetRowFilter, row_filter, filter_ptr); + UI_panel_custom_data_set(panel_iter, filter_ptr); + + panel_iter = panel_iter->next; + } + } +} + +static void filter_reorder(bContext *C, Panel *panel, int new_index) +{ + SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + ListBase *row_filters = &sspreadsheet->row_filters; + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + int current_index = BLI_findindex(row_filters, filter); + BLI_assert(current_index >= 0); + BLI_assert(new_index >= 0); + + BLI_listbase_link_move(row_filters, filter, new_index - current_index); +} + +static short get_filter_expand_flag(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + return (short)filter->flag & SPREADSHEET_ROW_FILTER_UI_EXPAND; +} + +static void set_filter_expand_flag(const bContext *UNUSED(C), Panel *panel, short expand_flag) +{ + PointerRNA *filter_ptr = UI_panel_custom_data_get(panel); + SpreadsheetRowFilter *filter = (SpreadsheetRowFilter *)filter_ptr->data; + + SET_FLAG_FROM_TEST(filter->flag, + expand_flag & SPREADSHEET_ROW_FILTER_UI_EXPAND, + SPREADSHEET_ROW_FILTER_UI_EXPAND); +} + +void register_row_filter_panels(ARegionType ®ion_type) +{ + { + PanelType *panel_type = (PanelType *)MEM_callocN(sizeof(PanelType), __func__); + strcpy(panel_type->idname, "SPREADSHEET_PT_row_filters"); + strcpy(panel_type->label, N_("Filters")); + strcpy(panel_type->category, "Filters"); + strcpy(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + panel_type->flag = PANEL_TYPE_NO_HEADER; + panel_type->draw = spreadsheet_row_filters_layout; + BLI_addtail(®ion_type.paneltypes, panel_type); + } + + { + PanelType *panel_type = (PanelType *)MEM_callocN(sizeof(PanelType), __func__); + strcpy(panel_type->idname, "SPREADSHEET_PT_filter"); + strcpy(panel_type->label, ""); + strcpy(panel_type->category, "Filters"); + strcpy(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + panel_type->flag = PANEL_TYPE_INSTANCED | PANEL_TYPE_DRAW_BOX | PANEL_TYPE_HEADER_EXPAND; + panel_type->draw_header = spreadsheet_filter_panel_draw_header; + panel_type->draw = spreadsheet_filter_panel_draw; + panel_type->get_list_data_expand_flag = get_filter_expand_flag; + panel_type->set_list_data_expand_flag = set_filter_expand_flag; + panel_type->reorder = filter_reorder; + BLI_addtail(®ion_type.paneltypes, panel_type); + } +} diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh new file mode 100644 index 00000000000..e22178b63ea --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_row_filter_ui.hh @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +struct ARegionType; + +void register_row_filter_panels(ARegionType ®ion_type); |