Nodes: Improvements to edge panning in the node editor.

- New operator property to toggle edge panning in the keymap:
  This is disabled by default to avoid edge-panning in cases where it
  gets distracting, such as adding a new node. Only the explicit
  translate operator(s) (GKEY or drag) have this enabled now.

- Restore the initial view rect on edge pan cancel:
  The initial view rect is now stored in the edge pan operator data.
  When an operator with edge panning is cancelled it can now call the
  `UI_view2d_edge_pan_cancel` function to restore the original View2D
  rect.

- Less delay in node editor scrolling:
  Delay is useful when scrolling through long lists, such as in the
  outliner, but makes node scrolling feel sluggish and unresponsive.
  The lower scroll speed here makes a faster response the better option.

- Zoom influence feature:
  Somewhat slower scrolling in UI-space when zoomed out. With the 0.5
  zoom influence factor nodes behave as if zoom factor is halved,
  otherwise it gets too fast when zoomed out. Previously scrolling would
  always be constant-speed in UI space, now it's half-way between UI
  space and node (view) space.
This commit is contained in:
Lukas Tönne 2021-08-24 17:45:40 +01:00
parent 38bdde852f
commit 19da434e9c
Notes: blender-bot 2023-03-10 16:42:47 +01:00
Referenced by issue #104482, Fix #101374: enable edge panning for move operators in menus
Referenced by issue #91023, Nodes: Improvements to edge panning in the node editor. - keymap entry vanishes by
Referenced by pull request #104482, Fix #101374: enable edge panning for move operators in menus
Referenced by commit 382a54aefb, Fix #101374: enable edge panning for move operators in menus
12 changed files with 126 additions and 43 deletions

View File

@ -1909,19 +1909,19 @@ def km_node_editor(params):
("node.clipboard_paste", {"type": 'V', "value": 'PRESS', "ctrl": True}, None),
("node.viewer_border", {"type": 'B', "value": 'PRESS', "ctrl": True}, None),
("node.clear_viewer_border", {"type": 'B', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("node.translate_attach", {"type": 'G', "value": 'PRESS'}, None),
("node.translate_attach", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, None),
("node.translate_attach", {"type": params.select_tweak, "value": 'ANY'}, None),
("transform.translate", {"type": 'G', "value": 'PRESS'}, None),
("node.translate_attach", {"type": 'G', "value": 'PRESS'}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("node.translate_attach", {"type": 'EVT_TWEAK_L', "value": 'ANY'}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("node.translate_attach", {"type": params.select_tweak, "value": 'ANY'}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("transform.translate", {"type": 'G', "value": 'PRESS'}, {"properties": [("view2d_edge_pan", True)]}),
("transform.translate", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
{"properties": [("release_confirm", True)]}),
{"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}),
("transform.translate", {"type": params.select_tweak, "value": 'ANY'},
{"properties": [("release_confirm", True)]}),
{"properties": [("release_confirm", True), ("view2d_edge_pan", True)]}),
("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
("node.move_detach_links", {"type": 'D', "value": 'PRESS', "alt": True}, None),
("node.move_detach_links_release", {"type": params.action_tweak, "value": 'ANY', "alt": True}, None),
("node.move_detach_links", {"type": params.select_tweak, "value": 'ANY', "alt": True}, None),
("node.move_detach_links", {"type": 'D', "value": 'PRESS', "alt": True}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("node.move_detach_links_release", {"type": params.action_tweak, "value": 'ANY', "alt": True}, {"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),
("node.move_detach_links", {"type": params.select_tweak, "value": 'ANY', "alt": True}, {"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("wm.context_toggle", {"type": 'TAB', "value": 'PRESS', "shift": True},
{"properties": [("data_path", 'tool_settings.use_snap')]}),
("wm.context_menu_enum", {"type": 'TAB', "value": 'PRESS', "shift": True, "ctrl": True},

View File

@ -53,8 +53,9 @@ typedef enum {
#define NODE_EDGE_PAN_INSIDE_PAD 2
#define NODE_EDGE_PAN_OUTSIDE_PAD 0 /* Disable clamping for node panning, use whole screen. */
#define NODE_EDGE_PAN_SPEED_RAMP 1
#define NODE_EDGE_PAN_MAX_SPEED 40 /* In UI units per second, slower than default. */
#define NODE_EDGE_PAN_DELAY 1.0f
#define NODE_EDGE_PAN_MAX_SPEED 26 /* In UI units per second, slower than default. */
#define NODE_EDGE_PAN_DELAY 0.5f
#define NODE_EDGE_PAN_ZOOM_INFLUENCE 0.5f
/* space_node.c */

View File

@ -137,6 +137,7 @@ int BIF_countTransformOrientation(const struct bContext *C);
#define P_GPENCIL_EDIT (1 << 13)
#define P_CURSOR_EDIT (1 << 14)
#define P_CLNOR_INVALIDATE (1 << 15)
#define P_VIEW2D_EDGE_PAN (1 << 16)
/* For properties performed when confirming the transformation. */
#define P_POST_TRANSFORM (1 << 19)

View File

@ -26,6 +26,7 @@
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_rect.h"
#ifdef __cplusplus
extern "C" {
@ -321,6 +322,14 @@ typedef struct View2DEdgePanData {
float max_speed;
/** Delay in seconds before maximum speed is reached. */
float delay;
/** Influence factor for view zoom:
* 0 = Constant speed in UI units
* 1 = Constant speed in view space, UI speed slows down when zooming out
*/
float zoom_influence;
/** Initial view rect. */
rctf initial_rect;
/** Amount to move view relative to zoom. */
float facx, facy;
@ -338,7 +347,8 @@ void UI_view2d_edge_pan_init(struct bContext *C,
float outside_pad,
float speed_ramp,
float max_speed,
float delay);
float delay,
float zoom_influence);
void UI_view2d_edge_pan_reset(struct View2DEdgePanData *vpd);
@ -350,6 +360,8 @@ void UI_view2d_edge_pan_apply_event(struct bContext *C,
struct View2DEdgePanData *vpd,
const struct wmEvent *event);
void UI_view2d_edge_pan_cancel(struct bContext *C, struct View2DEdgePanData *vpd);
void UI_view2d_edge_pan_operator_properties(struct wmOperatorType *ot);
void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
@ -357,7 +369,8 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
float outside_pad,
float speed_ramp,
float max_speed,
float delay);
float delay,
float zoom_influence);
/* Initialize panning data with operator settings. */
void UI_view2d_edge_pan_operator_init(struct bContext *C,

View File

@ -71,7 +71,8 @@ void UI_view2d_edge_pan_init(bContext *C,
float outside_pad,
float speed_ramp,
float max_speed,
float delay)
float delay,
float zoom_influence)
{
if (!UI_view2d_edge_pan_poll(C)) {
return;
@ -89,6 +90,7 @@ void UI_view2d_edge_pan_init(bContext *C,
vpd->speed_ramp = speed_ramp;
vpd->max_speed = max_speed;
vpd->delay = delay;
vpd->zoom_influence = zoom_influence;
/* Calculate translation factor, based on size of view. */
const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1);
@ -104,6 +106,7 @@ void UI_view2d_edge_pan_reset(View2DEdgePanData *vpd)
vpd->edge_pan_start_time_x = 0.0;
vpd->edge_pan_start_time_y = 0.0;
vpd->edge_pan_last_time = PIL_check_seconds_timer();
vpd->initial_rect = vpd->region->v2d.cur;
}
/**
@ -168,9 +171,15 @@ static float edge_pan_speed(View2DEdgePanData *vpd,
/* Apply a fade in to the speed based on a start time delay. */
const double start_time = x_dir ? vpd->edge_pan_start_time_x : vpd->edge_pan_start_time_y;
const float delay_factor = smootherstep(vpd->delay, (float)(current_time - start_time));
const float delay_factor = vpd->delay > 0.01f ?
smootherstep(vpd->delay, (float)(current_time - start_time)) :
1.0f;
return distance_factor * delay_factor * vpd->max_speed * U.widget_unit * (float)U.dpi_fac;
/* Zoom factor increases speed when zooming in and decreases speed when zooming out. */
const float zoomx = (float)(BLI_rcti_size_x(&region->winrct) + 1) / BLI_rctf_size_x(&region->v2d.cur);
const float zoom_factor = 1.0f + CLAMPIS(vpd->zoom_influence, 0.0f, 1.0f) * (zoomx - 1.0f);
return distance_factor * delay_factor * zoom_factor * vpd->max_speed * U.widget_unit * (float)U.dpi_fac;
}
static void edge_pan_apply_delta(bContext *C, View2DEdgePanData *vpd, float dx, float dy)
@ -264,6 +273,27 @@ void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const w
UI_view2d_edge_pan_apply(C, vpd, event->x, event->y);
}
void UI_view2d_edge_pan_cancel(bContext *C, View2DEdgePanData *vpd)
{
View2D *v2d = vpd->v2d;
if (!v2d) {
return;
}
v2d->cur = vpd->initial_rect;
/* Inform v2d about changes after this operation. */
UI_view2d_curRect_changed(C, v2d);
/* Don't rebuild full tree in outliner, since we're just changing our view. */
ED_region_tag_redraw_no_rebuild(vpd->region);
/* Request updates to be done. */
WM_event_add_mousemove(CTX_wm_window(C));
UI_view2d_sync(vpd->screen, vpd->area, v2d, V2D_LOCK_COPY);
}
void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot)
{
/* Default values for edge panning operators. */
@ -272,7 +302,8 @@ void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot)
/*outside_pad*/ 0.0f,
/*speed_ramp*/ 1.0f,
/*max_speed*/ 500.0f,
/*delay*/ 1.0f);
/*delay*/ 1.0f,
/*zoom_influence*/ 0.0f);
}
void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
@ -280,7 +311,8 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
float outside_pad,
float speed_ramp,
float max_speed,
float delay)
float delay,
float zoom_influence)
{
RNA_def_float(
ot->srna,
@ -329,6 +361,15 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
"Delay in seconds before maximum speed is reached",
0.0f,
10.0f);
RNA_def_float(ot->srna,
"zoom_influence",
zoom_influence,
0.0f,
1.0f,
"Zoom Influence",
"Influence of the zoom factor on scroll speed",
0.0f,
1.0f);
}
void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOperator *op)
@ -339,7 +380,8 @@ void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOpe
RNA_float_get(op->ptr, "outside_padding"),
RNA_float_get(op->ptr, "speed_ramp"),
RNA_float_get(op->ptr, "max_speed"),
RNA_float_get(op->ptr, "delay"));
RNA_float_get(op->ptr, "delay"),
RNA_float_get(op->ptr, "zoom_influence"));
}
/** \} */

View File

@ -17,6 +17,7 @@
#include "BLI_index_range.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_rect.h"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BLI_string_search.h"

View File

@ -880,6 +880,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
bNodeTree *ntree = snode->edittree;
bNodeLinkDrag *nldrag = (bNodeLinkDrag *)op->customdata;
bool do_tag_update = false;
/* View will be reset if no links connect. */
bool reset_view = true;
/* avoid updates while applying links */
ntree->is_updating = true;
@ -917,6 +919,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
if (link->tonode) {
do_tag_update |= (do_tag_update || node_connected_to_output(bmain, ntree, link->tonode));
}
reset_view = false;
}
else {
nodeRemLink(ntree, link);
@ -930,6 +934,10 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
snode_dag_update(C, snode);
}
if (reset_view) {
UI_view2d_edge_pan_cancel(C, &nldrag->pan_data);
}
BLI_remlink(&snode->runtime->linkdrag, nldrag);
/* links->data pointers are either held by the tree or freed already */
BLI_freelistN(&nldrag->links);
@ -1207,6 +1215,8 @@ static void node_link_cancel(bContext *C, wmOperator *op)
BLI_remlink(&snode->runtime->linkdrag, nldrag);
UI_view2d_edge_pan_cancel(C, &nldrag->pan_data);
BLI_freelistN(&nldrag->links);
MEM_freeN(nldrag);
clear_picking_highlight(&snode->edittree->links);
@ -1258,7 +1268,8 @@ void NODE_OT_link(wmOperatorType *ot)
NODE_EDGE_PAN_OUTSIDE_PAD,
NODE_EDGE_PAN_SPEED_RAMP,
NODE_EDGE_PAN_MAX_SPEED,
NODE_EDGE_PAN_DELAY);
NODE_EDGE_PAN_DELAY,
NODE_EDGE_PAN_ZOOM_INFLUENCE);
}
/** \} */

View File

@ -1671,6 +1671,13 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
}
}
if ((prop = RNA_struct_find_property(op->ptr, "view2d_edge_pan")) &&
RNA_property_is_set(op->ptr, prop)) {
if (RNA_property_boolean_get(op->ptr, prop)) {
options |= CTX_VIEW2D_EDGE_PAN;
}
}
t->options = options;
t->mode = mode;

View File

@ -94,6 +94,8 @@ typedef enum {
CTX_OBMODE_XFORM_OBDATA = (1 << 12),
/** Transform object parents without moving their children. */
CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 13),
/** Enable edge scrolling in 2D views */
CTX_VIEW2D_EDGE_PAN = (1 << 14),
} eTContext;
/** #TransInfo.flag */

View File

@ -46,12 +46,6 @@
/** \name Node Transform Creation
* \{ */
typedef struct NodeTransCustomData {
/* Initial rect of the view2d, used for computing offset during edge panning */
rctf initial_v2d_cur;
View2DEdgePanData edge_pan;
} NodeTransCustomData;
/* transcribe given node into TransData2D for Transforming */
static void NodeToTransData(TransData *td, TransData2D *td2d, bNode *node, const float dpi_fac)
{
@ -115,21 +109,16 @@ void createTransNodeData(TransInfo *t)
const float dpi_fac = UI_DPI_FAC;
SpaceNode *snode = t->area->spacedata.first;
if (t->mode == TFM_TRANSLATION) {
/* Disable cursor wrapping in the node editor for edge pan */
t->flag |= T_NO_CURSOR_WRAP;
}
/* Custom data to enable edge panning during the node transform */
NodeTransCustomData *customdata = MEM_callocN(sizeof(*customdata), __func__);
View2DEdgePanData *customdata = MEM_callocN(sizeof(*customdata), __func__);
UI_view2d_edge_pan_init(t->context,
&customdata->edge_pan,
customdata,
NODE_EDGE_PAN_INSIDE_PAD,
NODE_EDGE_PAN_OUTSIDE_PAD,
NODE_EDGE_PAN_SPEED_RAMP,
NODE_EDGE_PAN_MAX_SPEED,
NODE_EDGE_PAN_DELAY);
customdata->initial_v2d_cur = t->region->v2d.cur;
NODE_EDGE_PAN_DELAY,
NODE_EDGE_PAN_ZOOM_INFLUENCE);
t->custom.type.data = customdata;
t->custom.type.use_free = true;
@ -176,17 +165,22 @@ void flushTransNodes(TransInfo *t)
{
const float dpi_fac = UI_DPI_FAC;
NodeTransCustomData *customdata = (NodeTransCustomData *)t->custom.type.data;
View2DEdgePanData *customdata = (View2DEdgePanData *)t->custom.type.data;
if (t->mode == TFM_TRANSLATION) {
/* Edge panning functions expect window coordinates, mval is relative to region */
const float x = t->region->winrct.xmin + t->mval[0];
const float y = t->region->winrct.ymin + t->mval[1];
UI_view2d_edge_pan_apply(t->context, &customdata->edge_pan, x, y);
if (t->options & CTX_VIEW2D_EDGE_PAN) {
if (t->state == TRANS_CANCEL) {
UI_view2d_edge_pan_cancel(t->context, customdata);
}
else {
/* Edge panning functions expect window coordinates, mval is relative to region */
const float x = t->region->winrct.xmin + t->mval[0];
const float y = t->region->winrct.ymin + t->mval[1];
UI_view2d_edge_pan_apply(t->context, customdata, x, y);
}
}
/* Initial and current view2D rects for additional transform due to view panning and zooming */
const rctf *rect_src = &customdata->initial_v2d_cur;
const rctf *rect_src = &customdata->initial_rect;
const rctf *rect_dst = &t->region->v2d.cur;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {

View File

@ -625,6 +625,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
}
#endif
/* Disable cursor wrap when edge panning is enabled. */
if (t->options & CTX_VIEW2D_EDGE_PAN) {
t->flag |= T_NO_CURSOR_WRAP;
}
setTransformViewAspect(t, t->aspect);
if (op && (prop = RNA_struct_find_property(op->ptr, "center_override")) &&

View File

@ -709,6 +709,11 @@ void Transform_Properties(struct wmOperatorType *ot, int flags)
RNA_def_property_ui_text(prop, "Center Override", "Force using this center value (when set)");
}
if (flags & P_VIEW2D_EDGE_PAN) {
prop = RNA_def_boolean(ot->srna, "view2d_edge_pan", false, "Edge Pan", "Enable edge panning in 2D view");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
if ((flags & P_NO_DEFAULTS) == 0) {
prop = RNA_def_boolean(ot->srna,
"release_confirm",
@ -754,7 +759,8 @@ static void TRANSFORM_OT_translate(struct wmOperatorType *ot)
Transform_Properties(ot,
P_ORIENT_MATRIX | P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_ALIGN_SNAP |
P_OPTIONS | P_GPENCIL_EDIT | P_CURSOR_EDIT | P_POST_TRANSFORM);
P_OPTIONS | P_GPENCIL_EDIT | P_CURSOR_EDIT | P_VIEW2D_EDGE_PAN |
P_POST_TRANSFORM);
}
static void TRANSFORM_OT_resize(struct wmOperatorType *ot)