Refactor: Port spreadsheet data set to UI tree view

This patch removes a bunch of specific code for drawing the spreadsheet
data set region, which was an overly specific solution for a generic UI.
Nowadays, the UI tree view API used for asset browser catalogs is a much
better way to implement this behavior.

To make this possible, the tree view API is extended in a few ways.
Collapsibility can now be turned off, and whether an item should
be active is moved to a separate virtual function.

The only visual change is that the items are now drawn in a box,
just like the asset catalog.

Differential Revision: https://developer.blender.org/D13198
This commit is contained in:
Hans Goudey 2021-11-19 17:36:11 -05:00
parent a0780ad625
commit 01df48a983
Notes: blender-bot 2023-02-14 10:14:07 +01:00
Referenced by issue #94380, UI behavior Inconsistency: Mouse scroll changes scale of spreadsheet panel
Referenced by issue #93250, Crash when creating a new object
Referenced by issue #92965, Refactor the spreadsheet data set region to use the abstract tree view API
18 changed files with 273 additions and 590 deletions

View File

@ -400,9 +400,8 @@ typedef enum {
/** Resize handle (resize uilist). */
UI_BTYPE_GRIP = 57 << 9,
UI_BTYPE_DECORATOR = 58 << 9,
UI_BTYPE_DATASETROW = 59 << 9,
/* An item in a tree view. Parent items may be collapsible. */
UI_BTYPE_TREEROW = 60 << 9,
UI_BTYPE_TREEROW = 59 << 9,
} eButType;
#define BUTTYPE (63 << 9)
@ -1676,11 +1675,7 @@ int UI_searchbox_size_x(void);
int UI_search_items_find_index(uiSearchItems *items, const char *name);
void UI_but_hint_drawstr_set(uiBut *but, const char *string);
void UI_but_datasetrow_indentation_set(uiBut *but, int indentation);
void UI_but_datasetrow_component_set(uiBut *but, uint8_t geometry_component_type);
void UI_but_datasetrow_domain_set(uiBut *but, uint8_t attribute_domain);
uint8_t UI_but_datasetrow_component_get(uiBut *but);
uint8_t UI_but_datasetrow_domain_get(uiBut *but);
void UI_but_treerow_indentation_set(uiBut *but, int indentation);
void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]);

View File

@ -217,15 +217,11 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
friend class TreeViewLayoutBuilder;
public:
using IsActiveFn = std::function<bool()>;
private:
bool is_open_ = false;
bool is_active_ = false;
bool is_renaming_ = false;
IsActiveFn is_active_fn_;
protected:
/** This label is used for identifying an item (together with its parent's labels). */
std::string label_{};
@ -239,11 +235,6 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
virtual void build_context_menu(bContext &C, uiLayout &column) const;
virtual void on_activate();
/**
* Set a custom callback to check if this item should be active. There's a version without
* arguments for checking if the item is currently in an active state.
*/
virtual void is_active(IsActiveFn is_active_fn);
/**
* Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if
@ -329,6 +320,17 @@ class AbstractTreeViewItem : public TreeViewItemContainer {
*/
void activate();
/**
* If the result is not empty, it controls whether the item should be active or not,
* usually depending on the data that the view represents.
*/
virtual std::optional<bool> should_be_active() const;
/**
* Return whether the item can be collapsed. Used to disable collapsing for items with children.
*/
virtual bool supports_collapsing() const;
private:
static void rename_button_fn(bContext *, void *, char *);
static AbstractTreeViewItem *find_tree_item_from_rename_button(const uiBut &but);
@ -416,6 +418,7 @@ class AbstractTreeViewItemDropController {
*/
class BasicTreeViewItem : public AbstractTreeViewItem {
public:
using IsActiveFn = std::function<bool()>;
using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>;
BIFIconID icon;
@ -423,7 +426,11 @@ class BasicTreeViewItem : public AbstractTreeViewItem {
void build_row(uiLayout &row) override;
void add_label(uiLayout &layout, StringRefNull label_override = "");
void on_activate(ActivateFn fn);
void set_on_activate_fn(ActivateFn fn);
/**
* Set a custom callback to check if this item should be active.
*/
void set_is_active_fn(IsActiveFn fn);
protected:
/**
@ -433,9 +440,12 @@ class BasicTreeViewItem : public AbstractTreeViewItem {
*/
ActivateFn activate_fn_;
IsActiveFn is_active_fn_;
private:
static void tree_row_click_fn(struct bContext *C, void *arg1, void *arg2);
std::optional<bool> should_be_active() const override;
void on_activate() override;
};

View File

@ -3977,10 +3977,6 @@ static void ui_but_alloc_info(const eButType type,
alloc_size = sizeof(uiButCurveProfile);
alloc_str = "uiButCurveProfile";
break;
case UI_BTYPE_DATASETROW:
alloc_size = sizeof(uiButDatasetRow);
alloc_str = "uiButDatasetRow";
break;
case UI_BTYPE_TREEROW:
alloc_size = sizeof(uiButTreeRow);
alloc_str = "uiButTreeRow";
@ -4183,7 +4179,6 @@ static uiBut *ui_def_but(uiBlock *block,
UI_BTYPE_BLOCK,
UI_BTYPE_BUT_MENU,
UI_BTYPE_SEARCH_MENU,
UI_BTYPE_DATASETROW,
UI_BTYPE_TREEROW,
UI_BTYPE_POPOVER)) {
but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT);
@ -6924,15 +6919,6 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block,
return but;
}
void UI_but_datasetrow_indentation_set(uiBut *but, int indentation)
{
uiButDatasetRow *but_dataset = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
but_dataset->indentation = indentation;
BLI_assert(indentation >= 0);
}
void UI_but_treerow_indentation_set(uiBut *but, int indentation)
{
uiButTreeRow *but_row = (uiButTreeRow *)but;
@ -6950,38 +6936,6 @@ void UI_but_hint_drawstr_set(uiBut *but, const char *string)
ui_but_add_shortcut(but, string, false);
}
void UI_but_datasetrow_component_set(uiBut *but, uint8_t geometry_component_type)
{
uiButDatasetRow *but_dataset_row = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
but_dataset_row->geometry_component_type = geometry_component_type;
}
void UI_but_datasetrow_domain_set(uiBut *but, uint8_t attribute_domain)
{
uiButDatasetRow *but_dataset_row = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
but_dataset_row->attribute_domain = attribute_domain;
}
uint8_t UI_but_datasetrow_component_get(uiBut *but)
{
uiButDatasetRow *but_dataset_row = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
return but_dataset_row->geometry_component_type;
}
uint8_t UI_but_datasetrow_domain_get(uiBut *but)
{
uiButDatasetRow *but_dataset_row = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
return but_dataset_row->attribute_domain;
}
void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4])
{
but->flag |= UI_BUT_NODE_LINK;

View File

@ -2334,9 +2334,6 @@ static void ui_apply_but(
case UI_BTYPE_LISTROW:
ui_apply_but_LISTROW(C, block, but, data);
break;
case UI_BTYPE_DATASETROW:
ui_apply_but_ROW(C, block, but, data);
break;
case UI_BTYPE_TAB:
ui_apply_but_TAB(C, but, data);
break;
@ -8012,7 +8009,6 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_CHECKBOX:
case UI_BTYPE_CHECKBOX_N:
case UI_BTYPE_ROW:
case UI_BTYPE_DATASETROW:
retval = ui_do_but_TOG(C, but, data, event);
break;
case UI_BTYPE_TREEROW:

View File

@ -351,14 +351,6 @@ typedef struct uiButProgressbar {
float progress;
} uiButProgressbar;
/** Derived struct for #UI_BTYPE_DATASETROW. */
typedef struct uiButDatasetRow {
uiBut but;
uint8_t geometry_component_type;
uint8_t attribute_domain;
int indentation;
} uiButDatasetRow;
/** Derived struct for #UI_BTYPE_TREEROW. */
typedef struct uiButTreeRow {

View File

@ -69,7 +69,6 @@ bool ui_but_is_toggle(const uiBut *but)
UI_BTYPE_CHECKBOX,
UI_BTYPE_CHECKBOX_N,
UI_BTYPE_ROW,
UI_BTYPE_DATASETROW,
UI_BTYPE_TREEROW);
}

View File

@ -114,7 +114,6 @@ typedef enum {
UI_WTYPE_LISTITEM,
UI_WTYPE_PROGRESSBAR,
UI_WTYPE_NODESOCKET,
UI_WTYPE_DATASETROW,
UI_WTYPE_TREEROW,
} uiWidgetTypeEnum;
@ -3675,13 +3674,7 @@ static void widget_treerow(
widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation);
}
static void widget_datasetrow(
uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
{
uiButDatasetRow *dataset_row = (uiButDatasetRow *)but;
BLI_assert(but->type == UI_BTYPE_DATASETROW);
widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation);
}
static void widget_nodesocket(
uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign))
@ -4476,9 +4469,6 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
wt.custom = widget_progressbar;
break;
case UI_WTYPE_DATASETROW:
wt.custom = widget_datasetrow;
break;
case UI_WTYPE_TREEROW:
wt.custom = widget_treerow;
@ -4811,11 +4801,6 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
fstyle = &style->widgetlabel;
break;
case UI_BTYPE_DATASETROW:
wt = widget_type(UI_WTYPE_DATASETROW);
fstyle = &style->widgetlabel;
break;
case UI_BTYPE_TREEROW:
wt = widget_type(UI_WTYPE_TREEROW);
fstyle = &style->widgetlabel;

View File

@ -350,9 +350,14 @@ void AbstractTreeViewItem::on_activate()
/* Do nothing by default. */
}
void AbstractTreeViewItem::is_active(IsActiveFn is_active_fn)
std::optional<bool> AbstractTreeViewItem::should_be_active() const
{
is_active_fn_ = is_active_fn;
return std::nullopt;
}
bool AbstractTreeViewItem::supports_collapsing() const
{
return true;
}
std::unique_ptr<AbstractTreeViewItemDragController> AbstractTreeViewItem::create_drag_controller()
@ -504,7 +509,10 @@ void AbstractTreeViewItem::set_collapsed(bool collapsed)
bool AbstractTreeViewItem::is_collapsible() const
{
return !children_.is_empty();
if (children_.is_empty()) {
return false;
}
return this->supports_collapsing();
}
bool AbstractTreeViewItem::is_renaming() const
@ -546,7 +554,8 @@ uiButTreeRow *AbstractTreeViewItem::tree_row_button()
void AbstractTreeViewItem::change_state_delayed()
{
if (is_active_fn_ && is_active_fn_()) {
const std::optional<bool> should_be_active = this->should_be_active();
if (should_be_active.has_value() && *should_be_active) {
activate();
}
}
@ -670,11 +679,24 @@ void BasicTreeViewItem::on_activate()
}
}
void BasicTreeViewItem::on_activate(ActivateFn fn)
void BasicTreeViewItem::set_on_activate_fn(ActivateFn fn)
{
activate_fn_ = fn;
}
void BasicTreeViewItem::set_is_active_fn(IsActiveFn is_active_fn)
{
is_active_fn_ = is_active_fn;
}
std::optional<bool> BasicTreeViewItem::should_be_active() const
{
if (is_active_fn_) {
return is_active_fn_();
}
return std::nullopt;
}
} // namespace blender::ui
using namespace blender::ui;

View File

@ -191,7 +191,8 @@ ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive(
{
ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>(
&catalog);
view_item.is_active([this, &catalog]() { return is_active_catalog(catalog.get_catalog_id()); });
view_item.set_is_active_fn(
[this, &catalog]() { return is_active_catalog(catalog.get_catalog_id()); });
catalog.foreach_child([&view_item, this](AssetCatalogTreeItem &child) {
build_catalog_items_recursive(view_item, child);
@ -205,11 +206,11 @@ void AssetCatalogTreeView::add_all_item()
AssetCatalogTreeViewAllItem &item = add_tree_item<AssetCatalogTreeViewAllItem>(IFACE_("All"),
ICON_HOME);
item.on_activate([params](ui::BasicTreeViewItem & /*item*/) {
item.set_on_activate_fn([params](ui::BasicTreeViewItem & /*item*/) {
params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS;
WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
});
item.is_active(
item.set_is_active_fn(
[params]() { return params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS; });
}
@ -220,11 +221,11 @@ void AssetCatalogTreeView::add_unassigned_item()
AssetCatalogTreeViewUnassignedItem &item = add_tree_item<AssetCatalogTreeViewUnassignedItem>(
IFACE_("Unassigned"), ICON_FILE_HIDDEN);
item.on_activate([params](ui::BasicTreeViewItem & /*item*/) {
item.set_on_activate_fn([params](ui::BasicTreeViewItem & /*item*/) {
params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG;
WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
});
item.is_active(
item.set_is_active_fn(
[params]() { return params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG; });
}

View File

@ -41,10 +41,10 @@ set(SRC
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_panels.cc
spreadsheet_row_filter.cc
spreadsheet_row_filter_ui.cc
@ -56,7 +56,6 @@ set(SRC
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

View File

@ -41,6 +41,8 @@
#include "WM_api.h"
#include "WM_types.h"
#include "BLT_translation.h"
#include "BLF_api.h"
#include "spreadsheet_intern.hh"
@ -591,35 +593,10 @@ static void spreadsheet_dataset_region_listener(const wmRegionListenerParams *pa
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(&region->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(&region->handlers, keymap);
}
static void spreadsheet_dataset_region_draw(const bContext *C, ARegion *region)
{
spreadsheet_update_context_path(C);
View2D *v2d = &region->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);
ED_region_panels(C, region);
}
static void spreadsheet_sidebar_init(wmWindowManager *wm, ARegion *region)
@ -710,11 +687,12 @@ void ED_spacetype_spreadsheet(void)
/* regions: channels */
art = (ARegionType *)MEM_callocN(sizeof(ARegionType), "spreadsheet dataset region");
art->regionid = RGN_TYPE_CHANNELS;
art->prefsizex = 200 + V2D_SCROLL_WIDTH;
art->prefsizex = 150 + V2D_SCROLL_WIDTH;
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D;
art->init = spreadsheet_dataset_region_init;
art->init = ED_region_panels_init;
art->draw = spreadsheet_dataset_region_draw;
art->listener = spreadsheet_dataset_region_listener;
blender::ed::spreadsheet::spreadsheet_data_set_region_panels_register(*art);
BLI_addhead(&st->regiontypes, art);
BKE_spacetype_register(st);

View File

@ -582,8 +582,7 @@ int VolumeDataSource::tot_rows() const
}
GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspreadsheet,
Object *object_eval,
const GeometryComponentType used_component_type)
Object *object_eval)
{
GeometrySet geometry_set;
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL) {
@ -615,7 +614,7 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread
}
}
else {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
if (object_eval->mode == OB_MODE_EDIT && object_eval->type == OB_MESH) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {
return geometry_set;
@ -762,8 +761,7 @@ 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 = spreadsheet_get_display_geometry_set(
sspreadsheet, object_eval, component_type);
GeometrySet geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, object_eval);
if (!geometry_set.has(component_type)) {
return {};

View File

@ -14,288 +14,226 @@
* 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 "BKE_volume.h"
#include "BLF_api.h"
#include "BLI_rect.h"
#include "RNA_access.h"
#include "UI_interface.h"
#include "UI_view2d.h"
#include "UI_interface.hh"
#include "UI_tree_view.hh"
#include "WM_types.h"
#include "BLT_translation.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_VOLUME) {
is_selected = is_component_selected;
}
return is_selected;
}
namespace blender::ed::spreadsheet {
/* -------------------------------------------------------------------- */
/* Draw Context */
class GeometryDataSetTreeView;
class DatasetDrawContext {
std::array<int, 2> mval_;
class GeometryDataSetTreeViewItem : public ui::AbstractTreeViewItem {
GeometryComponentType component_type_;
std::optional<AttributeDomain> domain_;
BIFIconID icon_;
public:
const SpaceSpreadsheet *sspreadsheet;
Object *object_eval;
/* Current geometry set, changes per component. */
GeometrySet current_geometry_set;
GeometryDataSetTreeViewItem(GeometryComponentType component_type,
StringRef label,
BIFIconID icon);
GeometryDataSetTreeViewItem(GeometryComponentType component_type,
AttributeDomain domain,
StringRef label,
BIFIconID icon);
DatasetDrawContext(const bContext *C);
void on_activate() override;
GeometrySet geometry_set_from_component(GeometryComponentType component);
const std::array<int, 2> &cursor_mval() const;
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::DatasetDrawContext(const bContext *C)
: sspreadsheet(CTX_wm_space_spreadsheet(C)),
object_eval(spreadsheet_get_object_eval(sspreadsheet, CTX_data_depsgraph_pointer(C)))
class GeometryDataSetTreeView : public ui::AbstractTreeView {
GeometrySet geometry_set_;
const bContext &C_;
SpaceSpreadsheet &sspreadsheet_;
bScreen &screen_;
friend class GeometryDataSetTreeViewItem;
public:
GeometryDataSetTreeView(GeometrySet geometry_set, const bContext &C)
: geometry_set_(std::move(geometry_set)),
C_(C),
sspreadsheet_(*CTX_wm_space_spreadsheet(&C)),
screen_(*CTX_wm_screen(&C))
{
}
void build_tree() override
{
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);
this->add_tree_item<GeometryDataSetTreeViewItem>(
GEO_COMPONENT_TYPE_VOLUME, IFACE_("Volume Grids"), ICON_VOLUME_DATA);
this->add_tree_item<GeometryDataSetTreeViewItem>(
GEO_COMPONENT_TYPE_INSTANCES, ATTR_DOMAIN_INSTANCE, IFACE_("Instances"), ICON_EMPTY_AXIS);
}
};
GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem(GeometryComponentType component_type,
StringRef label,
BIFIconID icon)
: component_type_(component_type), domain_(std::nullopt), icon_(icon)
{
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};
label_ = label;
this->set_collapsed(false);
}
GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem(GeometryComponentType component_type,
AttributeDomain domain,
StringRef label,
BIFIconID icon)
: component_type_(component_type), domain_(domain), icon_(icon)
{
label_ = label;
}
GeometrySet DatasetDrawContext::geometry_set_from_component(GeometryComponentType component)
void GeometryDataSetTreeViewItem::on_activate()
{
return spreadsheet_get_display_geometry_set(sspreadsheet, object_eval, component);
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"));
}
const std::array<int, 2> &DatasetDrawContext::cursor_mval() const
void GeometryDataSetTreeViewItem::build_row(uiLayout &row)
{
return mval_;
uiItemL(&row, label_.c_str(), icon_);
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);
}
}
/* -------------------------------------------------------------------- */
/* 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)
std::optional<bool> GeometryDataSetTreeViewItem::should_be_active() const
{
GeometryDataSetTreeView &tree_view = this->get_tree();
SpaceSpreadsheet &sspreadsheet = tree_view.sspreadsheet_;
if (component_type_ == GEO_COMPONENT_TYPE_VOLUME) {
return sspreadsheet.geometry_component_type == component_type_;
}
if (!domain_) {
return false;
}
return sspreadsheet.geometry_component_type == component_type_ &&
sspreadsheet.attribute_domain == *domain_;
}
void DatasetRegionDrawer::draw_hierarchy(const DatasetLayoutHierarchy &layout)
bool GeometryDataSetTreeViewItem::supports_collapsing() const
{
for (const DatasetComponentLayoutInfo &component : layout.components) {
draw_context.current_geometry_set = draw_context.geometry_set_from_component(component.type);
return false;
}
draw_component_row(component);
GeometryDataSetTreeView &GeometryDataSetTreeViewItem::get_tree() const
{
return static_cast<GeometryDataSetTreeView &>(this->get_tree_view());
}
/* 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;
}
std::optional<int> GeometryDataSetTreeViewItem::count() const
{
GeometryDataSetTreeView &tree_view = this->get_tree();
GeometrySet &geometry = tree_view.geometry_set_;
const DatasetAttrDomainLayoutInfo &domain_info = *optional_domain;
draw_attribute_domain_row(component, domain_info);
/* Special case for volumes since there is no grid domain. */
if (component_type_ == GEO_COMPONENT_TYPE_VOLUME) {
if (const Volume *volume = geometry.get_volume_for_read()) {
return BKE_volume_num_grids(volume);
}
}
}
static int element_count_from_volume(const GeometrySet &geometry_set)
{
if (const Volume *volume = geometry_set.get_volume_for_read()) {
return BKE_volume_num_grids(volume);
}
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);
return 0;
}
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 (!domain_) {
return std::nullopt;
}
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);
}
if (geometry_set.has_instances() && component == GEO_COMPONENT_TYPE_INSTANCES) {
const InstancesComponent *instances_component =
geometry_set.get_component_for_read<InstancesComponent>();
return instances_component->attribute_domain_size(domain);
if (const GeometryComponent *component = geometry.get_component_for_read(component_type_)) {
return 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)
void spreadsheet_data_set_panel_draw(const bContext *C, Panel *panel)
{
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);
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,
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(
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. */
const SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
Object *object = spreadsheet_get_object_eval(sspreadsheet, CTX_data_depsgraph_pointer(C));
if (!object) {
return;
}
uiLayout *layout = panel->layout;
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
uiBlock *block = uiLayoutGetBlock(layout);
DatasetRegionDrawer drawer{region, *block, draw_context};
UI_block_layout_set_current(block, layout);
/* 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;
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));
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(&region->v2d, region->winx, abs(drawer.ymin_offset));
UI_block_draw(C, block);
ui::TreeViewBuilder builder(*block);
builder.build_tree_view(*tree_view);
}
} // namespace blender::ed::spreadsheet

View File

@ -16,49 +16,11 @@
#pragma once
#include <array>
#include "BKE_geometry_set.hh"
#include "UI_interface.h"
#include "spreadsheet_dataset_layout.hh"
struct ARegion;
struct View2D;
struct Panel;
struct bContext;
struct uiBlock;
namespace blender::ed::spreadsheet {
class DatasetDrawContext;
class DatasetRegionDrawer {
public:
const int row_height;
float ymin_offset = 0;
int xmin;
int xmax;
uiBlock &block;
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);
void spreadsheet_data_set_panel_draw(const bContext *C, Panel *panel);
} // namespace blender::ed::spreadsheet

View File

@ -1,118 +0,0 @@
/*
* 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_VOLUME,
N_("Volume Grids"),
ICON_VOLUME_DATA,
{},
},
{
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

View File

@ -1,68 +0,0 @@
/*
* 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

View File

@ -37,6 +37,7 @@ struct SpaceSpreadsheet_Runtime {
};
struct bContext;
struct ARegionType;
void spreadsheet_operatortypes(void);
void spreadsheet_update_context_path(const bContext *C);
@ -45,6 +46,8 @@ Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet,
namespace blender::ed::spreadsheet {
GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspreadsheet,
Object *object_eval,
const GeometryComponentType used_component_type);
}
Object *object_eval);
void spreadsheet_data_set_region_panels_register(ARegionType &region_type);
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,37 @@
/*
* 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 "BKE_screen.h"
#include "BLT_translation.h"
#include "spreadsheet_dataset_draw.hh"
#include "spreadsheet_intern.hh"
namespace blender::ed::spreadsheet {
void spreadsheet_data_set_region_panels_register(ARegionType &region_type)
{
PanelType *panel_type = (PanelType *)MEM_callocN(sizeof(PanelType), __func__);
strcpy(panel_type->idname, "SPREADSHEET_PT_data_set");
strcpy(panel_type->label, N_("Data Set"));
strcpy(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
panel_type->flag = PANEL_TYPE_NO_HEADER;
panel_type->draw = spreadsheet_data_set_panel_draw;
BLI_addtail(&region_type.paneltypes, panel_type);
}
} // namespace blender::ed::spreadsheet