Files
test2/source/blender/editors/space_node/node_gizmo.cc
Habib Gahbiche c956145530 Compositor: implement toggle for backdrop gizmos
The new gizmo toggle controls backdrop gizmo visibility for the
compositor node editor.

The implementation is consistent with the image editor, where the gizmo
toggle has a menu with a single entry, instead of having no drop down
menu at all.

The new toggle will be set inactive when there is no node tree in a
follow up PR: https://projects.blender.org/blender/blender/pulls/139526

Pull Request: https://projects.blender.org/blender/blender/pulls/139525
2025-06-02 17:48:19 +02:00

1116 lines
38 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spnode
*/
#include <cmath>
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_context.hh"
#include "BKE_image.hh"
#include "BKE_node_runtime.hh"
#include "ED_gizmo_library.hh"
#include "ED_screen.hh"
#include "IMB_imbuf_types.hh"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "WM_types.hh"
#include "node_intern.hh"
namespace blender::ed::space_node {
/* -------------------------------------------------------------------- */
/** \name Local Utilities
* \{ */
static void node_gizmo_calc_matrix_space(const SpaceNode *snode,
const ARegion *region,
float matrix_space[4][4])
{
unit_m4(matrix_space);
mul_v3_fl(matrix_space[0], snode->zoom);
mul_v3_fl(matrix_space[1], snode->zoom);
matrix_space[3][0] = (region->winx / 2) + snode->xof;
matrix_space[3][1] = (region->winy / 2) + snode->yof;
}
static void node_gizmo_calc_matrix_space_with_image_dims(const SpaceNode *snode,
const ARegion *region,
const float2 &image_dims,
const float2 &image_offset,
float matrix_space[4][4])
{
unit_m4(matrix_space);
mul_v3_fl(matrix_space[0], snode->zoom * image_dims.x);
mul_v3_fl(matrix_space[1], snode->zoom * image_dims.y);
matrix_space[3][0] = ((region->winx / 2) + snode->xof) -
((image_dims.x / 2.0f - image_offset.x) * snode->zoom);
matrix_space[3][1] = ((region->winy / 2) + snode->yof) -
((image_dims.y / 2.0f - image_offset.y) * snode->zoom);
}
static bool node_gizmo_is_set_visible(const bContext *C)
{
SpaceNode *snode = CTX_wm_space_node(C);
if (snode == nullptr) {
return false;
}
if ((snode->flag & SNODE_BACKDRAW) == 0) {
return false;
}
if (!snode->edittree || snode->edittree->type != NTREE_COMPOSIT) {
return false;
}
if (!(snode->gizmo_flag & (SNODE_GIZMO_HIDE | SNODE_GIZMO_HIDE_ACTIVE_NODE))) {
return true;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Backdrop Gizmo
* \{ */
static void gizmo_node_backdrop_prop_matrix_get(const wmGizmo * /*gz*/,
wmGizmoProperty *gz_prop,
void *value_p)
{
float(*matrix)[4] = (float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
const SpaceNode *snode = (const SpaceNode *)gz_prop->custom_func.user_data;
matrix[0][0] = snode->zoom;
matrix[1][1] = snode->zoom;
matrix[3][0] = snode->xof;
matrix[3][1] = snode->yof;
}
static void gizmo_node_backdrop_prop_matrix_set(const wmGizmo * /*gz*/,
wmGizmoProperty *gz_prop,
const void *value_p)
{
const float(*matrix)[4] = (const float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
SpaceNode *snode = (SpaceNode *)gz_prop->custom_func.user_data;
snode->zoom = matrix[0][0];
snode->xof = matrix[3][0];
snode->yof = matrix[3][1];
}
static bool WIDGETGROUP_node_transform_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeViewer")) {
return true;
}
return false;
}
static void WIDGETGROUP_node_transform_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
wmGizmoWrapper *wwrapper = MEM_mallocN<wmGizmoWrapper>(__func__);
wwrapper->gizmo = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr);
RNA_enum_set(wwrapper->gizmo->ptr,
"transform",
ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE_XFORM_FLAG_SCALE_UNIFORM);
gzgroup->customdata = wwrapper;
}
static void WIDGETGROUP_node_transform_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
wmGizmo *cage = ((wmGizmoWrapper *)gzgroup->customdata)->gizmo;
const ARegion *region = CTX_wm_region(C);
/* center is always at the origin */
const float origin[3] = {float(region->winx / 2), float(region->winy / 2), 0.0f};
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
const float2 dims = {
(ibuf->x > 0) ? ibuf->x : 64.0f,
(ibuf->y > 0) ? ibuf->y : 64.0f,
};
RNA_float_set_array(cage->ptr, "dimensions", dims);
WM_gizmo_set_matrix_location(cage, origin);
WM_gizmo_set_flag(cage, WM_GIZMO_HIDDEN, false);
/* Need to set property here for undo. TODO: would prefer to do this in _init. */
SpaceNode *snode = CTX_wm_space_node(C);
#if 0
PointerRNA nodeptr = RNA_pointer_create_discrete(snode->id, &RNA_SpaceNodeEditor, snode);
WM_gizmo_target_property_def_rna(cage, "offset", &nodeptr, "backdrop_offset", -1);
WM_gizmo_target_property_def_rna(cage, "scale", &nodeptr, "backdrop_zoom", -1);
#endif
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_node_backdrop_prop_matrix_get;
params.value_set_fn = gizmo_node_backdrop_prop_matrix_set;
params.range_get_fn = nullptr;
params.user_data = snode;
WM_gizmo_target_property_def_func(cage, "matrix", &params);
}
else {
WM_gizmo_set_flag(cage, WM_GIZMO_HIDDEN, true);
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_transform(wmGizmoGroupType *gzgt)
{
gzgt->name = "Backdrop Transform Widget";
gzgt->idname = "NODE_GGT_backdrop_transform";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_transform_poll;
gzgt->setup = WIDGETGROUP_node_transform_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->refresh = WIDGETGROUP_node_transform_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Crop Gizmo
* \{ */
struct NodeBBoxWidgetGroup {
wmGizmo *border;
struct {
float2 dims;
float2 offset;
} state;
struct {
PointerRNA ptr;
PropertyRNA *prop;
bContext *context;
} update_data;
};
static void gizmo_node_bbox_update(NodeBBoxWidgetGroup *bbox_group)
{
RNA_property_update(
bbox_group->update_data.context, &bbox_group->update_data.ptr, bbox_group->update_data.prop);
}
static void node_input_to_rect(const bNode *node,
const float2 &dims,
const float2 offset,
rctf *r_rect)
{
const bNodeSocket *x_input = bke::node_find_socket(*node, SOCK_IN, "X");
PointerRNA x_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(x_input));
const float xmin = float(RNA_int_get(&x_input_rna_pointer, "default_value"));
const bNodeSocket *y_input = bke::node_find_socket(*node, SOCK_IN, "Y");
PointerRNA y_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(y_input));
const float ymin = float(RNA_int_get(&y_input_rna_pointer, "default_value"));
const bNodeSocket *width_input = bke::node_find_socket(*node, SOCK_IN, "Width");
PointerRNA width_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(width_input));
const float width = float(RNA_int_get(&width_input_rna_pointer, "default_value"));
const bNodeSocket *height_input = bke::node_find_socket(*node, SOCK_IN, "Height");
PointerRNA height_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(height_input));
const float height = float(RNA_int_get(&height_input_rna_pointer, "default_value"));
r_rect->xmin = (xmin + offset.x) / dims.x;
r_rect->xmax = (xmin + width + offset.x) / dims.x;
r_rect->ymin = (ymin + offset.y) / dims.y;
r_rect->ymax = (ymin + height + offset.y) / dims.y;
}
static void node_input_from_rect(bNode *node,
const rctf *rect,
const float2 &dims,
const float2 &offset)
{
bNodeSocket *x_input = bke::node_find_socket(*node, SOCK_IN, "X");
PointerRNA x_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(x_input));
bNodeSocket *y_input = bke::node_find_socket(*node, SOCK_IN, "Y");
PointerRNA y_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(y_input));
bNodeSocket *width_input = bke::node_find_socket(*node, SOCK_IN, "Width");
PointerRNA width_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(width_input));
bNodeSocket *height_input = bke::node_find_socket(*node, SOCK_IN, "Height");
PointerRNA height_input_rna_pointer = RNA_pointer_create_discrete(
nullptr, &RNA_NodeSocket, const_cast<bNodeSocket *>(height_input));
const float xmin = rect->xmin * dims.x - offset.x;
const float width = rect->xmax * dims.x - offset.x - xmin;
const float ymin = rect->ymin * dims.y - offset.y;
const float height = rect->ymax * dims.y - offset.y - ymin;
RNA_int_set(&x_input_rna_pointer, "default_value", int(xmin));
RNA_int_set(&y_input_rna_pointer, "default_value", int(ymin));
RNA_int_set(&width_input_rna_pointer, "default_value", int(width));
RNA_int_set(&height_input_rna_pointer, "default_value", int(height));
}
/* scale callbacks */
static void gizmo_node_crop_prop_matrix_get(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
void *value_p)
{
float(*matrix)[4] = (float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *crop_group = (NodeBBoxWidgetGroup *)gz->parent_gzgroup->customdata;
const float2 dims = crop_group->state.dims;
const float2 offset = crop_group->state.offset;
const bNode *node = (const bNode *)gz_prop->custom_func.user_data;
rctf rct;
node_input_to_rect(node, dims, offset, &rct);
matrix[0][0] = fabsf(BLI_rctf_size_x(&rct));
matrix[1][1] = fabsf(BLI_rctf_size_y(&rct));
matrix[3][0] = (BLI_rctf_cent_x(&rct) - 0.5f) * dims[0];
matrix[3][1] = (BLI_rctf_cent_y(&rct) - 0.5f) * dims[1];
}
static void gizmo_node_crop_prop_matrix_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
const float(*matrix)[4] = (const float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *crop_group = (NodeBBoxWidgetGroup *)gz->parent_gzgroup->customdata;
const float2 dims = crop_group->state.dims;
const float2 offset = crop_group->state.offset;
bNode *node = (bNode *)gz_prop->custom_func.user_data;
rctf rct;
node_input_to_rect(node, dims, offset, &rct);
BLI_rctf_resize(&rct, fabsf(matrix[0][0]), fabsf(matrix[1][1]));
BLI_rctf_recenter(&rct, ((matrix[3][0]) / dims[0]) + 0.5f, ((matrix[3][1]) / dims[1]) + 0.5f);
rctf rct_isect{};
rct_isect.xmin = offset.x / dims.x;
rct_isect.xmax = offset.x / dims.x + 1;
rct_isect.ymin = offset.y;
rct_isect.ymax = offset.y / dims.y + 1;
BLI_rctf_isect(&rct_isect, &rct, &rct);
node_input_from_rect(node, &rct, dims, offset);
gizmo_node_bbox_update(crop_group);
}
static bool WIDGETGROUP_node_crop_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (!node || !node->is_type("CompositorNodeCrop")) {
return false;
}
snode->edittree->ensure_topology_cache();
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
if (!STREQ(input->name, "Image") && input->is_directly_linked()) {
/* Note: the Image input could be connected to a single value input, in which case the gizmo
* has no effect. */
return false;
}
else if (STREQ(input->name, "Alpha Crop") && !input->is_directly_linked()) {
PointerRNA input_rna_pointer = RNA_pointer_create_discrete(nullptr, &RNA_NodeSocket, input);
if (RNA_boolean_get(&input_rna_pointer, "default_value")) {
/* If Alpha Crop is not set, the image size changes depending on the input parameters,
* so we can't usefully edit the crop in this case. */
return true;
}
}
}
return false;
}
static void WIDGETGROUP_node_crop_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeBBoxWidgetGroup *crop_group = MEM_new<NodeBBoxWidgetGroup>(__func__);
crop_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr);
RNA_enum_set(crop_group->border->ptr,
"transform",
ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE_XFORM_FLAG_SCALE);
gzgroup->customdata = crop_group;
gzgroup->customdata_free = [](void *customdata) {
MEM_delete(static_cast<NodeBBoxWidgetGroup *>(customdata));
};
}
static void WIDGETGROUP_node_crop_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
{
ARegion *region = CTX_wm_region(C);
wmGizmo *gz = (wmGizmo *)gzgroup->gizmos.first;
SpaceNode *snode = CTX_wm_space_node(C);
node_gizmo_calc_matrix_space(snode, region, gz->matrix_space);
}
static void WIDGETGROUP_node_crop_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
SpaceNode *snode = CTX_wm_space_node(C);
NodeBBoxWidgetGroup *crop_group = (NodeBBoxWidgetGroup *)gzgroup->customdata;
wmGizmo *gz = crop_group->border;
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
crop_group->state.dims[0] = (ibuf->x > 0) ? ibuf->x : 64.0f;
crop_group->state.dims[1] = (ibuf->y > 0) ? ibuf->y : 64.0f;
copy_v2_v2(crop_group->state.offset, ima->runtime->backdrop_offset);
RNA_float_set_array(gz->ptr, "dimensions", crop_group->state.dims);
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
bNode *node = bke::node_get_active(*snode->edittree);
crop_group->update_data.context = (bContext *)C;
crop_group->update_data.ptr = RNA_pointer_create_discrete(
(ID *)snode->edittree, &RNA_CompositorNodeCrop, node);
crop_group->update_data.prop = RNA_struct_find_property(&crop_group->update_data.ptr,
"relative");
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_node_crop_prop_matrix_get;
params.value_set_fn = gizmo_node_crop_prop_matrix_set;
params.range_get_fn = nullptr;
params.user_data = node;
WM_gizmo_target_property_def_func(gz, "matrix", &params);
}
else {
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_crop(wmGizmoGroupType *gzgt)
{
gzgt->name = "Backdrop Crop Widget";
gzgt->idname = "NODE_GGT_backdrop_crop";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_crop_poll;
gzgt->setup = WIDGETGROUP_node_crop_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_node_crop_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_crop_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Box Mask
* \{ */
static void gizmo_node_box_mask_prop_matrix_get(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
void *value_p)
{
float(*matrix)[4] = (float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *mask_group = (NodeBBoxWidgetGroup *)gz->parent_gzgroup->customdata;
const float2 dims = mask_group->state.dims;
const float2 offset = mask_group->state.offset;
const bNode *node = (const bNode *)gz_prop->custom_func.user_data;
const float aspect = dims.x / dims.y;
float loc[3], rot[3][3], size[3];
mat4_to_loc_rot_size(loc, rot, size, matrix);
const bNodeSocket *rotation_input = bke::node_find_socket(*node, SOCK_IN, "Rotation");
const float rotation = rotation_input->default_value_typed<bNodeSocketValueFloat>()->value;
axis_angle_to_mat3_single(rot, 'Z', rotation);
const bNodeSocket *position_input = bke::node_find_socket(*node, SOCK_IN, "Position");
const float2 position = position_input->default_value_typed<bNodeSocketValueVector>()->value;
loc[0] = (position.x - 0.5) * dims.x + offset.x;
loc[1] = (position.y - 0.5) * dims.y + offset.y;
loc[2] = 0;
const bNodeSocket *size_input = bke::node_find_socket(*node, SOCK_IN, "Size");
const float2 size_value = size_input->default_value_typed<bNodeSocketValueVector>()->value;
size[0] = size_value.x;
size[1] = size_value.y * aspect;
size[2] = 1;
loc_rot_size_to_mat4(matrix, loc, rot, size);
}
static void gizmo_node_box_mask_prop_matrix_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
const float(*matrix)[4] = (const float(*)[4])value_p;
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *mask_group = (NodeBBoxWidgetGroup *)gz->parent_gzgroup->customdata;
const float2 dims = mask_group->state.dims;
const float2 offset = mask_group->state.offset;
bNode *node = (bNode *)gz_prop->custom_func.user_data;
bNodeSocket *position_input = bke::node_find_socket(*node, SOCK_IN, "Position");
const float2 position = position_input->default_value_typed<bNodeSocketValueVector>()->value;
bNodeSocket *size_input = bke::node_find_socket(*node, SOCK_IN, "Size");
const float2 size_value = size_input->default_value_typed<bNodeSocketValueVector>()->value;
const float aspect = dims.x / dims.y;
rctf rct;
rct.xmin = position.x - size_value.x / 2;
rct.xmax = position.x + size_value.x / 2;
rct.ymin = position.y - size_value.y / 2;
rct.ymax = position.y + size_value.y / 2;
float loc[3];
float rot[3][3];
float size[3];
mat4_to_loc_rot_size(loc, rot, size, matrix);
float eul[3];
/* Rotation can't be extracted from matrix when the gizmo width or height is zero. */
if (size[0] != 0 and size[1] != 0) {
mat4_to_eul(eul, matrix);
bNodeSocket *rotation_input = bke::node_find_socket(*node, SOCK_IN, "Rotation");
rotation_input->default_value_typed<bNodeSocketValueFloat>()->value = eul[2];
}
BLI_rctf_resize(&rct, fabsf(size[0]), fabsf(size[1]) / aspect);
BLI_rctf_recenter(
&rct, ((loc[0] - offset.x) / dims.x) + 0.5, ((loc[1] - offset.y) / dims.y) + 0.5);
size_input->default_value_typed<bNodeSocketValueVector>()->value[0] = size[0];
size_input->default_value_typed<bNodeSocketValueVector>()->value[1] = size[1] / aspect;
position_input->default_value_typed<bNodeSocketValueVector>()->value[0] = rct.xmin +
size_value.x / 2;
position_input->default_value_typed<bNodeSocketValueVector>()->value[1] = rct.ymin +
size_value.y / 2;
gizmo_node_bbox_update(mask_group);
}
static bool WIDGETGROUP_node_box_mask_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeBoxMask")) {
snode->edittree->ensure_topology_cache();
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
if (STR_ELEM(input->name, "Position", "Size", "Rotation") && input->is_directly_linked()) {
return false;
}
}
return true;
}
return false;
}
static void WIDGETGROUP_node_box_mask_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeBBoxWidgetGroup *mask_group = MEM_new<NodeBBoxWidgetGroup>(__func__);
mask_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr);
RNA_enum_set(mask_group->border->ptr,
"transform",
ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE_XFORM_FLAG_ROTATE |
ED_GIZMO_CAGE_XFORM_FLAG_SCALE);
RNA_enum_set(mask_group->border->ptr,
"draw_options",
ED_GIZMO_CAGE_DRAW_FLAG_XFORM_CENTER_HANDLE |
ED_GIZMO_CAGE_DRAW_FLAG_CORNER_HANDLES);
gzgroup->customdata = mask_group;
gzgroup->customdata_free = [](void *customdata) {
MEM_delete(static_cast<NodeBBoxWidgetGroup *>(customdata));
};
}
static void WIDGETGROUP_bbox_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
{
ARegion *region = CTX_wm_region(C);
wmGizmo *gz = (wmGizmo *)gzgroup->gizmos.first;
SpaceNode *snode = CTX_wm_space_node(C);
node_gizmo_calc_matrix_space(snode, region, gz->matrix_space);
}
static void WIDGETGROUP_node_mask_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
NodeBBoxWidgetGroup *mask_group = (NodeBBoxWidgetGroup *)gzgroup->customdata;
wmGizmo *gz = mask_group->border;
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Render Result");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
mask_group->state.dims[0] = (ibuf->x > 0) ? ibuf->x : 64.0f;
mask_group->state.dims[1] = (ibuf->y > 0) ? ibuf->y : 64.0f;
copy_v2_v2(mask_group->state.offset, ima->runtime->backdrop_offset);
RNA_float_set_array(gz->ptr, "dimensions", mask_group->state.dims);
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
mask_group->update_data.context = (bContext *)C;
mask_group->update_data.ptr = RNA_pointer_create_discrete(
(ID *)snode->edittree, &RNA_CompositorNodeCrop, node);
mask_group->update_data.prop = RNA_struct_find_property(&mask_group->update_data.ptr, "x");
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_node_box_mask_prop_matrix_get;
params.value_set_fn = gizmo_node_box_mask_prop_matrix_set;
params.range_get_fn = nullptr;
params.user_data = node;
WM_gizmo_target_property_def_func(gz, "matrix", &params);
}
else {
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_box_mask(wmGizmoGroupType *gzgt)
{
gzgt->name = "Backdrop Box Mask Widget";
gzgt->idname = "NODE_GGT_backdrop_box_mask";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_box_mask_poll;
gzgt->setup = WIDGETGROUP_node_box_mask_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_bbox_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_mask_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ellipse Mask
* \{ */
static bool WIDGETGROUP_node_ellipse_mask_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeEllipseMask")) {
snode->edittree->ensure_topology_cache();
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
if (STR_ELEM(input->name, "Position", "Size", "Rotation") && input->is_directly_linked()) {
return false;
}
}
return true;
}
return false;
}
static void WIDGETGROUP_node_ellipse_mask_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeBBoxWidgetGroup *mask_group = MEM_new<NodeBBoxWidgetGroup>(__func__);
mask_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr);
RNA_enum_set(mask_group->border->ptr,
"transform",
ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE_XFORM_FLAG_ROTATE |
ED_GIZMO_CAGE_XFORM_FLAG_SCALE);
RNA_enum_set(mask_group->border->ptr, "draw_style", ED_GIZMO_CAGE2D_STYLE_CIRCLE);
RNA_enum_set(mask_group->border->ptr,
"draw_options",
ED_GIZMO_CAGE_DRAW_FLAG_XFORM_CENTER_HANDLE |
ED_GIZMO_CAGE_DRAW_FLAG_CORNER_HANDLES);
gzgroup->customdata = mask_group;
gzgroup->customdata_free = [](void *customdata) {
MEM_delete(static_cast<NodeBBoxWidgetGroup *>(customdata));
};
}
void NODE_GGT_backdrop_ellipse_mask(wmGizmoGroupType *gzgt)
{
gzgt->name = "Backdrop Ellipse Mask Widget";
gzgt->idname = "NODE_GGT_backdrop_ellipse_mask";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_ellipse_mask_poll;
gzgt->setup = WIDGETGROUP_node_ellipse_mask_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_bbox_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_mask_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sun Beams
* \{ */
struct NodeSunBeamsWidgetGroup {
wmGizmo *gizmo;
struct {
float2 dims;
float2 offset;
} state;
};
static bool WIDGETGROUP_node_sbeam_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeSunBeams")) {
snode->edittree->ensure_topology_cache();
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
if (STR_ELEM(input->name, "Source") && input->is_directly_linked()) {
return false;
}
}
return true;
}
return false;
}
static void WIDGETGROUP_node_sbeam_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeSunBeamsWidgetGroup *sbeam_group = MEM_mallocN<NodeSunBeamsWidgetGroup>(__func__);
sbeam_group->gizmo = WM_gizmo_new("GIZMO_GT_move_3d", gzgroup, nullptr);
wmGizmo *gz = sbeam_group->gizmo;
RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_MOVE_STYLE_CROSS_2D);
gz->scale_basis = 0.05f / 75.0f;
gzgroup->customdata = sbeam_group;
}
static void WIDGETGROUP_node_sbeam_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
{
NodeSunBeamsWidgetGroup *sbeam_group = (NodeSunBeamsWidgetGroup *)gzgroup->customdata;
ARegion *region = CTX_wm_region(C);
wmGizmo *gz = (wmGizmo *)gzgroup->gizmos.first;
SpaceNode *snode = CTX_wm_space_node(C);
node_gizmo_calc_matrix_space_with_image_dims(
snode, region, sbeam_group->state.dims, sbeam_group->state.offset, gz->matrix_space);
}
static void WIDGETGROUP_node_sbeam_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
NodeSunBeamsWidgetGroup *sbeam_group = (NodeSunBeamsWidgetGroup *)gzgroup->customdata;
wmGizmo *gz = sbeam_group->gizmo;
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
sbeam_group->state.dims[0] = (ibuf->x > 0) ? ibuf->x : 64.0f;
sbeam_group->state.dims[1] = (ibuf->y > 0) ? ibuf->y : 64.0f;
copy_v2_v2(sbeam_group->state.offset, ima->runtime->backdrop_offset);
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
/* Need to set property here for undo. TODO: would prefer to do this in _init. */
bNodeSocket *source_input = bke::node_find_socket(*node, SOCK_IN, "Source");
PointerRNA socket_pointer = RNA_pointer_create_discrete(
reinterpret_cast<ID *>(snode->edittree), &RNA_NodeSocket, source_input);
WM_gizmo_target_property_def_rna(gz, "offset", &socket_pointer, "default_value", -1);
WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_MODAL, true);
}
else {
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_sun_beams(wmGizmoGroupType *gzgt)
{
gzgt->name = "Sun Beams Widget";
gzgt->idname = "NODE_GGT_sbeam";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_sbeam_poll;
gzgt->setup = WIDGETGROUP_node_sbeam_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_node_sbeam_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_sbeam_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Corner Pin
* \{ */
struct NodeCornerPinWidgetGroup {
wmGizmo *gizmos[4];
struct {
float2 dims;
float2 offset;
} state;
};
static bool WIDGETGROUP_node_corner_pin_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeCornerPin")) {
return true;
}
return false;
}
static void WIDGETGROUP_node_corner_pin_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeCornerPinWidgetGroup *cpin_group = MEM_mallocN<NodeCornerPinWidgetGroup>(__func__);
const wmGizmoType *gzt_move_3d = WM_gizmotype_find("GIZMO_GT_move_3d", false);
for (int i = 0; i < 4; i++) {
cpin_group->gizmos[i] = WM_gizmo_new_ptr(gzt_move_3d, gzgroup, nullptr);
wmGizmo *gz = cpin_group->gizmos[i];
RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_MOVE_STYLE_CROSS_2D);
gz->scale_basis = 0.05f / 75.0;
}
gzgroup->customdata = cpin_group;
}
static void WIDGETGROUP_node_corner_pin_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
{
NodeCornerPinWidgetGroup *cpin_group = (NodeCornerPinWidgetGroup *)gzgroup->customdata;
ARegion *region = CTX_wm_region(C);
SpaceNode *snode = CTX_wm_space_node(C);
float matrix_space[4][4];
node_gizmo_calc_matrix_space_with_image_dims(
snode, region, cpin_group->state.dims, cpin_group->state.offset, matrix_space);
for (int i = 0; i < 4; i++) {
wmGizmo *gz = cpin_group->gizmos[i];
copy_m4_m4(gz->matrix_space, matrix_space);
}
}
static void WIDGETGROUP_node_corner_pin_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
NodeCornerPinWidgetGroup *cpin_group = (NodeCornerPinWidgetGroup *)gzgroup->customdata;
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
cpin_group->state.dims[0] = (ibuf->x > 0) ? ibuf->x : 64.0f;
cpin_group->state.dims[1] = (ibuf->y > 0) ? ibuf->y : 64.0f;
copy_v2_v2(cpin_group->state.offset, ima->runtime->backdrop_offset);
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
/* need to set property here for undo. TODO: would prefer to do this in _init. */
int i = 0;
for (bNodeSocket *sock = (bNodeSocket *)node->inputs.first; sock && i < 4; sock = sock->next) {
if (sock->type == SOCK_VECTOR) {
wmGizmo *gz = cpin_group->gizmos[i++];
PointerRNA sockptr = RNA_pointer_create_discrete(
(ID *)snode->edittree, &RNA_NodeSocket, sock);
WM_gizmo_target_property_def_rna(gz, "offset", &sockptr, "default_value", -1);
WM_gizmo_set_flag(gz, WM_GIZMO_DRAW_MODAL, true);
}
}
}
else {
for (int i = 0; i < 4; i++) {
wmGizmo *gz = cpin_group->gizmos[i];
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
}
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_corner_pin(wmGizmoGroupType *gzgt)
{
gzgt->name = "Corner Pin Widget";
gzgt->idname = "NODE_GGT_backdrop_corner_pin";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_corner_pin_poll;
gzgt->setup = WIDGETGROUP_node_corner_pin_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_node_corner_pin_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_corner_pin_refresh;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Split
* \{ */
static bool WIDGETGROUP_node_split_poll(const bContext *C, wmGizmoGroupType * /*gzgt*/)
{
if (!node_gizmo_is_set_visible(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
if (node && node->is_type("CompositorNodeSplit")) {
snode->edittree->ensure_topology_cache();
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
if (STR_ELEM(input->name, "Factor") && input->is_directly_linked()) {
return false;
}
}
return true;
}
return false;
}
static void WIDGETGROUP_node_split_setup(const bContext * /*C*/, wmGizmoGroup *gzgroup)
{
NodeBBoxWidgetGroup *split_group = MEM_new<NodeBBoxWidgetGroup>(__func__);
split_group->border = WM_gizmo_new("GIZMO_GT_cage_2d", gzgroup, nullptr);
RNA_enum_set(split_group->border->ptr, "transform", ED_GIZMO_CAGE_XFORM_FLAG_TRANSLATE);
RNA_enum_set(split_group->border->ptr, "draw_options", ED_GIZMO_CAGE_DRAW_FLAG_NOP);
gzgroup->customdata = split_group;
gzgroup->customdata_free = [](void *customdata) {
MEM_delete(static_cast<NodeBBoxWidgetGroup *>(customdata));
};
}
static void gizmo_node_split_prop_matrix_get(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
void *value_p)
{
float(*matrix)[4] = reinterpret_cast<float(*)[4]>(value_p);
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *split_group = (NodeBBoxWidgetGroup *)gz->parent_gzgroup->customdata;
const float2 dims = split_group->state.dims;
const float2 offset = split_group->state.offset;
const bNode *node = (const bNode *)gz_prop->custom_func.user_data;
float loc[3], rot[3][3], size[3];
mat4_to_loc_rot_size(loc, rot, size, matrix);
const bNodeSocket *factor_input = bke::node_find_socket(*node, SOCK_IN, "Factor");
const float fac = factor_input->default_value_typed<bNodeSocketValueFloat>()->value;
CMPNodeSplitAxis axis = static_cast<CMPNodeSplitAxis>(node->custom2);
if (axis == CMP_NODE_SPLIT_VERTICAL) {
matrix[3][0] = offset.x;
matrix[3][1] = (fac - 0.5f) * dims.y + offset.y;
matrix[0][0] = 1.0f;
/* Set non zero scale to silence warning "Gizmo has matrix that could not be inverted". */
matrix[1][1] = std::numeric_limits<float>::epsilon();
}
else if (axis == CMP_NODE_SPLIT_HORIZONTAL) {
matrix[3][0] = (fac - 0.5f) * dims.x + offset.x;
matrix[3][1] = offset.y;
matrix[0][0] = std::numeric_limits<float>::epsilon();
matrix[1][1] = 1.0f;
}
}
static void gizmo_node_split_prop_matrix_set(const wmGizmo *gz,
wmGizmoProperty *gz_prop,
const void *value_p)
{
const float(*matrix)[4] = reinterpret_cast<const float(*)[4]>(value_p);
BLI_assert(gz_prop->type->array_length == 16);
NodeBBoxWidgetGroup *split_group = reinterpret_cast<NodeBBoxWidgetGroup *>(
gz->parent_gzgroup->customdata);
const float2 dims = split_group->state.dims;
const float2 offset = split_group->state.offset;
bNode *node = reinterpret_cast<bNode *>(gz_prop->custom_func.user_data);
bNodeSocket *factor_input = bke::node_find_socket(*node, SOCK_IN, "Factor");
CMPNodeSplitAxis axis = static_cast<CMPNodeSplitAxis>(node->custom2);
if (axis == CMPNodeSplitAxis::CMP_NODE_SPLIT_VERTICAL) {
float fac = (matrix[3][1] - offset.y) / dims.y + 0.5f;
/* Prevent dragging the gizmo outside the image. */
fac = math::clamp(fac, 0.0f, 1.0f);
factor_input->default_value_typed<bNodeSocketValueFloat>()->value = fac;
}
else if (axis == CMP_NODE_SPLIT_HORIZONTAL) {
float fac = (matrix[3][0] - offset.x) / dims.x + 0.5f;
fac = math::clamp(fac, 0.0f, 1.0f);
factor_input->default_value_typed<bNodeSocketValueFloat>()->value = fac;
}
gizmo_node_bbox_update(split_group);
}
static void WIDGETGROUP_node_split_refresh(const bContext *C, wmGizmoGroup *gzgroup)
{
Main *bmain = CTX_data_main(C);
NodeBBoxWidgetGroup *split_group = reinterpret_cast<NodeBBoxWidgetGroup *>(gzgroup->customdata);
wmGizmo *gz = split_group->border;
void *lock;
Image *ima = BKE_image_ensure_viewer(bmain, IMA_TYPE_COMPOSITE, "Render Result");
ImBuf *ibuf = BKE_image_acquire_ibuf(ima, nullptr, &lock);
if (ibuf) {
split_group->state.dims[0] = (ibuf->x > 0) ? ibuf->x : 64.0f;
split_group->state.dims[1] = (ibuf->y > 0) ? ibuf->y : 64.0f;
copy_v2_v2(split_group->state.offset, ima->runtime->backdrop_offset);
RNA_float_set_array(gz->ptr, "dimensions", split_group->state.dims);
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
SpaceNode *snode = CTX_wm_space_node(C);
bNode *node = bke::node_get_active(*snode->edittree);
split_group->update_data.context = (bContext *)C;
split_group->update_data.ptr = RNA_pointer_create_discrete(
reinterpret_cast<ID *>(snode->edittree), &RNA_CompositorNodeSplit, node);
split_group->update_data.prop = RNA_struct_find_property(&split_group->update_data.ptr,
"axis");
wmGizmoPropertyFnParams params{};
params.value_get_fn = gizmo_node_split_prop_matrix_get;
params.value_set_fn = gizmo_node_split_prop_matrix_set;
params.range_get_fn = nullptr;
params.user_data = node;
WM_gizmo_target_property_def_func(gz, "matrix", &params);
}
else {
WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
}
BKE_image_release_ibuf(ima, ibuf, lock);
}
void NODE_GGT_backdrop_split(wmGizmoGroupType *gzgt)
{
gzgt->name = "Split Widget";
gzgt->idname = "NODE_GGT_backdrop_split";
gzgt->flag |= WM_GIZMOGROUPTYPE_PERSISTENT;
gzgt->poll = WIDGETGROUP_node_split_poll;
gzgt->setup = WIDGETGROUP_node_split_setup;
gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag;
gzgt->draw_prepare = WIDGETGROUP_bbox_draw_prepare;
gzgt->refresh = WIDGETGROUP_node_split_refresh;
}
/** \} */
} // namespace blender::ed::space_node