UI: Improve node editor breadcrumbs display

This patch upgrades node editor breadcrumbs to have slightly more
visual weight, to including the base path of object/modifier/world,
etc, have more visually pleasing spacing, and contain icons.

In the code, a generic "context path" is added to interface code.
The idea is that this could be used to draw other breadcrumbs in areas
like the property editor or the spreadsheet, and features could be added
to all of those areas at the same time.

Ideally we would be able to control the color of the breadcrumbs with a
specific theme color, but since they are drawn with the regular layout
system, that is not easily possible.

Thanks to @fabian_schempp for the original patch.

Differential Revision: https://developer.blender.org/D10413
This commit is contained in:
Hans Goudey 2021-10-26 11:05:01 -05:00
parent f1a662c157
commit 3371a4c472
Notes: blender-bot 2023-02-14 03:13:26 +01:00
Referenced by issue #92539, Node Editor: Hard to read the breadcrumbs in Blender Light Theme
Referenced by issue #83109, Implement breadcrumbs for node editor
10 changed files with 337 additions and 32 deletions

View File

@ -64,7 +64,6 @@ void ED_node_cursor_location_set(struct SpaceNode *snode, const float value[2]);
int ED_node_tree_path_length(struct SpaceNode *snode);
void ED_node_tree_path_get(struct SpaceNode *snode, char *value);
void ED_node_tree_path_get_fixedbuf(struct SpaceNode *snode, char *value, int max_length);
void ED_node_tree_start(struct SpaceNode *snode,
struct bNodeTree *ntree,

View File

@ -23,15 +23,40 @@
#include <memory>
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "UI_resources.h"
namespace blender::nodes::geometry_nodes_eval_log {
struct GeometryAttributeInfo;
}
struct uiBlock;
struct StructRNA;
struct uiSearchItems;
namespace blender::ui {
class AbstractTreeView;
/**
* An item in a breadcrumb-like context. Currently this struct is very simple, but more
* could be added to it in the future, to support interactivity or tooltips, for example.
*/
struct ContextPathItem {
/* Text to display in the UI. */
std::string name;
/* #BIFIconID */
int icon;
};
void context_path_add_generic(Vector<ContextPathItem> &path,
StructRNA &rna_type,
void *ptr,
const BIFIconID icon_override = ICON_NONE);
void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path);
void attribute_search_add_items(
StringRefNull str,
const bool is_output,

View File

@ -43,6 +43,7 @@ set(SRC
interface_anim.c
interface_button_group.c
interface_context_menu.c
interface_context_path.cc
interface_draw.c
interface_dropboxes.cc
interface_eyedropper.c

View File

@ -0,0 +1,85 @@
/*
* 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.
*
* The Original Code is Copyright (C) 2021 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup edinterface
*/
#include "BLI_vector.hh"
#include "BKE_screen.h"
#include "RNA_access.h"
#include "ED_screen.h"
#include "UI_interface.h"
#include "UI_interface.hh"
#include "UI_resources.h"
#include "WM_api.h"
namespace blender::ui {
void context_path_add_generic(Vector<ContextPathItem> &path,
StructRNA &rna_type,
void *ptr,
const BIFIconID icon_override)
{
/* Add the null check here to make calling functions less verbose. */
if (!ptr) {
return;
}
PointerRNA rna_ptr;
RNA_pointer_create(nullptr, &rna_type, ptr, &rna_ptr);
char name[128];
RNA_struct_name_get_alloc(&rna_ptr, name, sizeof(name), nullptr);
/* Use a blank icon by default to check whether to retrieve it automatically from the type. */
const BIFIconID icon = icon_override == ICON_NONE ?
static_cast<BIFIconID>(RNA_struct_ui_icon(rna_ptr.type)) :
icon_override;
path.append({name, static_cast<int>(icon)});
}
/* -------------------------------------------------------------------- */
/** \name Breadcrumb Template
* \{ */
void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path)
{
uiLayout *row = uiLayoutRow(&layout, true);
uiLayoutSetAlignment(&layout, UI_LAYOUT_ALIGN_LEFT);
for (const int i : context_path.index_range()) {
uiLayout *sub_row = uiLayoutRow(row, true);
uiLayoutSetAlignment(sub_row, UI_LAYOUT_ALIGN_LEFT);
if (i > 0) {
uiItemL(sub_row, "", ICON_RIGHTARROW_THIN);
}
uiItemL(sub_row, context_path[i].name.c_str(), context_path[i].icon);
}
}
} // namespace blender::ui
/** \} */

View File

@ -40,6 +40,7 @@ set(INC
set(SRC
drawnode.cc
node_add.cc
node_context_path.cc
node_draw.cc
node_edit.cc
node_geometry_attribute_search.cc

View File

@ -0,0 +1,184 @@
/*
* 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.
*
* The Original Code is Copyright (C) 2008 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup spnode
* \brief Node breadcrumbs drawing
*/
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "BKE_context.h"
#include "BKE_material.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_screen.h"
#include "RNA_access.h"
#include "ED_screen.h"
#include "UI_interface.h"
#include "UI_interface.hh"
#include "UI_resources.h"
#include "UI_interface.hh"
#include "node_intern.h"
struct Mesh;
struct Curve;
struct Light;
struct World;
struct Material;
namespace blender::ed::space_node {
static void context_path_add_object_data(Vector<ui::ContextPathItem> &path, Object &object)
{
if (object.type == OB_MESH && object.data) {
Mesh *mesh = (Mesh *)object.data;
ui::context_path_add_generic(path, RNA_Mesh, mesh);
}
if (object.type == OB_LAMP && object.data) {
Light *light = (Light *)object.data;
ui::context_path_add_generic(path, RNA_Light, light);
}
if (ELEM(object.type, OB_CURVE, OB_FONT, OB_SURF) && object.data) {
Curve *curve = (Curve *)object.data;
ui::context_path_add_generic(path, RNA_Curve, curve);
}
}
static void context_path_add_node_tree_and_node_groups(const SpaceNode &snode,
Vector<ui::ContextPathItem> &path,
const bool skip_base = false)
{
Vector<const bNodeTreePath *> tree_path = snode.treepath;
for (const bNodeTreePath *path_item : tree_path.as_span().drop_front(int(skip_base))) {
ui::context_path_add_generic(path, RNA_NodeTree, path_item->nodetree, ICON_NODETREE);
}
}
static void get_context_path_node_shader(const bContext &C,
SpaceNode &snode,
Vector<ui::ContextPathItem> &path)
{
if (snode.flag & SNODE_PIN) {
if (snode.shaderfrom == SNODE_SHADER_WORLD) {
Scene *scene = CTX_data_scene(&C);
ui::context_path_add_generic(path, RNA_Scene, scene);
if (scene != nullptr) {
World *world = scene->world;
ui::context_path_add_generic(path, RNA_World, world);
}
/* Skip the base node tree here, because the world contains a node tree already. */
context_path_add_node_tree_and_node_groups(snode, path, true);
}
else {
context_path_add_node_tree_and_node_groups(snode, path);
}
}
else {
Object *object = CTX_data_active_object(&C);
if (snode.shaderfrom == SNODE_SHADER_OBJECT && object != nullptr) {
ui::context_path_add_generic(path, RNA_Object, object);
if (!(object->matbits && object->matbits[object->actcol - 1])) {
context_path_add_object_data(path, *object);
}
Material *material = BKE_object_material_get(object, object->actcol);
ui::context_path_add_generic(path, RNA_Material, material);
}
else if (snode.shaderfrom == SNODE_SHADER_WORLD) {
Scene *scene = CTX_data_scene(&C);
ui::context_path_add_generic(path, RNA_Scene, scene);
if (scene != nullptr) {
World *world = scene->world;
ui::context_path_add_generic(path, RNA_World, world);
}
}
#ifdef WITH_FREESTYLE
else if (snode.shaderfrom == SNODE_SHADER_LINESTYLE) {
ViewLayer *viewlayer = CTX_data_view_layer(&C);
FreestyleLineStyle *linestyle = BKE_linestyle_active_from_view_layer(viewlayer);
ui::context_path_add_generic(path, RNA_ViewLayer, viewlayer);
Material *mat = BKE_object_material_get(object, object->actcol);
ui::context_path_add_generic(path, RNA_Material, mat);
}
#endif
context_path_add_node_tree_and_node_groups(snode, path, true);
}
}
static void get_context_path_node_compositor(const bContext &C,
SpaceNode &snode,
Vector<ui::ContextPathItem> &path)
{
if (snode.flag & SNODE_PIN) {
context_path_add_node_tree_and_node_groups(snode, path);
}
else {
Scene *scene = CTX_data_scene(&C);
ui::context_path_add_generic(path, RNA_Scene, scene);
context_path_add_node_tree_and_node_groups(snode, path);
}
}
static void get_context_path_node_geometry(const bContext &C,
SpaceNode &snode,
Vector<ui::ContextPathItem> &path)
{
if (snode.flag & SNODE_PIN) {
context_path_add_node_tree_and_node_groups(snode, path);
}
else {
Object *object = CTX_data_active_object(&C);
ui::context_path_add_generic(path, RNA_Object, object);
ModifierData *modifier = BKE_object_active_modifier(object);
ui::context_path_add_generic(path, RNA_Modifier, modifier, ICON_MODIFIER);
context_path_add_node_tree_and_node_groups(snode, path);
}
}
Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C)
{
SpaceNode *snode = CTX_wm_space_node(&C);
if (snode == nullptr) {
return {};
}
Vector<ui::ContextPathItem> context_path;
if (snode->edittree->type == NTREE_GEOMETRY) {
get_context_path_node_geometry(C, *snode, context_path);
}
else if (snode->edittree->type == NTREE_SHADER) {
get_context_path_node_shader(C, *snode, context_path);
}
else if (snode->edittree->type == NTREE_COMPOSIT) {
get_context_path_node_compositor(C, *snode, context_path);
}
return context_path;
}
} // namespace blender::ed::space_node

View File

@ -71,8 +71,10 @@
#include "ED_gpencil.h"
#include "ED_node.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "UI_interface.hh"
#include "UI_resources.h"
#include "UI_view2d.h"
@ -2158,15 +2160,34 @@ void node_draw_nodetree(const bContext *C,
}
}
/* Draw tree path info in lower left corner. */
static void draw_tree_path(SpaceNode *snode)
/* Draw the breadcrumb on the bottom of the editor. */
static void draw_tree_path(const bContext &C, ARegion &region)
{
char info[256];
using namespace blender;
ED_node_tree_path_get_fixedbuf(snode, info, sizeof(info));
GPU_matrix_push_projection();
wmOrtho2_region_pixelspace(&region);
UI_FontThemeColor(BLF_default(), TH_TEXT_HI);
BLF_draw_default(1.5f * UI_UNIT_X, 1.5f * UI_UNIT_Y, 0.0f, info, sizeof(info));
const rcti *rect = ED_region_visible_rect(&region);
const uiStyle *style = UI_style_get_dpi();
const float padding_x = 16 * UI_DPI_FAC;
const int x = rect->xmin + padding_x;
const int y = region.winy - UI_UNIT_Y * 0.6f;
const int width = BLI_rcti_size_x(rect) - 2 * padding_x;
uiBlock *block = UI_block_begin(&C, &region, __func__, UI_EMBOSS_NONE);
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, x, y, width, 1, 0, style);
Vector<ui::ContextPathItem> context_path = ed::space_node::context_path_for_space_node(C);
ui::template_breadcrumbs(*layout, context_path);
UI_block_layout_resolve(block, nullptr, nullptr);
UI_block_end(&C, block);
UI_block_draw(&C, block);
GPU_matrix_pop_projection();
}
static void snode_setup_v2d(SpaceNode *snode, ARegion *region, const float center[2])
@ -2336,8 +2357,10 @@ void node_draw_space(const bContext *C, ARegion *region)
}
}
/* Tree path info. */
draw_tree_path(snode);
/* Draw context path. */
if (snode->edittree) {
draw_tree_path(*C, *region);
}
/* Scrollers. */
UI_view2d_scrollers_draw(v2d, nullptr);

View File

@ -341,3 +341,11 @@ extern const char *node_context_dir[];
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
# include "BLI_vector.hh"
# include "UI_interface.hh"
namespace blender::ed::space_node {
Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C);
}
#endif

View File

@ -201,27 +201,6 @@ void ED_node_tree_path_get(SpaceNode *snode, char *value)
}
}
void ED_node_tree_path_get_fixedbuf(SpaceNode *snode, char *value, int max_length)
{
int size;
value[0] = '\0';
int i = 0;
LISTBASE_FOREACH_INDEX (bNodeTreePath *, path, &snode->treepath, i) {
if (i == 0) {
size = BLI_strncpy_rlen(value, path->display_name, max_length);
}
else {
size = BLI_snprintf_rlen(value, max_length, "/%s", path->display_name);
}
max_length -= size;
if (max_length <= 0) {
break;
}
value += size;
}
}
void ED_node_set_active_viewer_key(SpaceNode *snode)
{
bNodeTreePath *path = snode->treepath.last;

View File

@ -3211,8 +3211,8 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna)
"Stroke Depth Offset",
"Move strokes slightly towards the camera to avoid clipping while "
"preserve depth for the viewport");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0f, 0.5f, 0.001f, 4);
RNA_def_property_ui_range(prop, 0.0, 0.5, 0.001, 4);
RNA_def_property_range(prop, -0.1, FLT_MAX);
RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "offset_towards_custom_camera", PROP_BOOLEAN, PROP_NONE);