Spreadsheet: improve separation of drawing and data generation

This is a refactor and no functional changes are expected.

The goal is to make it simpler to add other data sources without having
to repeat the drawing code everywhere. Also, having the `CellValue` class
allows us to implement filtering and sorting in a more generic way.
This commit is contained in:
Jacques Lucke 2021-03-15 12:19:48 +01:00
parent 4ed208bcd8
commit 5ad4713cd8
6 changed files with 359 additions and 259 deletions

View File

@ -33,11 +33,13 @@ set(INC
set(SRC
space_spreadsheet.cc
spreadsheet_column_layout.cc
spreadsheet_draw.cc
spreadsheet_from_geometry.cc
spreadsheet_ops.cc
spreadsheet_draw.hh
spreadsheet_column_layout.hh
spreadsheet_from_geometry.hh
spreadsheet_intern.hh
)

View File

@ -17,6 +17,7 @@
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_resource_collector.hh"
#include "BKE_screen.h"
@ -42,6 +43,7 @@
#include "spreadsheet_intern.hh"
#include "spreadsheet_column_layout.hh"
#include "spreadsheet_from_geometry.hh"
#include "spreadsheet_intern.hh"
@ -134,39 +136,44 @@ static ID *get_used_id(const bContext *C)
class FallbackSpreadsheetDrawer : public SpreadsheetDrawer {
};
static std::unique_ptr<SpreadsheetDrawer> generate_spreadsheet_drawer(const bContext *C)
static void gather_spreadsheet_columns(const bContext *C,
SpreadsheetColumnLayout &column_layout,
blender::ResourceCollector &resources)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
ID *used_id = get_used_id(C);
if (used_id == nullptr) {
return {};
return;
}
const ID_Type id_type = GS(used_id->name);
if (id_type != ID_OB) {
return {};
return;
}
Object *object_orig = (Object *)used_id;
if (!ELEM(object_orig->type, OB_MESH, OB_POINTCLOUD)) {
return {};
return;
}
Object *object_eval = DEG_get_evaluated_object(depsgraph, object_orig);
if (object_eval == nullptr) {
return {};
return;
}
return spreadsheet_drawer_from_geometry_attributes(C, object_eval);
return spreadsheet_columns_from_geometry(C, object_eval, column_layout, resources);
}
static void spreadsheet_main_region_draw(const bContext *C, ARegion *region)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
std::unique_ptr<SpreadsheetDrawer> drawer = generate_spreadsheet_drawer(C);
if (!drawer) {
sspreadsheet->runtime->visible_rows = 0;
sspreadsheet->runtime->tot_columns = 0;
sspreadsheet->runtime->tot_rows = 0;
drawer = std::make_unique<FallbackSpreadsheetDrawer>();
}
blender::ResourceCollector resources;
SpreadsheetColumnLayout column_layout;
gather_spreadsheet_columns(C, column_layout, resources);
sspreadsheet->runtime->visible_rows = column_layout.row_indices.size();
sspreadsheet->runtime->tot_columns = column_layout.columns.size();
sspreadsheet->runtime->tot_rows = column_layout.tot_rows;
std::unique_ptr<SpreadsheetDrawer> drawer = spreadsheet_drawer_from_column_layout(column_layout);
draw_spreadsheet_in_region(C, region, *drawer);
/* Tag footer for redraw, because the main region updates data for the footer. */

View File

@ -0,0 +1,189 @@
/*
* 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 <iomanip>
#include <sstream>
#include "spreadsheet_column_layout.hh"
#include "DNA_userdef_types.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "BLF_api.h"
namespace blender::ed::spreadsheet {
class ColumnLayoutDrawer : public SpreadsheetDrawer {
private:
const SpreadsheetColumnLayout &column_layout_;
Vector<int> column_widths_;
public:
ColumnLayoutDrawer(const SpreadsheetColumnLayout &column_layout) : column_layout_(column_layout)
{
tot_columns = column_layout.columns.size();
tot_rows = column_layout.row_indices.size();
const int fontid = UI_style_get()->widget.uifont_id;
/* Use a consistent font size for the width calculation. */
BLF_size(fontid, 11 * U.pixelsize, U.dpi);
/* The width of the index column depends on the maximum row index. */
left_column_width = std::to_string(std::max(0, column_layout_.tot_rows - 1)).size() *
BLF_width(fontid, "0", 1) +
UI_UNIT_X * 0.75;
/* The column widths depend on the column name widths. */
const int minimum_column_width = 3 * UI_UNIT_X;
const int header_name_padding = UI_UNIT_X;
for (const SpreadsheetColumn *column : column_layout_.columns) {
StringRefNull name = column->name();
const int name_width = BLF_width(fontid, name.data(), name.size());
const int width = std::max(name_width + header_name_padding, minimum_column_width);
column_widths_.append(width);
}
}
void draw_top_row_cell(int column_index, const CellDrawParams &params) const final
{
const StringRefNull name = column_layout_.columns[column_index]->name();
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
name.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
/* Center-align column headers. */
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT);
}
void draw_left_column_cell(int row_index, const CellDrawParams &params) const final
{
const int real_index = column_layout_.row_indices[row_index];
std::string index_str = std::to_string(real_index);
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
index_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
/* Right-align indices. */
UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
void draw_content_cell(int row_index, int column_index, const CellDrawParams &params) const final
{
const int real_index = column_layout_.row_indices[row_index];
const SpreadsheetColumn &column = *column_layout_.columns[column_index];
CellValue cell_value;
column.get_value(real_index, cell_value);
if (std::holds_alternative<int>(cell_value.value)) {
const int value = std::get<int>(cell_value.value);
const std::string value_str = std::to_string(value);
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
value_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
else if (std::holds_alternative<float>(cell_value.value)) {
const float value = std::get<float>(cell_value.value);
std::stringstream ss;
ss << std::fixed << std::setprecision(3) << value;
const std::string value_str = ss.str();
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
value_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
else if (std::holds_alternative<bool>(cell_value.value)) {
const bool value = std::get<bool>(cell_value.value);
const int icon = value ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
icon,
"",
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
}
int column_width(int column_index) const final
{
return column_widths_[column_index];
}
};
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_column_layout(
const SpreadsheetColumnLayout &column_layout)
{
return std::make_unique<ColumnLayoutDrawer>(column_layout);
}
} // namespace blender::ed::spreadsheet

View File

@ -0,0 +1,97 @@
/*
* 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 <variant>
#include "spreadsheet_draw.hh"
namespace blender::ed::spreadsheet {
/**
* This is a small type that can hold the value of a cell in a spreadsheet. This type allows us to
* decouple the drawing of individual cells from the code that generates the data to be displayed.
*/
class CellValue {
public:
/* The implementation just uses a `std::variant` for simplicity. It can be encapsulated better,
* but it's not really worth the complixity for now. */
using VariantType = std::variant<std::monostate, int, float, bool>;
VariantType value;
};
/**
* This represents a column in a spreadsheet. It has a name and provides a value for all the cells
* in the column.
*/
class SpreadsheetColumn {
protected:
std::string name_;
public:
SpreadsheetColumn(std::string name) : name_(std::move(name))
{
}
virtual ~SpreadsheetColumn() = default;
virtual void get_value(int index, CellValue &r_cell_value) const = 0;
StringRefNull name() const
{
return name_;
}
};
/* Utility class for the function below. */
template<typename GetValueF> class LambdaSpreadsheetColumn : public SpreadsheetColumn {
private:
GetValueF get_value_;
public:
LambdaSpreadsheetColumn(std::string name, GetValueF get_value)
: SpreadsheetColumn(std::move(name)), get_value_(std::move(get_value))
{
}
void get_value(int index, CellValue &r_cell_value) const final
{
get_value_(index, r_cell_value);
}
};
/* Utility function that simplifies creating a spreadsheet column from a lambda function. */
template<typename GetValueF>
std::unique_ptr<SpreadsheetColumn> spreadsheet_column_from_function(std::string name,
GetValueF get_value)
{
return std::make_unique<LambdaSpreadsheetColumn<GetValueF>>(std::move(name),
std::move(get_value));
}
/* This contains information required to create a spreadsheet drawer from columns. */
struct SpreadsheetColumnLayout {
Vector<const SpreadsheetColumn *> columns;
Span<int64_t> row_indices;
int tot_rows = 0;
};
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_column_layout(
const SpreadsheetColumnLayout &column_layout);
} // namespace blender::ed::spreadsheet

View File

@ -14,14 +14,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <iomanip>
#include <sstream>
#include "UI_interface.h"
#include "UI_resources.h"
#include "BLF_api.h"
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_lib_id.h"
@ -46,137 +38,6 @@ namespace blender::ed::spreadsheet {
using blender::bke::ReadAttribute;
using blender::bke::ReadAttributePtr;
class AttributeColumn {
public:
std::string name;
int width;
AttributeColumn(std::string column_name) : name(std::move(column_name))
{
/* Compute the column width based on its name. */
const int fontid = UI_style_get()->widget.uifont_id;
const int header_name_padding = UI_UNIT_X;
const int minimum_column_width = 3 * UI_UNIT_X;
/* Use a consistent font size for the width calculation. */
BLF_size(fontid, 11 * U.pixelsize, U.dpi);
const int text_width = BLF_width(fontid, name.data(), name.size());
width = std::max(text_width + header_name_padding, minimum_column_width);
}
virtual ~AttributeColumn() = default;
virtual void draw(const int index, const CellDrawParams &params) const = 0;
};
class GeometryAttributeSpreadsheetDrawer : public SpreadsheetDrawer {
private:
/* Contains resources that are used during drawing. They will be freed automatically. */
std::unique_ptr<ResourceCollector> resources_;
/* Information about how to draw the individual columns. */
Vector<std::unique_ptr<AttributeColumn>> columns_;
/* This is used to filter the selected rows. The referenced data lives at least as long as the
* resource collector above. */
Span<int64_t> visible_rows_;
public:
GeometryAttributeSpreadsheetDrawer(std::unique_ptr<ResourceCollector> resources,
Vector<std::unique_ptr<AttributeColumn>> columns,
Span<int64_t> visible_rows,
const int domain_size)
: resources_(std::move(resources)), columns_(std::move(columns)), visible_rows_(visible_rows)
{
tot_columns = columns_.size();
tot_rows = visible_rows.size();
/* Compute index column width based on number of digits. */
const int fontid = UI_style_get()->widget.uifont_id;
left_column_width = std::to_string(std::max(domain_size - 1, 0)).size() *
BLF_width(fontid, "0", 1) +
UI_UNIT_X * 0.75;
}
void draw_top_row_cell(int column_index, const CellDrawParams &params) const final
{
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
columns_[column_index]->name.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
/* Center-align column headers. */
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT);
}
void draw_left_column_cell(int row_index, const CellDrawParams &params) const final
{
const int real_index = visible_rows_[row_index];
std::string index_str = std::to_string(real_index);
uiBut *but = uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
index_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
/* Right-align indices. */
UI_but_drawflag_enable(but, UI_BUT_TEXT_RIGHT);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
void draw_content_cell(int row_index, int column_index, const CellDrawParams &params) const final
{
const int real_index = visible_rows_[row_index];
columns_[column_index]->draw(real_index, params);
}
int column_width(int column_index) const final
{
return columns_[column_index]->width;
}
};
/* Utility to make writing column drawing code more concise. */
template<typename DrawF> class CustomAttributeColumn : public AttributeColumn {
private:
DrawF draw_;
public:
CustomAttributeColumn(std::string attribute_name, DrawF draw)
: AttributeColumn(std::move(attribute_name)), draw_(std::move(draw))
{
}
void draw(const int index, const CellDrawParams &params) const final
{
draw_(index, params);
}
};
template<typename DrawF>
std::unique_ptr<CustomAttributeColumn<DrawF>> create_attribute_column(std::string attribute_name,
DrawF draw)
{
return std::make_unique<CustomAttributeColumn<DrawF>>(std::move(attribute_name),
std::move(draw));
}
static Vector<std::string> get_sorted_attribute_names_to_display(
const GeometryComponent &component, const AttributeDomain domain)
{
@ -196,92 +57,30 @@ static Vector<std::string> get_sorted_attribute_names_to_display(
return attribute_names;
}
static void draw_float_in_cell(const CellDrawParams &params, const float value)
{
std::stringstream ss;
ss << std::fixed << std::setprecision(3) << value;
const std::string value_str = ss.str();
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
value_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
static void draw_int_in_cell(const CellDrawParams &params, const int value)
{
const std::string value_str = std::to_string(value);
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
ICON_NONE,
value_str.c_str(),
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
static void draw_bool_in_cell(const CellDrawParams &params, const bool value)
{
const int icon = value ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
uiDefIconTextBut(params.block,
UI_BTYPE_LABEL,
0,
icon,
"",
params.xmin,
params.ymin,
params.width,
params.height,
nullptr,
0,
0,
0,
0,
nullptr);
}
static void add_columns_for_attribute(const ReadAttribute *attribute,
const StringRefNull attribute_name,
Vector<std::unique_ptr<AttributeColumn>> &columns)
Vector<std::unique_ptr<SpreadsheetColumn>> &columns)
{
const CustomDataType data_type = attribute->custom_data_type();
switch (data_type) {
case CD_PROP_FLOAT: {
columns.append(create_attribute_column(attribute_name,
[attribute](int index, const CellDrawParams &params) {
float value;
attribute->get(index, &value);
draw_float_in_cell(params, value);
}));
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
float value;
attribute->get(index, &value);
r_cell_value.value = value;
}));
break;
}
case CD_PROP_FLOAT2: {
static std::array<char, 2> axis_char = {'X', 'Y'};
for (const int i : {0, 1}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(
create_attribute_column(name, [attribute, i](int index, const CellDrawParams &params) {
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
float2 value;
attribute->get(index, &value);
draw_float_in_cell(params, value[i]);
r_cell_value.value = value[i];
}));
}
break;
@ -290,11 +89,11 @@ static void add_columns_for_attribute(const ReadAttribute *attribute,
static std::array<char, 3> axis_char = {'X', 'Y', 'Z'};
for (const int i : {0, 1, 2}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(
create_attribute_column(name, [attribute, i](int index, const CellDrawParams &params) {
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
float3 value;
attribute->get(index, &value);
draw_float_in_cell(params, value[i]);
r_cell_value.value = value[i];
}));
}
break;
@ -303,31 +102,31 @@ static void add_columns_for_attribute(const ReadAttribute *attribute,
static std::array<char, 4> axis_char = {'R', 'G', 'B', 'A'};
for (const int i : {0, 1, 2, 3}) {
std::string name = attribute_name + " " + axis_char[i];
columns.append(
create_attribute_column(name, [attribute, i](int index, const CellDrawParams &params) {
columns.append(spreadsheet_column_from_function(
name, [attribute, i](int index, CellValue &r_cell_value) {
Color4f value;
attribute->get(index, &value);
draw_float_in_cell(params, value[i]);
r_cell_value.value = value[i];
}));
}
break;
}
case CD_PROP_INT32: {
columns.append(create_attribute_column(attribute_name,
[attribute](int index, const CellDrawParams &params) {
int value;
attribute->get(index, &value);
draw_int_in_cell(params, value);
}));
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
int value;
attribute->get(index, &value);
r_cell_value.value = value;
}));
break;
}
case CD_PROP_BOOL: {
columns.append(create_attribute_column(attribute_name,
[attribute](int index, const CellDrawParams &params) {
bool value;
attribute->get(index, &value);
draw_bool_in_cell(params, value);
}));
columns.append(spreadsheet_column_from_function(
attribute_name, [attribute](int index, CellValue &r_cell_value) {
bool value;
attribute->get(index, &value);
r_cell_value.value = value;
}));
break;
}
default:
@ -526,52 +325,56 @@ static GeometryComponentType get_display_component_type(const bContext *C, Objec
return GEO_COMPONENT_TYPE_MESH;
}
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_geometry_attributes(const bContext *C,
Object *object_eval)
void spreadsheet_columns_from_geometry(const bContext *C,
Object *object_eval,
SpreadsheetColumnLayout &column_layout,
ResourceCollector &resources)
{
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);
/* Create a resource collector that owns stuff that needs to live until drawing is done. */
std::unique_ptr<ResourceCollector> resources = std::make_unique<ResourceCollector>();
GeometrySet &geometry_set = resources->add_value(
GeometrySet &geometry_set = resources.add_value(
get_display_geometry_set(sspreadsheet, object_eval, component_type), "geometry set");
const GeometryComponent *component = geometry_set.get_component_for_read(component_type);
if (component == nullptr) {
return {};
return;
}
if (!component->attribute_domain_supported(domain)) {
return {};
return;
}
Vector<std::string> attribute_names = get_sorted_attribute_names_to_display(*component, domain);
Vector<std::unique_ptr<AttributeColumn>> columns;
Vector<std::unique_ptr<SpreadsheetColumn>> &columns =
resources.construct<Vector<std::unique_ptr<SpreadsheetColumn>>>("columns");
for (StringRefNull attribute_name : attribute_names) {
ReadAttributePtr attribute_ptr = component->attribute_try_get_for_read(attribute_name);
ReadAttribute &attribute = *attribute_ptr;
resources->add(std::move(attribute_ptr), "attribute");
resources.add(std::move(attribute_ptr), "attribute");
add_columns_for_attribute(&attribute, attribute_name, columns);
}
for (std::unique_ptr<SpreadsheetColumn> &column : columns) {
column_layout.columns.append(column.get());
}
/* The filter below only works for mesh vertices currently. */
Span<int64_t> visible_rows;
if (component_type == GEO_COMPONENT_TYPE_MESH) {
visible_rows = filter_mesh_elements_by_selection(
C, object_eval, static_cast<const MeshComponent *>(component), domain, *resources);
C, object_eval, static_cast<const MeshComponent *>(component), domain, resources);
}
else {
visible_rows = IndexRange(component->attribute_domain_size(domain)).as_span();
}
const int domain_size = component->attribute_domain_size(domain);
sspreadsheet->runtime->tot_rows = domain_size;
sspreadsheet->runtime->visible_rows = visible_rows.size();
sspreadsheet->runtime->tot_columns = columns.size();
return std::make_unique<GeometryAttributeSpreadsheetDrawer>(
std::move(resources), std::move(columns), visible_rows, domain_size);
column_layout.row_indices = visible_rows;
column_layout.tot_rows = domain_size;
}
} // namespace blender::ed::spreadsheet

View File

@ -20,13 +20,15 @@
#include "BLI_resource_collector.hh"
#include "spreadsheet_draw.hh"
#include "spreadsheet_column_layout.hh"
struct bContext;
namespace blender::ed::spreadsheet {
std::unique_ptr<SpreadsheetDrawer> spreadsheet_drawer_from_geometry_attributes(
const bContext *C, Object *object_eval);
void spreadsheet_columns_from_geometry(const bContext *C,
Object *object_eval,
SpreadsheetColumnLayout &column_layout,
ResourceCollector &resources);
} // namespace blender::ed::spreadsheet