Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc
| /* | /* | ||||
| * This program is free software; you can redistribute it and/or | * This program is free software; you can redistribute it and/or | ||||
| * modify it under the terms of the GNU General Public License | * modify it under the terms of the GNU General Public License | ||||
| * as published by the Free Software Foundation; either version 2 | * as published by the Free Software Foundation; either version 2 | ||||
| * of the License, or (at your option) any later version. | * of the License, or (at your option) any later version. | ||||
| * | * | ||||
| * This program is distributed in the hope that it will be useful, | * This program is distributed in the hope that it will be useful, | ||||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | * | ||||
| * You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| */ | */ | ||||
| #include <array> | |||||
| #include "DNA_space_types.h" | #include "DNA_space_types.h" | ||||
| #include "DNA_windowmanager_types.h" | #include "DNA_windowmanager_types.h" | ||||
| #include "BKE_context.h" | #include "BKE_context.h" | ||||
| #include "BKE_volume.h" | #include "BKE_volume.h" | ||||
| #include "BLF_api.h" | |||||
| #include "BLI_rect.h" | |||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_view2d.h" | #include "UI_interface.hh" | ||||
| #include "UI_tree_view.hh" | |||||
| #include "WM_types.h" | #include "WM_types.h" | ||||
| #include "BLT_translation.h" | |||||
| #include "spreadsheet_dataset_draw.hh" | #include "spreadsheet_dataset_draw.hh" | ||||
| #include "spreadsheet_draw.hh" | #include "spreadsheet_draw.hh" | ||||
| #include "spreadsheet_intern.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_VOLUME) { | |||||
| is_selected = is_component_selected; | |||||
| } | |||||
| return is_selected; | |||||
| } | |||||
| namespace blender::ed::spreadsheet { | namespace blender::ed::spreadsheet { | ||||
| /* -------------------------------------------------------------------- */ | class GeometryDataSetTreeView; | ||||
| /* Draw Context */ | |||||
| class DatasetDrawContext { | class GeometryDataSetTreeViewItem : public ui::AbstractTreeViewItem { | ||||
| std::array<int, 2> mval_; | GeometryComponentType component_type_; | ||||
| std::optional<AttributeDomain> domain_; | |||||
| BIFIconID icon_; | |||||
| public: | public: | ||||
| const SpaceSpreadsheet *sspreadsheet; | GeometryDataSetTreeViewItem(GeometryComponentType component_type, | ||||
| Object *object_eval; | StringRef label, | ||||
| /* Current geometry set, changes per component. */ | BIFIconID icon); | ||||
| GeometrySet current_geometry_set; | GeometryDataSetTreeViewItem(GeometryComponentType component_type, | ||||
| AttributeDomain domain, | |||||
| StringRef label, | |||||
| BIFIconID icon); | |||||
| void on_activate() override; | |||||
| void build_row(uiLayout &row) override; | |||||
| protected: | |||||
| std::optional<bool> should_be_active() const override; | |||||
| bool supports_collapsing() const override; | |||||
| private: | |||||
| GeometryDataSetTreeView &get_tree() const; | |||||
| std::optional<int> count() const; | |||||
| }; | |||||
| DatasetDrawContext(const bContext *C); | class GeometryDataSetTreeView : public ui::AbstractTreeView { | ||||
| GeometrySet geometry_set_; | |||||
| const bContext &C_; | |||||
| SpaceSpreadsheet &sspreadsheet_; | |||||
| bScreen &screen_; | |||||
| GeometrySet geometry_set_from_component(GeometryComponentType component); | friend class GeometryDataSetTreeViewItem; | ||||
| const std::array<int, 2> &cursor_mval() const; | |||||
| }; | |||||
| DatasetDrawContext::DatasetDrawContext(const bContext *C) | public: | ||||
| : sspreadsheet(CTX_wm_space_spreadsheet(C)), | GeometryDataSetTreeView(GeometrySet geometry_set, const bContext &C) | ||||
| object_eval(spreadsheet_get_object_eval(sspreadsheet, CTX_data_depsgraph_pointer(C))) | : geometry_set_(std::move(geometry_set)), | ||||
| C_(C), | |||||
| sspreadsheet_(*CTX_wm_space_spreadsheet(&C)), | |||||
| screen_(*CTX_wm_screen(&C)) | |||||
| { | { | ||||
| const wmWindow *win = CTX_wm_window(C); | |||||
| const ARegion *region = CTX_wm_region(C); | |||||
| mval_ = {win->eventstate->xy[0] - region->winrct.xmin, | |||||
| win->eventstate->xy[1] - region->winrct.ymin}; | |||||
| } | } | ||||
| GeometrySet DatasetDrawContext::geometry_set_from_component(GeometryComponentType component) | void build_tree() override | ||||
| { | { | ||||
| return spreadsheet_get_display_geometry_set(sspreadsheet, object_eval, component); | GeometryDataSetTreeViewItem &mesh = this->add_tree_item<GeometryDataSetTreeViewItem>( | ||||
| } | GEO_COMPONENT_TYPE_MESH, IFACE_("Mesh"), ICON_MESH_DATA); | ||||
| mesh.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_MESH, ATTR_DOMAIN_POINT, IFACE_("Vertex"), ICON_VERTEXSEL); | |||||
| mesh.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_MESH, ATTR_DOMAIN_EDGE, IFACE_("Edge"), ICON_EDGESEL); | |||||
| mesh.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_MESH, ATTR_DOMAIN_FACE, IFACE_("Face"), ICON_FACESEL); | |||||
| mesh.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_MESH, ATTR_DOMAIN_CORNER, IFACE_("Face Corner"), ICON_NODE_CORNER); | |||||
| GeometryDataSetTreeViewItem &curve = this->add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_CURVE, IFACE_("Curve"), ICON_CURVE_DATA); | |||||
| curve.add_tree_item<GeometryDataSetTreeViewItem>(GEO_COMPONENT_TYPE_CURVE, | |||||
| ATTR_DOMAIN_POINT, | |||||
| IFACE_("Control Point"), | |||||
| ICON_CURVE_BEZCIRCLE); | |||||
| curve.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_CURVE, ATTR_DOMAIN_CURVE, IFACE_("Spline"), ICON_CURVE_PATH); | |||||
| GeometryDataSetTreeViewItem &pointcloud = this->add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_POINT_CLOUD, IFACE_("Point Cloud"), ICON_POINTCLOUD_DATA); | |||||
| pointcloud.add_tree_item<GeometryDataSetTreeViewItem>( | |||||
| GEO_COMPONENT_TYPE_POINT_CLOUD, ATTR_DOMAIN_POINT, IFACE_("Point"), ICON_PARTICLE_POINT); | |||||
| const std::array<int, 2> &DatasetDrawContext::cursor_mval() const | this->add_tree_item<GeometryDataSetTreeViewItem>( | ||||
| { | GEO_COMPONENT_TYPE_VOLUME, IFACE_("Volume Grids"), ICON_VOLUME_DATA); | ||||
| return mval_; | |||||
| } | |||||
| /* -------------------------------------------------------------------- */ | this->add_tree_item<GeometryDataSetTreeViewItem>( | ||||
| /* Drawer */ | GEO_COMPONENT_TYPE_INSTANCES, ATTR_DOMAIN_INSTANCE, IFACE_("Instances"), ICON_EMPTY_AXIS); | ||||
| } | |||||
| }; | |||||
| DatasetRegionDrawer::DatasetRegionDrawer(const ARegion *region, | GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem(GeometryComponentType component_type, | ||||
| uiBlock &block, | StringRef label, | ||||
| DatasetDrawContext &draw_context) | BIFIconID icon) | ||||
| : row_height(UI_UNIT_Y), | : component_type_(component_type), domain_(std::nullopt), icon_(icon) | ||||
| xmin(region->v2d.cur.xmin), | |||||
| xmax(region->v2d.cur.xmax), | |||||
| block(block), | |||||
| v2d(region->v2d), | |||||
| draw_context(draw_context) | |||||
| { | { | ||||
| label_ = label; | |||||
| this->set_collapsed(false); | |||||
| } | } | ||||
| GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem(GeometryComponentType component_type, | |||||
| void DatasetRegionDrawer::draw_hierarchy(const DatasetLayoutHierarchy &layout) | AttributeDomain domain, | ||||
| StringRef label, | |||||
| BIFIconID icon) | |||||
| : component_type_(component_type), domain_(domain), icon_(icon) | |||||
| { | { | ||||
| for (const DatasetComponentLayoutInfo &component : layout.components) { | label_ = label; | ||||
| 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; | void GeometryDataSetTreeViewItem::on_activate() | ||||
| draw_attribute_domain_row(component, domain_info); | { | ||||
| } | GeometryDataSetTreeView &tree_view = this->get_tree(); | ||||
| bContext &C = const_cast<bContext &>(tree_view.C_); | |||||
| SpaceSpreadsheet &sspreadsheet = tree_view.sspreadsheet_; | |||||
| tree_view.sspreadsheet_.geometry_component_type = component_type_; | |||||
| if (domain_) { | |||||
| tree_view.sspreadsheet_.attribute_domain = *domain_; | |||||
| } | } | ||||
| PointerRNA ptr; | |||||
| RNA_pointer_create(&tree_view.screen_.id, &RNA_SpaceSpreadsheet, &sspreadsheet, &ptr); | |||||
| RNA_property_update(&C, &ptr, RNA_struct_find_property(&ptr, "attribute_domain")); | |||||
| RNA_property_update(&C, &ptr, RNA_struct_find_property(&ptr, "geometry_component_type")); | |||||
| } | } | ||||
| static int element_count_from_volume(const GeometrySet &geometry_set) | void GeometryDataSetTreeViewItem::build_row(uiLayout &row) | ||||
| { | { | ||||
| if (const Volume *volume = geometry_set.get_volume_for_read()) { | uiItemL(&row, label_.c_str(), icon_); | ||||
| return BKE_volume_num_grids(volume); | |||||
| if (const std::optional<int> count = this->count()) { | |||||
| /* Using the tree row button instead of a separate right aligned button gives padding | |||||
| * to the right side of the number, which it didn't have with the button. */ | |||||
| char element_count[7]; | |||||
| BLI_str_format_attribute_domain_size(element_count, *count); | |||||
| UI_but_hint_drawstr_set((uiBut *)this->tree_row_button(), element_count); | |||||
| } | } | ||||
| return 0; | |||||
| } | } | ||||
| static int element_count_from_component_domain(const GeometrySet &geometry_set, | std::optional<bool> GeometryDataSetTreeViewItem::should_be_active() const | ||||
| GeometryComponentType component, | |||||
| AttributeDomain domain) | |||||
| { | { | ||||
| if (geometry_set.has_mesh() && component == GEO_COMPONENT_TYPE_MESH) { | GeometryDataSetTreeView &tree_view = this->get_tree(); | ||||
| const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>(); | SpaceSpreadsheet &sspreadsheet = tree_view.sspreadsheet_; | ||||
| return mesh_component->attribute_domain_size(domain); | |||||
| } | |||||
| if (geometry_set.has_pointcloud() && component == GEO_COMPONENT_TYPE_POINT_CLOUD) { | if (component_type_ == GEO_COMPONENT_TYPE_VOLUME) { | ||||
| const PointCloudComponent *point_cloud_component = | return sspreadsheet.geometry_component_type == component_type_; | ||||
| 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) { | if (!domain_) { | ||||
| const VolumeComponent *volume_component = | return false; | ||||
| geometry_set.get_component_for_read<VolumeComponent>(); | |||||
| return volume_component->attribute_domain_size(domain); | |||||
| } | } | ||||
| if (geometry_set.has_curve() && component == GEO_COMPONENT_TYPE_CURVE) { | return sspreadsheet.geometry_component_type == component_type_ && | ||||
| const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); | sspreadsheet.attribute_domain == *domain_; | ||||
| return curve_component->attribute_domain_size(domain); | |||||
| } | } | ||||
| if (geometry_set.has_instances() && component == GEO_COMPONENT_TYPE_INSTANCES) { | bool GeometryDataSetTreeViewItem::supports_collapsing() const | ||||
| const InstancesComponent *instances_component = | { | ||||
| geometry_set.get_component_for_read<InstancesComponent>(); | return false; | ||||
| return instances_component->attribute_domain_size(domain); | |||||
| } | |||||
| return 0; | |||||
| } | } | ||||
| void DatasetRegionDrawer::draw_dataset_row(const int indentation, | GeometryDataSetTreeView &GeometryDataSetTreeViewItem::get_tree() const | ||||
| const GeometryComponentType component, | |||||
| const std::optional<AttributeDomain> domain, | |||||
| BIFIconID icon, | |||||
| const char *label, | |||||
| const bool is_active) | |||||
| { | { | ||||
| return static_cast<GeometryDataSetTreeView &>(this->get_tree_view()); | |||||
| 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_VOLUME) { | |||||
| BLI_str_format_attribute_domain_size( | |||||
| element_count, element_count_from_volume(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", | |||||
| WM_OP_INVOKE_DEFAULT, | |||||
| icon, | |||||
| label, | |||||
| rect.xmin, | |||||
| rect.ymin, | |||||
| BLI_rctf_size_x(&rect), | |||||
| BLI_rctf_size_y(&rect), | |||||
| nullptr); | |||||
| UI_but_datasetrow_indentation_set(bt, indentation); | std::optional<int> GeometryDataSetTreeViewItem::count() const | ||||
| { | |||||
| if (is_active) { | GeometryDataSetTreeView &tree_view = this->get_tree(); | ||||
| UI_but_hint_drawstr_set(bt, element_count); | GeometrySet &geometry = tree_view.geometry_set_; | ||||
| 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); | /* Special case for volumes since there is no grid domain. */ | ||||
| RNA_int_set(but_ptr, "component_type", component); | if (component_type_ == GEO_COMPONENT_TYPE_VOLUME) { | ||||
| if (domain) { | if (const Volume *volume = geometry.get_volume_for_read()) { | ||||
| RNA_int_set(but_ptr, "attribute_domain_type", *domain); | return BKE_volume_num_grids(volume); | ||||
| } | } | ||||
| return 0; | |||||
| } | } | ||||
| ymin_offset -= row_height; | if (!domain_) { | ||||
| return std::nullopt; | |||||
| } | } | ||||
| void DatasetRegionDrawer::draw_component_row(const DatasetComponentLayoutInfo &component_info) | if (const GeometryComponent *component = geometry.get_component_for_read(component_type_)) { | ||||
| { | return component->attribute_domain_size(*domain_); | ||||
| if (component_info.type == GEO_COMPONENT_TYPE_INSTANCES) { | |||||
| draw_dataset_row(0, | |||||
| component_info.type, | |||||
| ATTR_DOMAIN_INSTANCE, | |||||
| component_info.icon, | |||||
| component_info.label, | |||||
| true); | |||||
| } | |||||
| else if (component_info.type == GEO_COMPONENT_TYPE_VOLUME) { | |||||
| 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( | return 0; | ||||
| 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); | |||||
| } | } | ||||
| /* -------------------------------------------------------------------- */ | void spreadsheet_data_set_panel_draw(const bContext *C, Panel *panel) | ||||
| /* Drawer */ | |||||
| void draw_dataset_in_region(const bContext *C, ARegion *region) | |||||
| { | { | ||||
| DatasetDrawContext draw_context{C}; | const SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); | ||||
| if (!draw_context.object_eval) { | Object *object = spreadsheet_get_object_eval(sspreadsheet, CTX_data_depsgraph_pointer(C)); | ||||
| /* No object means nothing to display. Keep the region empty. */ | if (!object) { | ||||
| return; | return; | ||||
| } | } | ||||
| uiLayout *layout = panel->layout; | |||||
| uiBlock *block = uiLayoutGetBlock(layout); | |||||
| uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); | UI_block_layout_set_current(block, layout); | ||||
| DatasetRegionDrawer drawer{region, *block, draw_context}; | ui::AbstractTreeView *tree_view = UI_block_add_view( | ||||
| *block, | |||||
| "Data Set Tree View", | |||||
| std::make_unique<GeometryDataSetTreeView>( | |||||
| spreadsheet_get_display_geometry_set(sspreadsheet, object), *C)); | |||||
| /* Start with an offset to align buttons to spreadsheet rows. Use spreadsheet drawing info for | ui::TreeViewBuilder builder(*block); | ||||
| * that. */ | builder.build_tree_view(*tree_view); | ||||
| 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 | } // namespace blender::ed::spreadsheet | ||||