This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/space_view3d/view3d_navigate_zoom.c
Campbell Barton 0b85d6a030 3D View: support canceling viewport operations
Support Esc / RMB to cancel dolly, move, rotate & zoom.
Previously only roll could be canceled.

This can be useful to temporary orbit away from the camera or an
orthographic view without having to manually set it back.
2022-12-07 17:45:24 +11:00

639 lines
20 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup spview3d
*/
#include "BLI_math.h"
#include "BLI_rect.h"
#include "BKE_context.h"
#include "BKE_screen.h"
#include "DEG_depsgraph_query.h"
#include "WM_api.h"
#include "RNA_access.h"
#include "ED_screen.h"
#include "PIL_time.h"
#include "view3d_intern.h"
#include "view3d_navigate.h" /* own include */
/* -------------------------------------------------------------------- */
/** \name View Zoom Operator
* \{ */
/* #viewdolly_modal_keymap has an exact copy of this, apply fixes to both. */
void viewzoom_modal_keymap(wmKeyConfig *keyconf)
{
static const EnumPropertyItem modal_items[] = {
{VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
{VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
{VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
{0, NULL, 0, NULL, NULL},
};
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Zoom Modal");
/* This function is called for each space-type, only needs to add map once. */
if (keymap && keymap->modal_items) {
return;
}
keymap = WM_modalkeymap_ensure(keyconf, "View3D Zoom Modal", modal_items);
/* disabled mode switching for now, can re-implement better, later on */
#if 0
WM_modalkeymap_add_item(keymap,
&(const KeyMapItem_Params){
.type = LEFTMOUSE,
.value = KM_RELEASE,
.modifier = KM_ANY,
.direction = KM_ANY,
},
VIEWROT_MODAL_SWITCH_ROTATE);
WM_modalkeymap_add_item(keymap,
&(const KeyMapItem_Params){
.type = EVT_LEFTCTRLKEY,
.value = KM_RELEASE,
.modifier = KM_ANY,
.direction = KM_ANY,
},
VIEWROT_MODAL_SWITCH_ROTATE);
WM_modalkeymap_add_item(keymap,
&(const KeyMapItem_Params){
.type = EVT_LEFTSHIFTKEY,
.value = KM_PRESS,
.modifier = KM_ANY,
.direction = KM_ANY,
},
VIEWROT_MODAL_SWITCH_MOVE);
#endif
/* assign map to operators */
WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
}
/**
* \param zoom_xy: Optionally zoom to window location
* (coords compatible w/ #wmEvent.xy). Use when not NULL.
*/
static void view_zoom_to_window_xy_camera(Scene *scene,
Depsgraph *depsgraph,
View3D *v3d,
ARegion *region,
float dfac,
const int zoom_xy[2])
{
RegionView3D *rv3d = region->regiondata;
const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
const float zoomfac_new = clamp_f(
zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
if (zoom_xy != NULL) {
float zoomfac_px;
rctf camera_frame_old;
rctf camera_frame_new;
const float pt_src[2] = {zoom_xy[0], zoom_xy[1]};
float pt_dst[2];
float delta_px[2];
ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &camera_frame_old, false);
BLI_rctf_translate(&camera_frame_old, region->winrct.xmin, region->winrct.ymin);
rv3d->camzoom = camzoom_new;
CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &camera_frame_new, false);
BLI_rctf_translate(&camera_frame_new, region->winrct.xmin, region->winrct.ymin);
BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
sub_v2_v2v2(delta_px, pt_dst, pt_src);
/* translate the camera offset using pixel space delta
* mapped back to the camera (same logic as panning in camera view) */
zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
rv3d->camdx += delta_px[0] / (region->winx * zoomfac_px);
rv3d->camdy += delta_px[1] / (region->winy * zoomfac_px);
CLAMP(rv3d->camdx, -1.0f, 1.0f);
CLAMP(rv3d->camdy, -1.0f, 1.0f);
}
else {
rv3d->camzoom = camzoom_new;
CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
}
}
/**
* \param zoom_xy: Optionally zoom to window location
* (coords compatible w/ #wmEvent.xy). Use when not NULL.
*/
static void view_zoom_to_window_xy_3d(ARegion *region, float dfac, const int zoom_xy[2])
{
RegionView3D *rv3d = region->regiondata;
const float dist_new = rv3d->dist * dfac;
if (zoom_xy != NULL) {
float dvec[3];
float tvec[3];
float tpos[3];
float xy_delta[2];
float zfac;
negate_v3_v3(tpos, rv3d->ofs);
xy_delta[0] = (float)(((zoom_xy[0] - region->winrct.xmin) * 2) - region->winx) / 2.0f;
xy_delta[1] = (float)(((zoom_xy[1] - region->winrct.ymin) * 2) - region->winy) / 2.0f;
/* Project cursor position into 3D space */
zfac = ED_view3d_calc_zfac(rv3d, tpos);
ED_view3d_win_to_delta(region, xy_delta, zfac, dvec);
/* Calculate view target position for dolly */
add_v3_v3v3(tvec, tpos, dvec);
negate_v3(tvec);
/* Offset to target position and dolly */
copy_v3_v3(rv3d->ofs, tvec);
rv3d->dist = dist_new;
/* Calculate final offset */
madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
}
else {
rv3d->dist = dist_new;
}
}
static float viewzoom_scale_value(const rcti *winrct,
const eViewZoom_Style viewzoom,
const bool zoom_invert,
const bool zoom_invert_force,
const int xy_curr[2],
const int xy_init[2],
const float val,
const float val_orig,
double *r_timer_lastdraw)
{
float zfac;
if (viewzoom == USER_ZOOM_CONTINUE) {
double time = PIL_check_seconds_timer();
float time_step = (float)(time - *r_timer_lastdraw);
float fac;
if (U.uiflag & USER_ZOOM_HORIZ) {
fac = (float)(xy_init[0] - xy_curr[0]);
}
else {
fac = (float)(xy_init[1] - xy_curr[1]);
}
fac /= U.dpi_fac;
if (zoom_invert != zoom_invert_force) {
fac = -fac;
}
zfac = 1.0f + ((fac / 20.0f) * time_step);
*r_timer_lastdraw = time;
}
else if (viewzoom == USER_ZOOM_SCALE) {
/* method which zooms based on how far you move the mouse */
const int ctr[2] = {
BLI_rcti_cent_x(winrct),
BLI_rcti_cent_y(winrct),
};
float len_new = (5 * U.dpi_fac) + ((float)len_v2v2_int(ctr, xy_curr) / U.dpi_fac);
float len_old = (5 * U.dpi_fac) + ((float)len_v2v2_int(ctr, xy_init) / U.dpi_fac);
/* intentionally ignore 'zoom_invert' for scale */
if (zoom_invert_force) {
SWAP(float, len_new, len_old);
}
zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
}
else { /* USER_ZOOM_DOLLY */
float len_new = 5 * U.dpi_fac;
float len_old = 5 * U.dpi_fac;
if (U.uiflag & USER_ZOOM_HORIZ) {
len_new += (winrct->xmax - (xy_curr[0])) / U.dpi_fac;
len_old += (winrct->xmax - (xy_init[0])) / U.dpi_fac;
}
else {
len_new += (winrct->ymax - (xy_curr[1])) / U.dpi_fac;
len_old += (winrct->ymax - (xy_init[1])) / U.dpi_fac;
}
if (zoom_invert != zoom_invert_force) {
SWAP(float, len_new, len_old);
}
zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
}
return zfac;
}
static float viewzoom_scale_value_offset(const rcti *winrct,
const eViewZoom_Style viewzoom,
const bool zoom_invert,
const bool zoom_invert_force,
const int xy_curr[2],
const int xy_init[2],
const int xy_offset[2],
const float val,
const float val_orig,
double *r_timer_lastdraw)
{
const int xy_curr_offset[2] = {
xy_curr[0] + xy_offset[0],
xy_curr[1] + xy_offset[1],
};
const int xy_init_offset[2] = {
xy_init[0] + xy_offset[0],
xy_init[1] + xy_offset[1],
};
return viewzoom_scale_value(winrct,
viewzoom,
zoom_invert,
zoom_invert_force,
xy_curr_offset,
xy_init_offset,
val,
val_orig,
r_timer_lastdraw);
}
static void viewzoom_apply_camera(ViewOpsData *vod,
const int xy[2],
const eViewZoom_Style viewzoom,
const bool zoom_invert,
const bool zoom_to_pos)
{
float zfac;
float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
zfac = viewzoom_scale_value_offset(&vod->region->winrct,
viewzoom,
zoom_invert,
true,
xy,
vod->init.event_xy,
vod->init.event_xy_offset,
zoomfac,
zoomfac_prev,
&vod->prev.time);
if (!ELEM(zfac, 1.0f, 0.0f)) {
/* calculate inverted, then invert again (needed because of camera zoom scaling) */
zfac = 1.0f / zfac;
view_zoom_to_window_xy_camera(vod->scene,
vod->depsgraph,
vod->v3d,
vod->region,
zfac,
zoom_to_pos ? vod->prev.event_xy : NULL);
}
ED_region_tag_redraw(vod->region);
}
static void viewzoom_apply_3d(ViewOpsData *vod,
const int xy[2],
const eViewZoom_Style viewzoom,
const bool zoom_invert,
const bool zoom_to_pos)
{
float zfac;
float dist_range[2];
ED_view3d_dist_range_get(vod->v3d, dist_range);
zfac = viewzoom_scale_value_offset(&vod->region->winrct,
viewzoom,
zoom_invert,
false,
xy,
vod->init.event_xy,
vod->init.event_xy_offset,
vod->rv3d->dist,
vod->init.dist,
&vod->prev.time);
if (zfac != 1.0f) {
const float zfac_min = dist_range[0] / vod->rv3d->dist;
const float zfac_max = dist_range[1] / vod->rv3d->dist;
CLAMP(zfac, zfac_min, zfac_max);
view_zoom_to_window_xy_3d(vod->region, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
}
/* these limits were in old code too */
CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
view3d_boxview_sync(vod->area, vod->region);
}
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
ED_region_tag_redraw(vod->region);
}
static void viewzoom_apply(ViewOpsData *vod,
const int xy[2],
const eViewZoom_Style viewzoom,
const bool zoom_invert,
const bool zoom_to_pos)
{
if ((vod->rv3d->persp == RV3D_CAMOB) &&
(vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0) {
viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
}
else {
viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
}
}
static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
ViewOpsData *vod = op->customdata;
short event_code = VIEW_PASS;
bool use_autokey = false;
int ret = OPERATOR_RUNNING_MODAL;
/* Execute the events. */
if (event->type == EVT_MODAL_MAP) {
switch (event->val) {
case VIEW_MODAL_CONFIRM:
event_code = VIEW_CONFIRM;
break;
case VIEWROT_MODAL_SWITCH_MOVE:
WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL, event);
event_code = VIEW_CONFIRM;
break;
case VIEWROT_MODAL_SWITCH_ROTATE:
WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL, event);
event_code = VIEW_CONFIRM;
break;
}
}
else {
if (event->type == MOUSEMOVE) {
event_code = VIEW_APPLY;
}
else if (event->type == TIMER) {
if (event->customdata == vod->timer) {
/* Continuous zoom. */
event_code = VIEW_APPLY;
}
}
else if (event->type == vod->init.event_type) {
if (event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
}
}
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
if (event->val == KM_PRESS) {
event_code = VIEW_CANCEL;
}
}
}
switch (event_code) {
case VIEW_APPLY: {
const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
viewzoom_apply(vod,
event->xy,
(eViewZoom_Style)U.viewzoom,
(U.uiflag & USER_ZOOM_INVERT) != 0,
(use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
use_autokey = true;
}
break;
}
case VIEW_CONFIRM: {
use_autokey = true;
ret = OPERATOR_FINISHED;
break;
}
case VIEW_CANCEL: {
/* Note this does not remove auto-keys on locked cameras. */
vod->rv3d->dist = vod->init.dist;
/* The offset may have change when zooming to mouse position. */
copy_v3_v3(vod->rv3d->ofs, vod->init.ofs);
vod->rv3d->camzoom = vod->init.camzoom;
/* Zoom to mouse position in camera view changes these values. */
vod->rv3d->camdx = vod->init.camdx;
vod->rv3d->camdy = vod->init.camdy;
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
ret = OPERATOR_CANCELLED;
break;
}
}
if (use_autokey) {
ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
}
if ((ret & OPERATOR_RUNNING_MODAL) == 0) {
if (ret & OPERATOR_FINISHED) {
ED_view3d_camera_lock_undo_push(op->type->name, vod->v3d, vod->rv3d, C);
}
viewops_data_free(C, op->customdata);
op->customdata = NULL;
}
return ret;
}
static int viewzoom_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
View3D *v3d;
RegionView3D *rv3d;
ScrArea *area;
ARegion *region;
bool use_cam_zoom;
float dist_range[2];
const int delta = RNA_int_get(op->ptr, "delta");
const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
if (op->customdata) {
ViewOpsData *vod = op->customdata;
area = vod->area;
region = vod->region;
}
else {
area = CTX_wm_area(C);
region = CTX_wm_region(C);
}
v3d = area->spacedata.first;
rv3d = region->regiondata;
use_cam_zoom = (rv3d->persp == RV3D_CAMOB) &&
!(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
int zoom_xy_buf[2];
const int *zoom_xy = NULL;
if (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") :
region->winx / 2;
zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") :
region->winy / 2;
zoom_xy = zoom_xy_buf;
}
ED_view3d_dist_range_get(v3d, dist_range);
if (delta < 0) {
const float step = 1.2f;
if (use_cam_zoom) {
view_zoom_to_window_xy_camera(scene, depsgraph, v3d, region, step, zoom_xy);
}
else {
if (rv3d->dist < dist_range[1]) {
view_zoom_to_window_xy_3d(region, step, zoom_xy);
}
}
}
else {
const float step = 1.0f / 1.2f;
if (use_cam_zoom) {
view_zoom_to_window_xy_camera(scene, depsgraph, v3d, region, step, zoom_xy);
}
else {
if (rv3d->dist > dist_range[0]) {
view_zoom_to_window_xy_3d(region, step, zoom_xy);
}
}
}
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
view3d_boxview_sync(area, region);
}
ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
ED_region_tag_redraw(region);
ED_view3d_camera_lock_undo_grouped_push(op->type->name, v3d, rv3d, C);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_FINISHED;
}
/* viewdolly_invoke() copied this function, changes here may apply there */
static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ViewOpsData *vod;
const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
vod = op->customdata = viewops_data_create(
C,
event,
(viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
(use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
/* if one or the other zoom position aren't set, set from event */
if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
RNA_int_set(op->ptr, "mx", event->xy[0]);
RNA_int_set(op->ptr, "my", event->xy[1]);
}
if (RNA_struct_property_is_set(op->ptr, "delta")) {
viewzoom_exec(C, op);
}
else {
if (ELEM(event->type, MOUSEZOOM, MOUSEPAN)) {
if (U.uiflag & USER_ZOOM_HORIZ) {
vod->init.event_xy[0] = vod->prev.event_xy[0] = event->xy[0];
}
else {
/* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->xy[0] -
event->prev_xy[0];
}
viewzoom_apply(vod,
event->prev_xy,
USER_ZOOM_DOLLY,
(U.uiflag & USER_ZOOM_INVERT) != 0,
(use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_FINISHED;
}
if (U.viewzoom == USER_ZOOM_CONTINUE) {
/* needs a timer to continue redrawing */
vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
vod->prev.time = PIL_check_seconds_timer();
}
/* add temp handler */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
return OPERATOR_FINISHED;
}
static void viewzoom_cancel(bContext *C, wmOperator *op)
{
viewops_data_free(C, op->customdata);
op->customdata = NULL;
}
void VIEW3D_OT_zoom(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Zoom View";
ot->description = "Zoom in/out in the view";
ot->idname = "VIEW3D_OT_zoom";
/* api callbacks */
ot->invoke = viewzoom_invoke;
ot->exec = viewzoom_exec;
ot->modal = viewzoom_modal;
ot->poll = view3d_zoom_or_dolly_poll;
ot->cancel = viewzoom_cancel;
/* flags */
ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
/* properties */
view3d_operator_properties_common(
ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
}
/** \} */