From be699936af7b86ae542f7cc73caf56ab9de350d3 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 3 Apr 2022 00:00:42 -0500 Subject: Cleanup: Move interface View2D files to C++ Similar to 4537eb0c3b1678a6235 --- source/blender/editors/interface/CMakeLists.txt | 10 +- source/blender/editors/interface/view2d.c | 2127 ------------------ source/blender/editors/interface/view2d.cc | 2130 ++++++++++++++++++ source/blender/editors/interface/view2d_draw.c | 600 ----- source/blender/editors/interface/view2d_draw.cc | 599 +++++ source/blender/editors/interface/view2d_edge_pan.c | 382 ---- .../blender/editors/interface/view2d_edge_pan.cc | 382 ++++ .../editors/interface/view2d_gizmo_navigate.c | 253 --- .../editors/interface/view2d_gizmo_navigate.cc | 253 +++ source/blender/editors/interface/view2d_ops.c | 2282 -------------------- source/blender/editors/interface/view2d_ops.cc | 2274 +++++++++++++++++++ .../blender/windowmanager/gizmo/WM_gizmo_types.h | 2 + 12 files changed, 5645 insertions(+), 5649 deletions(-) delete mode 100644 source/blender/editors/interface/view2d.c create mode 100644 source/blender/editors/interface/view2d.cc delete mode 100644 source/blender/editors/interface/view2d_draw.c create mode 100644 source/blender/editors/interface/view2d_draw.cc delete mode 100644 source/blender/editors/interface/view2d_edge_pan.c create mode 100644 source/blender/editors/interface/view2d_edge_pan.cc delete mode 100644 source/blender/editors/interface/view2d_gizmo_navigate.c create mode 100644 source/blender/editors/interface/view2d_gizmo_navigate.cc delete mode 100644 source/blender/editors/interface/view2d_ops.c create mode 100644 source/blender/editors/interface/view2d_ops.cc diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index a1ee5c38838..0bc52fd3a38 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -69,11 +69,11 @@ set(SRC interface_widgets.c resources.c tree_view.cc - view2d.c - view2d_draw.c - view2d_edge_pan.c - view2d_gizmo_navigate.c - view2d_ops.c + view2d.cc + view2d_draw.cc + view2d_edge_pan.cc + view2d_gizmo_navigate.cc + view2d_ops.cc interface_eyedropper_intern.h interface_intern.h diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c deleted file mode 100644 index bf3c8362ce6..00000000000 --- a/source/blender/editors/interface/view2d.c +++ /dev/null @@ -1,2127 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_scene_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_array.h" -#include "BLI_easing.h" -#include "BLI_link_utils.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_memarena.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_timecode.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_screen.h" - -#include "GPU_immediate.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "WM_api.h" - -#include "BLF_api.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "interface_intern.h" - -static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize); - -/* -------------------------------------------------------------------- */ -/** \name Internal Utilities - * \{ */ - -BLI_INLINE int clamp_float_to_int(const float f) -{ - const float min = (float)INT_MIN; - const float max = (float)INT_MAX; - - if (UNLIKELY(f < min)) { - return min; - } - if (UNLIKELY(f > max)) { - return (int)max; - } - return (int)f; -} - -/** - * use instead of #BLI_rcti_rctf_copy so we have consistent behavior - * with users of #clamp_float_to_int. - */ -BLI_INLINE void clamp_rctf_to_rcti(rcti *dst, const rctf *src) -{ - dst->xmin = clamp_float_to_int(src->xmin); - dst->xmax = clamp_float_to_int(src->xmax); - dst->ymin = clamp_float_to_int(src->ymin); - dst->ymax = clamp_float_to_int(src->ymax); -} - -/* XXX still unresolved: scrolls hide/unhide vs region mask handling */ -/* XXX there's V2D_SCROLL_HORIZONTAL_HIDE and V2D_SCROLL_HORIZONTAL_FULLR ... */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Internal Scroll & Mask Utilities - * \{ */ - -/** - * helper to allow scrollbars to dynamically hide - * - returns a copy of the scrollbar settings with the flags to display - * horizontal/vertical scrollbars removed - * - input scroll value is the v2d->scroll var - * - hide flags are set per region at drawtime - */ -static int view2d_scroll_mapped(int scroll) -{ - if (scroll & V2D_SCROLL_HORIZONTAL_FULLR) { - scroll &= ~V2D_SCROLL_HORIZONTAL; - } - if (scroll & V2D_SCROLL_VERTICAL_FULLR) { - scroll &= ~V2D_SCROLL_VERTICAL; - } - return scroll; -} - -void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask) -{ - r_mask->xmin = 0; - r_mask->ymin = 0; - r_mask->xmax = v2d->winx - 1; /* -1 yes! masks are pixels */ - r_mask->ymax = v2d->winy - 1; -} - -/** - * Called each time #View2D.cur changes, to dynamically update masks. - * - * \param mask_scroll: Optionally clamp scrollbars by this region. - */ -static void view2d_masks(View2D *v2d, const rcti *mask_scroll) -{ - int scroll; - - /* mask - view frame */ - UI_view2d_mask_from_win(v2d, &v2d->mask); - if (mask_scroll == NULL) { - mask_scroll = &v2d->mask; - } - - /* check size if hiding flag is set: */ - if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) { - if (!(v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES)) { - if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) { - v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR; - } - else { - v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR; - } - } - } - if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) { - if (!(v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES)) { - if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) { - v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR; - } - else { - v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR; - } - } - } - - scroll = view2d_scroll_mapped(v2d->scroll); - - /* Scrollers are based off region-size: - * - they can only be on one to two edges of the region they define - * - if they overlap, they must not occupy the corners (which are reserved for other widgets) - */ - if (scroll) { - float scroll_width, scroll_height; - - UI_view2d_scroller_size_get(v2d, &scroll_width, &scroll_height); - - /* vertical scroller */ - if (scroll & V2D_SCROLL_LEFT) { - /* on left-hand edge of region */ - v2d->vert = *mask_scroll; - v2d->vert.xmax = scroll_width; - } - else if (scroll & V2D_SCROLL_RIGHT) { - /* on right-hand edge of region */ - v2d->vert = *mask_scroll; - v2d->vert.xmax++; /* one pixel extra... was leaving a minor gap... */ - v2d->vert.xmin = v2d->vert.xmax - scroll_width; - } - - /* Currently, all regions that have vertical scale handles, - * also have the scrubbing area at the top. - * So the scrollbar has to move down a bit. */ - if (scroll & V2D_SCROLL_VERTICAL_HANDLES) { - v2d->vert.ymax -= UI_TIME_SCRUB_MARGIN_Y; - } - - /* horizontal scroller */ - if (scroll & V2D_SCROLL_BOTTOM) { - /* on bottom edge of region */ - v2d->hor = *mask_scroll; - v2d->hor.ymax = scroll_height; - } - else if (scroll & V2D_SCROLL_TOP) { - /* on upper edge of region */ - v2d->hor = *mask_scroll; - v2d->hor.ymin = v2d->hor.ymax - scroll_height; - } - - /* adjust vertical scroller if there's a horizontal scroller, to leave corner free */ - if (scroll & V2D_SCROLL_VERTICAL) { - if (scroll & V2D_SCROLL_BOTTOM) { - /* on bottom edge of region */ - v2d->vert.ymin = v2d->hor.ymax; - } - else if (scroll & V2D_SCROLL_TOP) { - /* on upper edge of region */ - v2d->vert.ymax = v2d->hor.ymin; - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View2D Refresh and Validation (Spatial) - * \{ */ - -void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy) -{ - bool tot_changed = false, do_init; - const uiStyle *style = UI_style_get(); - - do_init = (v2d->flag & V2D_IS_INIT) == 0; - - /* see eView2D_CommonViewTypes in UI_view2d.h for available view presets */ - switch (type) { - /* 'standard view' - optimum setup for 'standard' view behavior, - * that should be used new views as basis for their - * own unique View2D settings, which should be used instead of this in most cases... - */ - case V2D_COMMONVIEW_STANDARD: { - /* for now, aspect ratio should be maintained, - * and zoom is clamped within sane default limits */ - v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM); - v2d->minzoom = 0.01f; - v2d->maxzoom = 1000.0f; - - /* View2D tot rect and cur should be same size, - * and aligned using 'standard' OpenGL coordinates for now: - * - region can resize 'tot' later to fit other data - * - keeptot is only within bounds, as strict locking is not that critical - * - view is aligned for (0,0) -> (winx-1, winy-1) setup - */ - v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); - v2d->keeptot = V2D_KEEPTOT_BOUNDS; - if (do_init) { - v2d->tot.xmin = v2d->tot.ymin = 0.0f; - v2d->tot.xmax = (float)(winx - 1); - v2d->tot.ymax = (float)(winy - 1); - - v2d->cur = v2d->tot; - } - /* scrollers - should we have these by default? */ - /* XXX for now, we don't override this, or set it either! */ - break; - } - /* 'list/channel view' - zoom, aspect ratio, and alignment restrictions are set here */ - case V2D_COMMONVIEW_LIST: { - /* zoom + aspect ratio are locked */ - v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); - v2d->minzoom = v2d->maxzoom = 1.0f; - - /* tot rect has strictly regulated placement, and must only occur in +/- quadrant */ - v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y); - v2d->keeptot = V2D_KEEPTOT_STRICT; - tot_changed = do_init; - - /* scroller settings are currently not set here... that is left for regions... */ - break; - } - /* 'stack view' - practically the same as list/channel view, - * except is located in the pos y half instead. - * Zoom, aspect ratio, and alignment restrictions are set here. */ - case V2D_COMMONVIEW_STACK: { - /* zoom + aspect ratio are locked */ - v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); - v2d->minzoom = v2d->maxzoom = 1.0f; - - /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */ - v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); - v2d->keeptot = V2D_KEEPTOT_STRICT; - tot_changed = do_init; - - /* scroller settings are currently not set here... that is left for regions... */ - break; - } - /* 'header' regions - zoom, aspect ratio, - * alignment, and panning restrictions are set here */ - case V2D_COMMONVIEW_HEADER: { - /* zoom + aspect ratio are locked */ - v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); - v2d->minzoom = v2d->maxzoom = 1.0f; - - if (do_init) { - v2d->tot.xmin = 0.0f; - v2d->tot.xmax = winx; - v2d->tot.ymin = 0.0f; - v2d->tot.ymax = winy; - v2d->cur = v2d->tot; - - v2d->min[0] = v2d->max[0] = (float)(winx - 1); - v2d->min[1] = v2d->max[1] = (float)(winy - 1); - } - /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */ - v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); - v2d->keeptot = V2D_KEEPTOT_STRICT; - tot_changed = do_init; - - /* panning in y-axis is prohibited */ - v2d->keepofs = V2D_LOCKOFS_Y; - - /* absolutely no scrollers allowed */ - v2d->scroll = 0; - break; - } - /* panels view, with horizontal/vertical align */ - case V2D_COMMONVIEW_PANELS_UI: { - - /* for now, aspect ratio should be maintained, - * and zoom is clamped within sane default limits */ - v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM | V2D_KEEPZOOM); - v2d->minzoom = 0.5f; - v2d->maxzoom = 2.0f; - - v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y); - v2d->keeptot = V2D_KEEPTOT_BOUNDS; - - /* NOTE: scroll is being flipped in #ED_region_panels() drawing. */ - v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE); - - if (do_init) { - const float panelzoom = (style) ? style->panelzoom : 1.0f; - - v2d->tot.xmin = 0.0f; - v2d->tot.xmax = winx; - - v2d->tot.ymax = 0.0f; - v2d->tot.ymin = -winy; - - v2d->cur.xmin = 0.0f; - v2d->cur.xmax = (winx)*panelzoom; - - v2d->cur.ymax = 0.0f; - v2d->cur.ymin = (-winy) * panelzoom; - } - break; - } - /* other view types are completely defined using their own settings already */ - default: - /* we don't do anything here, - * as settings should be fine, but just make sure that rect */ - break; - } - - /* set initialized flag so that View2D doesn't get reinitialized next time again */ - v2d->flag |= V2D_IS_INIT; - - /* store view size */ - v2d->winx = winx; - v2d->winy = winy; - - view2d_masks(v2d, NULL); - - if (do_init) { - /* Visible by default. */ - v2d->alpha_hor = v2d->alpha_vert = 255; - } - - /* set 'tot' rect before setting cur? */ - /* XXX confusing stuff here still */ - if (tot_changed) { - UI_view2d_totRect_set_resize(v2d, winx, winy, !do_init); - } - else { - ui_view2d_curRect_validate_resize(v2d, !do_init); - } -} - -/** - * Ensure View2D rects remain in a viable configuration - * 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot' - */ -/* XXX pre2.5 -> this used to be called test_view2d() */ -static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize) -{ - float totwidth, totheight, curwidth, curheight, width, height; - float winx, winy; - rctf *cur, *tot; - - /* use mask as size of region that View2D resides in, as it takes into account - * scrollbars already - keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */ - winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1); - winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1); - - /* get pointers to rcts for less typing */ - cur = &v2d->cur; - tot = &v2d->tot; - - /* we must satisfy the following constraints (in decreasing order of importance): - * - alignment restrictions are respected - * - cur must not fall outside of tot - * - axis locks (zoom and offset) must be maintained - * - zoom must not be excessive (check either sizes or zoom values) - * - aspect ratio should be respected (NOTE: this is quite closely related to zoom too) - */ - - /* Step 1: if keepzoom, adjust the sizes of the rects only - * - firstly, we calculate the sizes of the rects - * - curwidth and curheight are saved as reference... modify width and height values here - */ - totwidth = BLI_rctf_size_x(tot); - totheight = BLI_rctf_size_y(tot); - /* keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */ - curwidth = width = BLI_rctf_size_x(cur); - curheight = height = BLI_rctf_size_y(cur); - - /* if zoom is locked, size on the appropriate axis is reset to mask size */ - if (v2d->keepzoom & V2D_LOCKZOOM_X) { - width = winx; - } - if (v2d->keepzoom & V2D_LOCKZOOM_Y) { - height = winy; - } - - /* values used to divide, so make it safe - * NOTE: width and height must use FLT_MIN instead of 1, otherwise it is impossible to - * get enough resolution in Graph Editor for editing some curves - */ - if (width < FLT_MIN) { - width = 1; - } - if (height < FLT_MIN) { - height = 1; - } - if (winx < 1) { - winx = 1; - } - if (winy < 1) { - winy = 1; - } - - /* V2D_LIMITZOOM indicates that zoom level should be preserved when the window size changes */ - if (resize && (v2d->keepzoom & V2D_KEEPZOOM)) { - float zoom, oldzoom; - - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - zoom = winx / width; - oldzoom = v2d->oldwinx / curwidth; - - if (oldzoom != zoom) { - width *= zoom / oldzoom; - } - } - - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - zoom = winy / height; - oldzoom = v2d->oldwiny / curheight; - - if (oldzoom != zoom) { - height *= zoom / oldzoom; - } - } - } - /* keepzoom (V2D_LIMITZOOM set), indicates that zoom level on each axis must not exceed limits - * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this - */ - else if (v2d->keepzoom & V2D_LIMITZOOM) { - - /* check if excessive zoom on x-axis */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - const float zoom = winx / width; - if (zoom < v2d->minzoom) { - width = winx / v2d->minzoom; - } - else if (zoom > v2d->maxzoom) { - width = winx / v2d->maxzoom; - } - } - - /* check if excessive zoom on y-axis */ - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - const float zoom = winy / height; - if (zoom < v2d->minzoom) { - height = winy / v2d->minzoom; - } - else if (zoom > v2d->maxzoom) { - height = winy / v2d->maxzoom; - } - } - } - else { - /* make sure sizes don't exceed that of the min/max sizes - * (even though we're not doing zoom clamping) */ - CLAMP(width, v2d->min[0], v2d->max[0]); - CLAMP(height, v2d->min[1], v2d->max[1]); - } - - /* check if we should restore aspect ratio (if view size changed) */ - if (v2d->keepzoom & V2D_KEEPASPECT) { - bool do_x = false, do_y = false, do_cur /* , do_win */ /* UNUSED */; - float curRatio, winRatio; - - /* when a window edge changes, the aspect ratio can't be used to - * find which is the best new 'cur' rect. that's why it stores 'old' - */ - if (winx != v2d->oldwinx) { - do_x = true; - } - if (winy != v2d->oldwiny) { - do_y = true; - } - - curRatio = height / width; - winRatio = winy / winx; - - /* Both sizes change (area/region maximized). */ - if (do_x == do_y) { - if (do_x && do_y) { - /* here is 1,1 case, so all others must be 0,0 */ - if (fabsf(winx - v2d->oldwinx) > fabsf(winy - v2d->oldwiny)) { - do_y = false; - } - else { - do_x = false; - } - } - else if (winRatio > curRatio) { - do_x = false; - } - else { - do_x = true; - } - } - do_cur = do_x; - /* do_win = do_y; */ /* UNUSED */ - - if (do_cur) { - if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winx != v2d->oldwinx)) { - /* Special exception for Outliner (and later channel-lists): - * - The view may be moved left to avoid contents - * being pushed out of view when view shrinks. - * - The keeptot code will make sure cur->xmin will not be less than tot->xmin - * (which cannot be allowed). - * - width is not adjusted for changed ratios here. - */ - if (winx < v2d->oldwinx) { - const float temp = v2d->oldwinx - winx; - - cur->xmin -= temp; - cur->xmax -= temp; - - /* width does not get modified, as keepaspect here is just set to make - * sure visible area adjusts to changing view shape! - */ - } - } - else { - /* portrait window: correct for x */ - width = height / winRatio; - } - } - else { - if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winy != v2d->oldwiny)) { - /* special exception for Outliner (and later channel-lists): - * - Currently, no actions need to be taken here... - */ - - if (winy < v2d->oldwiny) { - const float temp = v2d->oldwiny - winy; - - if (v2d->align & V2D_ALIGN_NO_NEG_Y) { - cur->ymin -= temp; - cur->ymax -= temp; - } - else { /* Assume V2D_ALIGN_NO_POS_Y or combination */ - cur->ymin += temp; - cur->ymax += temp; - } - } - } - else { - /* landscape window: correct for y */ - height = width * winRatio; - } - } - - /* store region size for next time */ - v2d->oldwinx = (short)winx; - v2d->oldwiny = (short)winy; - } - - /* Step 2: apply new sizes to cur rect, - * but need to take into account alignment settings here... */ - if ((width != curwidth) || (height != curheight)) { - float temp, dh; - - /* Resize from center-point, unless otherwise specified. */ - if (width != curwidth) { - if (v2d->keepofs & V2D_LOCKOFS_X) { - cur->xmax += width - BLI_rctf_size_x(cur); - } - else if (v2d->keepofs & V2D_KEEPOFS_X) { - if (v2d->align & V2D_ALIGN_NO_POS_X) { - cur->xmin -= width - BLI_rctf_size_x(cur); - } - else { - cur->xmax += width - BLI_rctf_size_x(cur); - } - } - else { - temp = BLI_rctf_cent_x(cur); - dh = width * 0.5f; - - cur->xmin = temp - dh; - cur->xmax = temp + dh; - } - } - if (height != curheight) { - if (v2d->keepofs & V2D_LOCKOFS_Y) { - cur->ymax += height - BLI_rctf_size_y(cur); - } - else if (v2d->keepofs & V2D_KEEPOFS_Y) { - if (v2d->align & V2D_ALIGN_NO_POS_Y) { - cur->ymin -= height - BLI_rctf_size_y(cur); - } - else { - cur->ymax += height - BLI_rctf_size_y(cur); - } - } - else { - temp = BLI_rctf_cent_y(cur); - dh = height * 0.5f; - - cur->ymin = temp - dh; - cur->ymax = temp + dh; - } - } - } - - /* Step 3: adjust so that it doesn't fall outside of bounds of 'tot' */ - if (v2d->keeptot) { - float temp, diff; - - /* recalculate extents of cur */ - curwidth = BLI_rctf_size_x(cur); - curheight = BLI_rctf_size_y(cur); - - /* width */ - if ((curwidth > totwidth) && - !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_X | V2D_LIMITZOOM))) { - /* if zoom doesn't have to be maintained, just clamp edges */ - if (cur->xmin < tot->xmin) { - cur->xmin = tot->xmin; - } - if (cur->xmax > tot->xmax) { - cur->xmax = tot->xmax; - } - } - else if (v2d->keeptot == V2D_KEEPTOT_STRICT) { - /* This is an exception for the outliner (and later channel-lists, headers) - * - must clamp within tot rect (absolutely no excuses) - * --> therefore, cur->xmin must not be less than tot->xmin - */ - if (cur->xmin < tot->xmin) { - /* move cur across so that it sits at minimum of tot */ - temp = tot->xmin - cur->xmin; - - cur->xmin += temp; - cur->xmax += temp; - } - else if (cur->xmax > tot->xmax) { - /* - only offset by difference of cur-xmax and tot-xmax if that would not move - * cur-xmin to lie past tot-xmin - * - otherwise, simply shift to tot-xmin??? - */ - temp = cur->xmax - tot->xmax; - - if ((cur->xmin - temp) < tot->xmin) { - /* only offset by difference from cur-min and tot-min */ - temp = cur->xmin - tot->xmin; - - cur->xmin -= temp; - cur->xmax -= temp; - } - else { - cur->xmin -= temp; - cur->xmax -= temp; - } - } - } - else { - /* This here occurs when: - * - width too big, but maintaining zoom (i.e. widths cannot be changed) - * - width is OK, but need to check if outside of boundaries - * - * So, resolution is to just shift view by the gap between the extremities. - * We favor moving the 'minimum' across, as that's origin for most things. - * (XXX: in the past, max was favored... if there are bugs, swap!) - */ - if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) { - /* outside boundaries on both sides, - * so take middle-point of tot, and place in balanced way */ - temp = BLI_rctf_cent_x(tot); - diff = curwidth * 0.5f; - - cur->xmin = temp - diff; - cur->xmax = temp + diff; - } - else if (cur->xmin < tot->xmin) { - /* move cur across so that it sits at minimum of tot */ - temp = tot->xmin - cur->xmin; - - cur->xmin += temp; - cur->xmax += temp; - } - else if (cur->xmax > tot->xmax) { - /* - only offset by difference of cur-xmax and tot-xmax if that would not move - * cur-xmin to lie past tot-xmin - * - otherwise, simply shift to tot-xmin??? - */ - temp = cur->xmax - tot->xmax; - - if ((cur->xmin - temp) < tot->xmin) { - /* only offset by difference from cur-min and tot-min */ - temp = cur->xmin - tot->xmin; - - cur->xmin -= temp; - cur->xmax -= temp; - } - else { - cur->xmin -= temp; - cur->xmax -= temp; - } - } - } - - /* height */ - if ((curheight > totheight) && - !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_Y | V2D_LIMITZOOM))) { - /* if zoom doesn't have to be maintained, just clamp edges */ - if (cur->ymin < tot->ymin) { - cur->ymin = tot->ymin; - } - if (cur->ymax > tot->ymax) { - cur->ymax = tot->ymax; - } - } - else { - /* This here occurs when: - * - height too big, but maintaining zoom (i.e. heights cannot be changed) - * - height is OK, but need to check if outside of boundaries - * - * So, resolution is to just shift view by the gap between the extremities. - * We favor moving the 'minimum' across, as that's origin for most things. - */ - if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) { - /* outside boundaries on both sides, - * so take middle-point of tot, and place in balanced way */ - temp = BLI_rctf_cent_y(tot); - diff = curheight * 0.5f; - - cur->ymin = temp - diff; - cur->ymax = temp + diff; - } - else if (cur->ymin < tot->ymin) { - /* there's still space remaining, so shift up */ - temp = tot->ymin - cur->ymin; - - cur->ymin += temp; - cur->ymax += temp; - } - else if (cur->ymax > tot->ymax) { - /* there's still space remaining, so shift down */ - temp = cur->ymax - tot->ymax; - - cur->ymin -= temp; - cur->ymax -= temp; - } - } - } - - /* Step 4: Make sure alignment restrictions are respected */ - if (v2d->align) { - /* If alignment flags are set (but keeptot is not), they must still be respected, as although - * they don't specify any particular bounds to stay within, they do define ranges which are - * invalid. - * - * Here, we only check to make sure that on each axis, the 'cur' rect doesn't stray into these - * invalid zones, otherwise we offset. - */ - - /* handle width - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { - /* width is in negative-x half */ - if (v2d->cur.xmax > 0) { - v2d->cur.xmin -= v2d->cur.xmax; - v2d->cur.xmax = 0.0f; - } - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { - /* width is in positive-x half */ - if (v2d->cur.xmin < 0) { - v2d->cur.xmax -= v2d->cur.xmin; - v2d->cur.xmin = 0.0f; - } - } - - /* handle height - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { - /* height is in negative-y half */ - if (v2d->cur.ymax > 0) { - v2d->cur.ymin -= v2d->cur.ymax; - v2d->cur.ymax = 0.0f; - } - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { - /* height is in positive-y half */ - if (v2d->cur.ymin < 0) { - v2d->cur.ymax -= v2d->cur.ymin; - v2d->cur.ymin = 0.0f; - } - } - } - - /* set masks */ - view2d_masks(v2d, NULL); -} - -void UI_view2d_curRect_validate(View2D *v2d) -{ - ui_view2d_curRect_validate_resize(v2d, false); -} - -void UI_view2d_curRect_changed(const bContext *C, View2D *v2d) -{ - UI_view2d_curRect_validate(v2d); - - ARegion *region = CTX_wm_region(C); - - if (region->type->on_view2d_changed != NULL) { - region->type->on_view2d_changed(C, region); - } -} - -/* ------------------ */ - -bool UI_view2d_area_supports_sync(ScrArea *area) -{ - return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH); -} - -void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) -{ - /* don't continue if no view syncing to be done */ - if ((v2dcur->flag & (V2D_VIEWSYNC_SCREEN_TIME | V2D_VIEWSYNC_AREA_VERTICAL)) == 0) { - return; - } - - /* check if doing within area syncing (i.e. channels/vertical) */ - if ((v2dcur->flag & V2D_VIEWSYNC_AREA_VERTICAL) && (area)) { - LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - /* don't operate on self */ - if (v2dcur != ®ion->v2d) { - /* only if view has vertical locks enabled */ - if (region->v2d.flag & V2D_VIEWSYNC_AREA_VERTICAL) { - if (flag == V2D_LOCK_COPY) { - /* other views with locks on must copy active */ - region->v2d.cur.ymin = v2dcur->cur.ymin; - region->v2d.cur.ymax = v2dcur->cur.ymax; - } - else { /* V2D_LOCK_SET */ - /* active must copy others */ - v2dcur->cur.ymin = region->v2d.cur.ymin; - v2dcur->cur.ymax = region->v2d.cur.ymax; - } - - /* region possibly changed, so refresh */ - ED_region_tag_redraw_no_rebuild(region); - } - } - } - } - - /* check if doing whole screen syncing (i.e. time/horizontal) */ - if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { - LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { - if (!UI_view2d_area_supports_sync(area_iter)) { - continue; - } - LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) { - /* don't operate on self */ - if (v2dcur != ®ion->v2d) { - /* only if view has horizontal locks enabled */ - if (region->v2d.flag & V2D_VIEWSYNC_SCREEN_TIME) { - if (flag == V2D_LOCK_COPY) { - /* other views with locks on must copy active */ - region->v2d.cur.xmin = v2dcur->cur.xmin; - region->v2d.cur.xmax = v2dcur->cur.xmax; - } - else { /* V2D_LOCK_SET */ - /* active must copy others */ - v2dcur->cur.xmin = region->v2d.cur.xmin; - v2dcur->cur.xmax = region->v2d.cur.xmax; - } - - /* region possibly changed, so refresh */ - ED_region_tag_redraw_no_rebuild(region); - } - } - } - } - } -} - -void UI_view2d_curRect_reset(View2D *v2d) -{ - float width, height; - - /* assume width and height of 'cur' rect by default, should be same size as mask */ - width = (float)(BLI_rcti_size_x(&v2d->mask) + 1); - height = (float)(BLI_rcti_size_y(&v2d->mask) + 1); - - /* handle width - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { - /* width is in negative-x half */ - v2d->cur.xmin = -width; - v2d->cur.xmax = 0.0f; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { - /* width is in positive-x half */ - v2d->cur.xmin = 0.0f; - v2d->cur.xmax = width; - } - else { - /* width is centered around (x == 0) */ - const float dx = width / 2.0f; - - v2d->cur.xmin = -dx; - v2d->cur.xmax = dx; - } - - /* handle height - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { - /* height is in negative-y half */ - v2d->cur.ymin = -height; - v2d->cur.ymax = 0.0f; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { - /* height is in positive-y half */ - v2d->cur.ymin = 0.0f; - v2d->cur.ymax = height; - } - else { - /* height is centered around (y == 0) */ - const float dy = height / 2.0f; - - v2d->cur.ymin = -dy; - v2d->cur.ymax = dy; - } -} - -/* ------------------ */ - -void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resize) -{ - /* don't do anything if either value is 0 */ - width = abs(width); - height = abs(height); - - if (ELEM(0, width, height)) { - if (G.debug & G_DEBUG) { - /* XXX: temp debug info. */ - printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d\n", - (void *)v2d, - width, - height); - } - return; - } - - /* handle width - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { - /* width is in negative-x half */ - v2d->tot.xmin = (float)-width; - v2d->tot.xmax = 0.0f; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { - /* width is in positive-x half */ - v2d->tot.xmin = 0.0f; - v2d->tot.xmax = (float)width; - } - else { - /* width is centered around (x == 0) */ - const float dx = (float)width / 2.0f; - - v2d->tot.xmin = -dx; - v2d->tot.xmax = dx; - } - - /* handle height - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { - /* height is in negative-y half */ - v2d->tot.ymin = (float)-height; - v2d->tot.ymax = 0.0f; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { - /* height is in positive-y half */ - v2d->tot.ymin = 0.0f; - v2d->tot.ymax = (float)height; - } - else { - /* height is centered around (y == 0) */ - const float dy = (float)height / 2.0f; - - v2d->tot.ymin = -dy; - v2d->tot.ymax = dy; - } - - /* make sure that 'cur' rect is in a valid state as a result of these changes */ - ui_view2d_curRect_validate_resize(v2d, resize); -} - -void UI_view2d_totRect_set(View2D *v2d, int width, int height) -{ - UI_view2d_totRect_set_resize(v2d, width, height, false); -} - -void UI_view2d_zoom_cache_reset(void) -{ - /* TODO(sergey): This way we avoid threading conflict with sequencer rendering - * text strip. But ideally we want to make glyph cache to be fully safe - * for threading. - */ - if (G.is_rendering) { - return; - } - /* While scaling we can accumulate fonts at many sizes (~20 or so). - * Not an issue with embedded font, but can use over 500Mb with i18n ones! See T38244. */ - - /* NOTE: only some views draw text, we could check for this case to avoid cleaning cache. */ - BLF_cache_clear(); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View2D Matrix Setup - * \{ */ - -/* mapping function to ensure 'cur' draws extended over the area where sliders are */ -static void view2d_map_cur_using_mask(const View2D *v2d, rctf *r_curmasked) -{ - *r_curmasked = v2d->cur; - - if (view2d_scroll_mapped(v2d->scroll)) { - const float sizex = BLI_rcti_size_x(&v2d->mask); - const float sizey = BLI_rcti_size_y(&v2d->mask); - - /* prevent tiny or narrow regions to get - * invalid coordinates - mask can get negative even... */ - if (sizex > 0.0f && sizey > 0.0f) { - const float dx = BLI_rctf_size_x(&v2d->cur) / (sizex + 1); - const float dy = BLI_rctf_size_y(&v2d->cur) / (sizey + 1); - - if (v2d->mask.xmin != 0) { - r_curmasked->xmin -= dx * (float)v2d->mask.xmin; - } - if (v2d->mask.xmax + 1 != v2d->winx) { - r_curmasked->xmax += dx * (float)(v2d->winx - v2d->mask.xmax - 1); - } - - if (v2d->mask.ymin != 0) { - r_curmasked->ymin -= dy * (float)v2d->mask.ymin; - } - if (v2d->mask.ymax + 1 != v2d->winy) { - r_curmasked->ymax += dy * (float)(v2d->winy - v2d->mask.ymax - 1); - } - } - } -} - -void UI_view2d_view_ortho(const View2D *v2d) -{ - rctf curmasked; - const int sizex = BLI_rcti_size_x(&v2d->mask); - const int sizey = BLI_rcti_size_y(&v2d->mask); - const float eps = 0.001f; - float xofs = 0.0f, yofs = 0.0f; - - /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1 - * correspondence with pixels for smooth UI drawing, - * but only applied where requested. - */ - /* XXX brecht: instead of zero at least use a tiny offset, otherwise - * pixel rounding is effectively random due to float inaccuracy */ - if (sizex > 0) { - xofs = eps * BLI_rctf_size_x(&v2d->cur) / sizex; - } - if (sizey > 0) { - yofs = eps * BLI_rctf_size_y(&v2d->cur) / sizey; - } - - /* apply mask-based adjustments to cur rect (due to scrollers), - * to eliminate scaling artifacts */ - view2d_map_cur_using_mask(v2d, &curmasked); - - BLI_rctf_translate(&curmasked, -xofs, -yofs); - - /* XXX ton: this flag set by outliner, for icons */ - if (v2d->flag & V2D_PIXELOFS_X) { - curmasked.xmin = floorf(curmasked.xmin) - (eps + xofs); - curmasked.xmax = floorf(curmasked.xmax) - (eps + xofs); - } - if (v2d->flag & V2D_PIXELOFS_Y) { - curmasked.ymin = floorf(curmasked.ymin) - (eps + yofs); - curmasked.ymax = floorf(curmasked.ymax) - (eps + yofs); - } - - /* set matrix on all appropriate axes */ - wmOrtho2(curmasked.xmin, curmasked.xmax, curmasked.ymin, curmasked.ymax); -} - -void UI_view2d_view_orthoSpecial(ARegion *region, View2D *v2d, const bool xaxis) -{ - rctf curmasked; - float xofs, yofs; - - /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1 - * correspondence with pixels for smooth UI drawing, - * but only applied where requested. - */ - /* XXX(ton): temp. */ - xofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f; - yofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f; - - /* apply mask-based adjustments to cur rect (due to scrollers), - * to eliminate scaling artifacts */ - view2d_map_cur_using_mask(v2d, &curmasked); - - /* only set matrix with 'cur' coordinates on relevant axes */ - if (xaxis) { - wmOrtho2(curmasked.xmin - xofs, curmasked.xmax - xofs, -yofs, region->winy - yofs); - } - else { - wmOrtho2(-xofs, region->winx - xofs, curmasked.ymin - yofs, curmasked.ymax - yofs); - } -} - -void UI_view2d_view_restore(const bContext *C) -{ - ARegion *region = CTX_wm_region(C); - const int width = BLI_rcti_size_x(®ion->winrct) + 1; - const int height = BLI_rcti_size_y(®ion->winrct) + 1; - - wmOrtho2(0.0f, (float)width, 0.0f, (float)height); - GPU_matrix_identity_set(); - - // ED_region_pixelspace(CTX_wm_region(C)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Grid-Line Drawing - * \{ */ - -void UI_view2d_multi_grid_draw( - const View2D *v2d, int colorid, float step, int level_size, int totlevels) -{ - /* Exit if there is nothing to draw */ - if (totlevels == 0) { - return; - } - - int offset = -10; - float lstep = step; - uchar grid_line_color[3]; - - /* Make an estimate of at least how many vertices will be needed */ - uint vertex_count = 4; - vertex_count += 2 * ((int)((v2d->cur.xmax - v2d->cur.xmin) / lstep) + 1); - vertex_count += 2 * ((int)((v2d->cur.ymax - v2d->cur.ymin) / lstep) + 1); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint color = GPU_vertformat_attr_add( - format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT); - - GPU_line_width(1.0f); - - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - immBeginAtMost(GPU_PRIM_LINES, vertex_count); - - for (int level = 0; level < totlevels; level++) { - /* Blend the background color (colorid) with the grid color, to avoid either too low contrast - * or high contrast grid lines. This only has an effect if colorid != TH_GRID. */ - UI_GetThemeColorBlendShade3ubv(colorid, TH_GRID, 0.25f, offset, grid_line_color); - - int i = (int)(v2d->cur.xmin / lstep); - if (v2d->cur.xmin > 0.0f) { - i++; - } - float start = i * lstep; - - for (; start < v2d->cur.xmax; start += lstep, i++) { - if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) { - continue; - } - - immAttrSkip(color); - immVertex2f(pos, start, v2d->cur.ymin); - immAttr3ubv(color, grid_line_color); - immVertex2f(pos, start, v2d->cur.ymax); - } - - i = (int)(v2d->cur.ymin / lstep); - if (v2d->cur.ymin > 0.0f) { - i++; - } - start = i * lstep; - - for (; start < v2d->cur.ymax; start += lstep, i++) { - if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) { - continue; - } - - immAttrSkip(color); - immVertex2f(pos, v2d->cur.xmin, start); - immAttr3ubv(color, grid_line_color); - immVertex2f(pos, v2d->cur.xmax, start); - } - - lstep *= level_size; - offset -= 6; - } - - /* X and Y axis */ - UI_GetThemeColorBlendShade3ubv( - colorid, TH_GRID, 0.5f, -18 + ((totlevels - 1) * -6), grid_line_color); - - immAttrSkip(color); - immVertex2f(pos, 0.0f, v2d->cur.ymin); - immAttr3ubv(color, grid_line_color); - immVertex2f(pos, 0.0f, v2d->cur.ymax); - - immAttrSkip(color); - immVertex2f(pos, v2d->cur.xmin, 0.0f); - immAttr3ubv(color, grid_line_color); - immVertex2f(pos, v2d->cur.xmax, 0.0f); - - immEnd(); - immUnbindProgram(); -} - -static void grid_axis_start_and_count( - const float step, const float min, const float max, float *r_start, int *r_count) -{ - *r_start = min; - if (*r_start < 0.0f) { - *r_start += -(float)fmod(min, step); - } - else { - *r_start += step - (float)fabs(fmod(min, step)); - } - - if (*r_start > max) { - *r_count = 0; - } - else { - *r_count = (max - *r_start) / step + 1; - } -} - -void UI_view2d_dot_grid_draw(const View2D *v2d, - const int grid_color_id, - const float min_step, - const int grid_subdivisions) -{ - BLI_assert(grid_subdivisions >= 0 && grid_subdivisions < 4); - if (grid_subdivisions == 0) { - return; - } - - const float zoom_x = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - - /* Scaling the dots fully with the zoom looks too busy, but a bit of size variation is nice. */ - const float min_point_size = 2.0f * UI_DPI_FAC; - const float point_size_factor = 1.5f; - const float max_point_size = point_size_factor * min_point_size; - - /* Each consecutive grid level is five times larger than the previous. */ - const int subdivision_scale = 5; - - const float view_level = logf(min_step / zoom_x) / logf(subdivision_scale); - const int largest_visible_level = (int)view_level; - - for (int level_offset = 0; level_offset <= grid_subdivisions; level_offset++) { - const int level = largest_visible_level - level_offset; - - if (level < 0) { - break; - } - - const float level_scale = powf(subdivision_scale, level); - const float point_size_precise = min_point_size * level_scale * zoom_x; - const float point_size_draw = ceilf( - clamp_f(point_size_precise, min_point_size, max_point_size)); - - /* To compensate the for the clamped point_size we adjust the alpha to make the overall - * brightness of the grid background more consistent. */ - const float alpha = pow2f(point_size_precise / point_size_draw); - - /* Make sure we don't draw points once the alpha gets too low. */ - const float alpha_cutoff = 0.01f; - if (alpha < alpha_cutoff) { - break; - } - const float alpha_clamped = clamp_f((1.0f + alpha_cutoff) * alpha - alpha_cutoff, 0.0f, 1.0f); - - /* If we have don't draw enough subdivision levels so they fade out naturally, we apply an - * additional fade to the last level to avoid pop in. */ - const bool last_level = level_offset == grid_subdivisions; - const float subdivision_fade = last_level ? (1.0f - fractf(view_level)) : 1.0f; - - float color[4]; - UI_GetThemeColor3fv(grid_color_id, color); - color[3] = alpha_clamped * subdivision_fade; - - const float step = min_step * level_scale; - int count_x; - float start_x; - grid_axis_start_and_count(step, v2d->cur.xmin, v2d->cur.xmax, &start_x, &count_x); - int count_y; - float start_y; - grid_axis_start_and_count(step, v2d->cur.ymin, v2d->cur.ymax, &start_y, &count_y); - if (count_x == 0 || count_y == 0) { - continue; - } - - GPU_point_size(point_size_draw); - immBegin(GPU_PRIM_POINTS, count_x * count_y); - - /* Theoretically drawing on top of lower grid levels could be avoided, but it would also - * increase the complexity of this loop, which isn't worth the time at the moment. */ - for (int i_y = 0; i_y < count_y; i_y++) { - const float y = start_y + step * i_y; - for (int i_x = 0; i_x < count_x; i_x++) { - const float x = start_x + step * i_x; - immAttr4fv(color_id, color); - immVertex2f(pos, x, y); - } - } - - immEnd(); - } - - immUnbindProgram(); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Scrollers - * \{ */ - -/** - * View2DScrollers is typedef'd in UI_view2d.h - * - * \warning The start of this struct must not change, as view2d_ops.c uses this too. - * For now, we don't need to have a separate (internal) header for structs like this... - */ -struct View2DScrollers { - /* focus bubbles */ - /* focus bubbles */ - /* focus bubbles */ - int vert_min, vert_max; /* vertical scrollbar */ - int hor_min, hor_max; /* horizontal scrollbar */ - - /** Exact size of slider backdrop. */ - rcti hor, vert; - /* set if sliders are full, we don't draw them */ - /* int horfull, vertfull; */ /* UNUSED */ -}; - -void UI_view2d_scrollers_calc(View2D *v2d, - const rcti *mask_custom, - struct View2DScrollers *r_scrollers) -{ - rcti vert, hor; - float fac1, fac2, totsize, scrollsize; - const int scroll = view2d_scroll_mapped(v2d->scroll); - int smaller; - - /* Always update before drawing (for dynamically sized scrollers). */ - view2d_masks(v2d, mask_custom); - - vert = v2d->vert; - hor = v2d->hor; - - /* slider rects need to be smaller than region and not interfere with splitter areas */ - hor.xmin += UI_HEADER_OFFSET; - hor.xmax -= UI_HEADER_OFFSET; - vert.ymin += UI_HEADER_OFFSET; - vert.ymax -= UI_HEADER_OFFSET; - - /* width of sliders */ - smaller = (int)(0.1f * U.widget_unit); - if (scroll & V2D_SCROLL_BOTTOM) { - hor.ymin += smaller; - } - else { - hor.ymax -= smaller; - } - - if (scroll & V2D_SCROLL_LEFT) { - vert.xmin += smaller; - } - else { - vert.xmax -= smaller; - } - - CLAMP_MAX(vert.ymin, vert.ymax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); - CLAMP_MAX(hor.xmin, hor.xmax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); - - /* store in scrollers, used for drawing */ - r_scrollers->vert = vert; - r_scrollers->hor = hor; - - /* scroller 'buttons': - * - These should always remain within the visible region of the scrollbar - * - They represent the region of 'tot' that is visible in 'cur' - */ - - /* horizontal scrollers */ - if (scroll & V2D_SCROLL_HORIZONTAL) { - /* scroller 'button' extents */ - totsize = BLI_rctf_size_x(&v2d->tot); - scrollsize = (float)BLI_rcti_size_x(&hor); - if (totsize == 0.0f) { - totsize = 1.0f; /* avoid divide by zero */ - } - - fac1 = (v2d->cur.xmin - v2d->tot.xmin) / totsize; - if (fac1 <= 0.0f) { - r_scrollers->hor_min = hor.xmin; - } - else { - r_scrollers->hor_min = (int)(hor.xmin + (fac1 * scrollsize)); - } - - fac2 = (v2d->cur.xmax - v2d->tot.xmin) / totsize; - if (fac2 >= 1.0f) { - r_scrollers->hor_max = hor.xmax; - } - else { - r_scrollers->hor_max = (int)(hor.xmin + (fac2 * scrollsize)); - } - - /* prevent inverted sliders */ - if (r_scrollers->hor_min > r_scrollers->hor_max) { - r_scrollers->hor_min = r_scrollers->hor_max; - } - /* prevent sliders from being too small to grab */ - if ((r_scrollers->hor_max - r_scrollers->hor_min) < V2D_SCROLL_THUMB_SIZE_MIN) { - r_scrollers->hor_max = r_scrollers->hor_min + V2D_SCROLL_THUMB_SIZE_MIN; - - CLAMP(r_scrollers->hor_max, hor.xmin + V2D_SCROLL_THUMB_SIZE_MIN, hor.xmax); - CLAMP(r_scrollers->hor_min, hor.xmin, hor.xmax - V2D_SCROLL_THUMB_SIZE_MIN); - } - } - - /* vertical scrollers */ - if (scroll & V2D_SCROLL_VERTICAL) { - /* scroller 'button' extents */ - totsize = BLI_rctf_size_y(&v2d->tot); - scrollsize = (float)BLI_rcti_size_y(&vert); - if (totsize == 0.0f) { - totsize = 1.0f; /* avoid divide by zero */ - } - - fac1 = (v2d->cur.ymin - v2d->tot.ymin) / totsize; - if (fac1 <= 0.0f) { - r_scrollers->vert_min = vert.ymin; - } - else { - r_scrollers->vert_min = (int)(vert.ymin + (fac1 * scrollsize)); - } - - fac2 = (v2d->cur.ymax - v2d->tot.ymin) / totsize; - if (fac2 >= 1.0f) { - r_scrollers->vert_max = vert.ymax; - } - else { - r_scrollers->vert_max = (int)(vert.ymin + (fac2 * scrollsize)); - } - - /* prevent inverted sliders */ - if (r_scrollers->vert_min > r_scrollers->vert_max) { - r_scrollers->vert_min = r_scrollers->vert_max; - } - /* prevent sliders from being too small to grab */ - if ((r_scrollers->vert_max - r_scrollers->vert_min) < V2D_SCROLL_THUMB_SIZE_MIN) { - r_scrollers->vert_max = r_scrollers->vert_min + V2D_SCROLL_THUMB_SIZE_MIN; - - CLAMP(r_scrollers->vert_max, vert.ymin + V2D_SCROLL_THUMB_SIZE_MIN, vert.ymax); - CLAMP(r_scrollers->vert_min, vert.ymin, vert.ymax - V2D_SCROLL_THUMB_SIZE_MIN); - } - } -} - -void UI_view2d_scrollers_draw(View2D *v2d, const rcti *mask_custom) -{ - View2DScrollers scrollers; - UI_view2d_scrollers_calc(v2d, mask_custom, &scrollers); - bTheme *btheme = UI_GetTheme(); - rcti vert, hor; - const int scroll = view2d_scroll_mapped(v2d->scroll); - const char emboss_alpha = btheme->tui.widget_emboss[3]; - uchar scrollers_back_color[4]; - - /* Color for scrollbar backs */ - UI_GetThemeColor4ubv(TH_BACK, scrollers_back_color); - - /* make copies of rects for less typing */ - vert = scrollers.vert; - hor = scrollers.hor; - - /* horizontal scrollbar */ - if (scroll & V2D_SCROLL_HORIZONTAL) { - uiWidgetColors wcol = btheme->tui.wcol_scroll; - const float alpha_fac = v2d->alpha_hor / 255.0f; - rcti slider; - int state; - - slider.xmin = scrollers.hor_min; - slider.xmax = scrollers.hor_max; - slider.ymin = hor.ymin; - slider.ymax = hor.ymax; - - state = (v2d->scroll_ui & V2D_SCROLL_H_ACTIVE) ? UI_SCROLL_PRESSED : 0; - - wcol.inner[3] *= alpha_fac; - wcol.item[3] *= alpha_fac; - wcol.outline[3] *= alpha_fac; - btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */ - - /* show zoom handles if: - * - zooming on x-axis is allowed (no scroll otherwise) - * - slider bubble is large enough (no overdraw confusion) - * - scale is shown on the scroller - * (workaround to make sure that button windows don't show these, - * and only the time-grids with their zoom-ability can do so). - */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) && - (BLI_rcti_size_x(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) { - state |= UI_SCROLL_ARROWS; - } - - UI_draw_widget_scroll(&wcol, &hor, &slider, state); - } - - /* vertical scrollbar */ - if (scroll & V2D_SCROLL_VERTICAL) { - uiWidgetColors wcol = btheme->tui.wcol_scroll; - rcti slider; - const float alpha_fac = v2d->alpha_vert / 255.0f; - int state; - - slider.xmin = vert.xmin; - slider.xmax = vert.xmax; - slider.ymin = scrollers.vert_min; - slider.ymax = scrollers.vert_max; - - state = (v2d->scroll_ui & V2D_SCROLL_V_ACTIVE) ? UI_SCROLL_PRESSED : 0; - - wcol.inner[3] *= alpha_fac; - wcol.item[3] *= alpha_fac; - wcol.outline[3] *= alpha_fac; - btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */ - - /* show zoom handles if: - * - zooming on y-axis is allowed (no scroll otherwise) - * - slider bubble is large enough (no overdraw confusion) - * - scale is shown on the scroller - * (workaround to make sure that button windows don't show these, - * and only the time-grids with their zoomability can do so) - */ - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) && - (BLI_rcti_size_y(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) { - state |= UI_SCROLL_ARROWS; - } - - UI_draw_widget_scroll(&wcol, &vert, &slider, state); - } - - /* Was changed above, so reset. */ - btheme->tui.widget_emboss[3] = emboss_alpha; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name List View Utilities - * \{ */ - -void UI_view2d_listview_view_to_cell(float columnwidth, - float rowheight, - float startx, - float starty, - float viewx, - float viewy, - int *r_column, - int *r_row) -{ - if (r_column) { - if (columnwidth > 0) { - /* Columns go from left to right (x increases). */ - *r_column = floorf((viewx - startx) / columnwidth); - } - else { - *r_column = 0; - } - } - - if (r_row) { - if (rowheight > 0) { - /* Rows got from top to bottom (y decreases). */ - *r_row = floorf((starty - viewy) / rowheight); - } - else { - *r_row = 0; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Coordinate Conversions - * \{ */ - -float UI_view2d_region_to_view_x(const struct View2D *v2d, float x) -{ - return (v2d->cur.xmin + - (BLI_rctf_size_x(&v2d->cur) * (x - v2d->mask.xmin) / BLI_rcti_size_x(&v2d->mask))); -} -float UI_view2d_region_to_view_y(const struct View2D *v2d, float y) -{ - return (v2d->cur.ymin + - (BLI_rctf_size_y(&v2d->cur) * (y - v2d->mask.ymin) / BLI_rcti_size_y(&v2d->mask))); -} - -void UI_view2d_region_to_view( - const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) -{ - *r_view_x = UI_view2d_region_to_view_x(v2d, x); - *r_view_y = UI_view2d_region_to_view_y(v2d, y); -} - -void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst) -{ - const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; - const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)}; - - rect_dst->xmin = (v2d->cur.xmin + - (cur_size[0] * (rect_src->xmin - v2d->mask.xmin) / mask_size[0])); - rect_dst->xmax = (v2d->cur.xmin + - (cur_size[0] * (rect_src->xmax - v2d->mask.xmin) / mask_size[0])); - rect_dst->ymin = (v2d->cur.ymin + - (cur_size[1] * (rect_src->ymin - v2d->mask.ymin) / mask_size[1])); - rect_dst->ymax = (v2d->cur.ymin + - (cur_size[1] * (rect_src->ymax - v2d->mask.ymin) / mask_size[1])); -} - -float UI_view2d_view_to_region_x(const View2D *v2d, float x) -{ - return (v2d->mask.xmin + - (((x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur)) * BLI_rcti_size_x(&v2d->mask))); -} -float UI_view2d_view_to_region_y(const View2D *v2d, float y) -{ - return (v2d->mask.ymin + - (((y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur)) * BLI_rcti_size_y(&v2d->mask))); -} - -bool UI_view2d_view_to_region_clip( - const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) -{ - /* express given coordinates as proportional values */ - x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); - y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); - - /* check if values are within bounds */ - if ((x >= 0.0f) && (x <= 1.0f) && (y >= 0.0f) && (y <= 1.0f)) { - *r_region_x = (int)(v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask))); - *r_region_y = (int)(v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask))); - - return true; - } - - /* set initial value in case coordinate lies outside of bounds */ - *r_region_x = *r_region_y = V2D_IS_CLIPPED; - - return false; -} - -void UI_view2d_view_to_region( - const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) -{ - /* Step 1: express given coordinates as proportional values. */ - x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); - y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); - - /* Step 2: convert proportional distances to screen coordinates. */ - x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)); - y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); - - /* Although we don't clamp to lie within region bounds, we must avoid exceeding size of ints. */ - *r_region_x = clamp_float_to_int(x); - *r_region_y = clamp_float_to_int(y); -} - -void UI_view2d_view_to_region_fl( - const View2D *v2d, float x, float y, float *r_region_x, float *r_region_y) -{ - /* express given coordinates as proportional values */ - x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); - y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); - - /* convert proportional distances to screen coordinates */ - *r_region_x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)); - *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); -} - -void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) -{ - const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; - const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)}; - rctf rect_tmp; - - /* Step 1: express given coordinates as proportional values. */ - rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0]; - rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0]; - rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1]; - rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1]; - - /* Step 2: convert proportional distances to screen coordinates. */ - rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]); - rect_tmp.xmax = v2d->mask.xmin + (rect_tmp.xmax * mask_size[0]); - rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]); - rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]); - - clamp_rctf_to_rcti(rect_dst, &rect_tmp); -} - -void UI_view2d_view_to_region_m4(const View2D *v2d, float matrix[4][4]) -{ - rctf mask; - unit_m4(matrix); - BLI_rctf_rcti_copy(&mask, &v2d->mask); - BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &mask, matrix); -} - -bool UI_view2d_view_to_region_rcti_clip(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) -{ - const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; - const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)}; - rctf rect_tmp; - - BLI_assert(rect_src->xmin <= rect_src->xmax && rect_src->ymin <= rect_src->ymax); - - /* Step 1: express given coordinates as proportional values. */ - rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0]; - rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0]; - rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1]; - rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1]; - - if (((rect_tmp.xmax < 0.0f) || (rect_tmp.xmin > 1.0f) || (rect_tmp.ymax < 0.0f) || - (rect_tmp.ymin > 1.0f)) == 0) { - /* Step 2: convert proportional distances to screen coordinates. */ - rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]); - rect_tmp.xmax = v2d->mask.ymin + (rect_tmp.xmax * mask_size[0]); - rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]); - rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]); - - clamp_rctf_to_rcti(rect_dst, &rect_tmp); - - return true; - } - - rect_dst->xmin = rect_dst->xmax = rect_dst->ymin = rect_dst->ymax = V2D_IS_CLIPPED; - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Utilities - * \{ */ - -View2D *UI_view2d_fromcontext(const bContext *C) -{ - ScrArea *area = CTX_wm_area(C); - ARegion *region = CTX_wm_region(C); - - if (area == NULL) { - return NULL; - } - if (region == NULL) { - return NULL; - } - return &(region->v2d); -} - -View2D *UI_view2d_fromcontext_rwin(const bContext *C) -{ - ScrArea *area = CTX_wm_area(C); - ARegion *region = CTX_wm_region(C); - - if (area == NULL) { - return NULL; - } - if (region == NULL) { - return NULL; - } - if (region->regiontype != RGN_TYPE_WINDOW) { - ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - return region_win ? &(region_win->v2d) : NULL; - } - return &(region->v2d); -} - -void UI_view2d_scroller_size_get(const View2D *v2d, float *r_x, float *r_y) -{ - const int scroll = view2d_scroll_mapped(v2d->scroll); - - if (r_x) { - if (scroll & V2D_SCROLL_VERTICAL) { - *r_x = (scroll & V2D_SCROLL_VERTICAL_HANDLES) ? V2D_SCROLL_HANDLE_WIDTH : V2D_SCROLL_WIDTH; - } - else { - *r_x = 0; - } - } - if (r_y) { - if (scroll & V2D_SCROLL_HORIZONTAL) { - *r_y = (scroll & V2D_SCROLL_HORIZONTAL_HANDLES) ? V2D_SCROLL_HANDLE_HEIGHT : - V2D_SCROLL_HEIGHT; - } - else { - *r_y = 0; - } - } -} - -void UI_view2d_scale_get(const View2D *v2d, float *r_x, float *r_y) -{ - if (r_x) { - *r_x = UI_view2d_scale_get_x(v2d); - } - if (r_y) { - *r_y = UI_view2d_scale_get_y(v2d); - } -} -float UI_view2d_scale_get_x(const View2D *v2d) -{ - return BLI_rcti_size_x(&v2d->mask) / BLI_rctf_size_x(&v2d->cur); -} -float UI_view2d_scale_get_y(const View2D *v2d) -{ - return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur); -} -void UI_view2d_scale_get_inverse(const View2D *v2d, float *r_x, float *r_y) -{ - if (r_x) { - *r_x = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask); - } - if (r_y) { - *r_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask); - } -} - -void UI_view2d_center_get(const struct View2D *v2d, float *r_x, float *r_y) -{ - /* get center */ - if (r_x) { - *r_x = BLI_rctf_cent_x(&v2d->cur); - } - if (r_y) { - *r_y = BLI_rctf_cent_y(&v2d->cur); - } -} -void UI_view2d_center_set(struct View2D *v2d, float x, float y) -{ - BLI_rctf_recenter(&v2d->cur, x, y); - - /* make sure that 'cur' rect is in a valid state as a result of these changes */ - UI_view2d_curRect_validate(v2d); -} - -void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac) -{ - if (xfac != -1.0f) { - const float xsize = BLI_rctf_size_x(&v2d->cur); - const float xmin = v2d->tot.xmin; - const float xmax = v2d->tot.xmax - xsize; - - v2d->cur.xmin = (xmin * (1.0f - xfac)) + (xmax * xfac); - v2d->cur.xmax = v2d->cur.xmin + xsize; - } - - if (yfac != -1.0f) { - const float ysize = BLI_rctf_size_y(&v2d->cur); - const float ymin = v2d->tot.ymin; - const float ymax = v2d->tot.ymax - ysize; - - v2d->cur.ymin = (ymin * (1.0f - yfac)) + (ymax * yfac); - v2d->cur.ymax = v2d->cur.ymin + ysize; - } - - UI_view2d_curRect_validate(v2d); -} - -char UI_view2d_mouse_in_scrollers_ex(const ARegion *region, - const View2D *v2d, - const int xy[2], - int *r_scroll) -{ - const int scroll = view2d_scroll_mapped(v2d->scroll); - *r_scroll = scroll; - - if (scroll) { - /* Move to region-coordinates. */ - const int co[2] = { - xy[0] - region->winrct.xmin, - xy[1] - region->winrct.ymin, - }; - if (scroll & V2D_SCROLL_HORIZONTAL) { - if (IN_2D_HORIZ_SCROLL(v2d, co)) { - return 'h'; - } - } - if (scroll & V2D_SCROLL_VERTICAL) { - if (IN_2D_VERT_SCROLL(v2d, co)) { - return 'v'; - } - } - } - - return 0; -} - -char UI_view2d_rect_in_scrollers_ex(const ARegion *region, - const View2D *v2d, - const rcti *rect, - int *r_scroll) -{ - const int scroll = view2d_scroll_mapped(v2d->scroll); - *r_scroll = scroll; - - if (scroll) { - /* Move to region-coordinates. */ - rcti rect_region = *rect; - BLI_rcti_translate(&rect_region, -region->winrct.xmin, region->winrct.ymin); - if (scroll & V2D_SCROLL_HORIZONTAL) { - if (IN_2D_HORIZ_SCROLL_RECT(v2d, &rect_region)) { - return 'h'; - } - } - if (scroll & V2D_SCROLL_VERTICAL) { - if (IN_2D_VERT_SCROLL_RECT(v2d, &rect_region)) { - return 'v'; - } - } - } - - return 0; -} - -char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, const int xy[2]) -{ - int scroll_dummy = 0; - return UI_view2d_mouse_in_scrollers_ex(region, v2d, xy, &scroll_dummy); -} - -char UI_view2d_rect_in_scrollers(const ARegion *region, const View2D *v2d, const rcti *rect) -{ - int scroll_dummy = 0; - return UI_view2d_rect_in_scrollers_ex(region, v2d, rect, &scroll_dummy); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View2D Text Drawing Cache - * \{ */ - -typedef struct View2DString { - struct View2DString *next; - union { - uchar ub[4]; - int pack; - } col; - rcti rect; - int mval[2]; - - /* str is allocated past the end */ - char str[0]; -} View2DString; - -/* assumes caches are used correctly, so for time being no local storage in v2d */ -static MemArena *g_v2d_strings_arena = NULL; -static View2DString *g_v2d_strings = NULL; - -void UI_view2d_text_cache_add( - View2D *v2d, float x, float y, const char *str, size_t str_len, const uchar col[4]) -{ - int mval[2]; - - BLI_assert(str_len == strlen(str)); - - if (UI_view2d_view_to_region_clip(v2d, x, y, &mval[0], &mval[1])) { - const int alloc_len = str_len + 1; - View2DString *v2s; - - if (g_v2d_strings_arena == NULL) { - g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__); - } - - v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len); - - BLI_LINKS_PREPEND(g_v2d_strings, v2s); - - v2s->col.pack = *((const int *)col); - - memset(&v2s->rect, 0, sizeof(v2s->rect)); - - v2s->mval[0] = mval[0]; - v2s->mval[1] = mval[1]; - - memcpy(v2s->str, str, alloc_len); - } -} - -void UI_view2d_text_cache_add_rectf( - View2D *v2d, const rctf *rect_view, const char *str, size_t str_len, const uchar col[4]) -{ - rcti rect; - - BLI_assert(str_len == strlen(str)); - - if (UI_view2d_view_to_region_rcti_clip(v2d, rect_view, &rect)) { - const int alloc_len = str_len + 1; - View2DString *v2s; - - if (g_v2d_strings_arena == NULL) { - g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__); - } - - v2s = BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len); - - BLI_LINKS_PREPEND(g_v2d_strings, v2s); - - v2s->col.pack = *((const int *)col); - - v2s->rect = rect; - - v2s->mval[0] = v2s->rect.xmin; - v2s->mval[1] = v2s->rect.ymin; - - memcpy(v2s->str, str, alloc_len); - } -} - -void UI_view2d_text_cache_draw(ARegion *region) -{ - View2DString *v2s; - int col_pack_prev = 0; - - /* investigate using BLF_ascender() */ - const int font_id = BLF_default(); - - BLF_set_default(); - const float default_height = g_v2d_strings ? BLF_height(font_id, "28", 3) : 0.0f; - - wmOrtho2_region_pixelspace(region); - - for (v2s = g_v2d_strings; v2s; v2s = v2s->next) { - int xofs = 0, yofs; - - yofs = ceil(0.5f * (BLI_rcti_size_y(&v2s->rect) - default_height)); - if (yofs < 1) { - yofs = 1; - } - - if (col_pack_prev != v2s->col.pack) { - BLF_color3ubv(font_id, v2s->col.ub); - col_pack_prev = v2s->col.pack; - } - - if (v2s->rect.xmin >= v2s->rect.xmax) { - BLF_draw_default((float)(v2s->mval[0] + xofs), - (float)(v2s->mval[1] + yofs), - 0.0, - v2s->str, - BLF_DRAW_STR_DUMMY_MAX); - } - else { - BLF_enable(font_id, BLF_CLIPPING); - BLF_clipping( - font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4); - BLF_draw_default( - v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX); - BLF_disable(font_id, BLF_CLIPPING); - } - } - g_v2d_strings = NULL; - - if (g_v2d_strings_arena) { - BLI_memarena_free(g_v2d_strings_arena); - g_v2d_strings_arena = NULL; - } -} - -/** \} */ diff --git a/source/blender/editors/interface/view2d.cc b/source/blender/editors/interface/view2d.cc new file mode 100644 index 00000000000..66171dc13e4 --- /dev/null +++ b/source/blender/editors/interface/view2d.cc @@ -0,0 +1,2130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_array.h" +#include "BLI_easing.h" +#include "BLI_link_utils.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_timecode.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_screen.h" + +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "WM_api.h" + +#include "BLF_api.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "interface_intern.h" + +static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize); + +/* -------------------------------------------------------------------- */ +/** \name Internal Utilities + * \{ */ + +BLI_INLINE int clamp_float_to_int(const float f) +{ + const float min = (float)INT_MIN; + const float max = (float)INT_MAX; + + if (UNLIKELY(f < min)) { + return min; + } + if (UNLIKELY(f > max)) { + return (int)max; + } + return (int)f; +} + +/** + * use instead of #BLI_rcti_rctf_copy so we have consistent behavior + * with users of #clamp_float_to_int. + */ +BLI_INLINE void clamp_rctf_to_rcti(rcti *dst, const rctf *src) +{ + dst->xmin = clamp_float_to_int(src->xmin); + dst->xmax = clamp_float_to_int(src->xmax); + dst->ymin = clamp_float_to_int(src->ymin); + dst->ymax = clamp_float_to_int(src->ymax); +} + +/* XXX still unresolved: scrolls hide/unhide vs region mask handling */ +/* XXX there's V2D_SCROLL_HORIZONTAL_HIDE and V2D_SCROLL_HORIZONTAL_FULLR ... */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Scroll & Mask Utilities + * \{ */ + +/** + * helper to allow scrollbars to dynamically hide + * - returns a copy of the scrollbar settings with the flags to display + * horizontal/vertical scrollbars removed + * - input scroll value is the v2d->scroll var + * - hide flags are set per region at drawtime + */ +static int view2d_scroll_mapped(int scroll) +{ + if (scroll & V2D_SCROLL_HORIZONTAL_FULLR) { + scroll &= ~V2D_SCROLL_HORIZONTAL; + } + if (scroll & V2D_SCROLL_VERTICAL_FULLR) { + scroll &= ~V2D_SCROLL_VERTICAL; + } + return scroll; +} + +void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask) +{ + r_mask->xmin = 0; + r_mask->ymin = 0; + r_mask->xmax = v2d->winx - 1; /* -1 yes! masks are pixels */ + r_mask->ymax = v2d->winy - 1; +} + +/** + * Called each time #View2D.cur changes, to dynamically update masks. + * + * \param mask_scroll: Optionally clamp scrollbars by this region. + */ +static void view2d_masks(View2D *v2d, const rcti *mask_scroll) +{ + int scroll; + + /* mask - view frame */ + UI_view2d_mask_from_win(v2d, &v2d->mask); + if (mask_scroll == nullptr) { + mask_scroll = &v2d->mask; + } + + /* check size if hiding flag is set: */ + if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) { + if (!(v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES)) { + if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) { + v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR; + } + else { + v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR; + } + } + } + if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) { + if (!(v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES)) { + if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) { + v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR; + } + else { + v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR; + } + } + } + + scroll = view2d_scroll_mapped(v2d->scroll); + + /* Scrollers are based off region-size: + * - they can only be on one to two edges of the region they define + * - if they overlap, they must not occupy the corners (which are reserved for other widgets) + */ + if (scroll) { + float scroll_width, scroll_height; + + UI_view2d_scroller_size_get(v2d, &scroll_width, &scroll_height); + + /* vertical scroller */ + if (scroll & V2D_SCROLL_LEFT) { + /* on left-hand edge of region */ + v2d->vert = *mask_scroll; + v2d->vert.xmax = scroll_width; + } + else if (scroll & V2D_SCROLL_RIGHT) { + /* on right-hand edge of region */ + v2d->vert = *mask_scroll; + v2d->vert.xmax++; /* one pixel extra... was leaving a minor gap... */ + v2d->vert.xmin = v2d->vert.xmax - scroll_width; + } + + /* Currently, all regions that have vertical scale handles, + * also have the scrubbing area at the top. + * So the scrollbar has to move down a bit. */ + if (scroll & V2D_SCROLL_VERTICAL_HANDLES) { + v2d->vert.ymax -= UI_TIME_SCRUB_MARGIN_Y; + } + + /* horizontal scroller */ + if (scroll & V2D_SCROLL_BOTTOM) { + /* on bottom edge of region */ + v2d->hor = *mask_scroll; + v2d->hor.ymax = scroll_height; + } + else if (scroll & V2D_SCROLL_TOP) { + /* on upper edge of region */ + v2d->hor = *mask_scroll; + v2d->hor.ymin = v2d->hor.ymax - scroll_height; + } + + /* adjust vertical scroller if there's a horizontal scroller, to leave corner free */ + if (scroll & V2D_SCROLL_VERTICAL) { + if (scroll & V2D_SCROLL_BOTTOM) { + /* on bottom edge of region */ + v2d->vert.ymin = v2d->hor.ymax; + } + else if (scroll & V2D_SCROLL_TOP) { + /* on upper edge of region */ + v2d->vert.ymax = v2d->hor.ymin; + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View2D Refresh and Validation (Spatial) + * \{ */ + +void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy) +{ + bool tot_changed = false, do_init; + const uiStyle *style = UI_style_get(); + + do_init = (v2d->flag & V2D_IS_INIT) == 0; + + /* see eView2D_CommonViewTypes in UI_view2d.h for available view presets */ + switch (type) { + /* 'standard view' - optimum setup for 'standard' view behavior, + * that should be used new views as basis for their + * own unique View2D settings, which should be used instead of this in most cases... + */ + case V2D_COMMONVIEW_STANDARD: { + /* for now, aspect ratio should be maintained, + * and zoom is clamped within sane default limits */ + v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM); + v2d->minzoom = 0.01f; + v2d->maxzoom = 1000.0f; + + /* View2D tot rect and cur should be same size, + * and aligned using 'standard' OpenGL coordinates for now: + * - region can resize 'tot' later to fit other data + * - keeptot is only within bounds, as strict locking is not that critical + * - view is aligned for (0,0) -> (winx-1, winy-1) setup + */ + v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); + v2d->keeptot = V2D_KEEPTOT_BOUNDS; + if (do_init) { + v2d->tot.xmin = v2d->tot.ymin = 0.0f; + v2d->tot.xmax = (float)(winx - 1); + v2d->tot.ymax = (float)(winy - 1); + + v2d->cur = v2d->tot; + } + /* scrollers - should we have these by default? */ + /* XXX for now, we don't override this, or set it either! */ + break; + } + /* 'list/channel view' - zoom, aspect ratio, and alignment restrictions are set here */ + case V2D_COMMONVIEW_LIST: { + /* zoom + aspect ratio are locked */ + v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); + v2d->minzoom = v2d->maxzoom = 1.0f; + + /* tot rect has strictly regulated placement, and must only occur in +/- quadrant */ + v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y); + v2d->keeptot = V2D_KEEPTOT_STRICT; + tot_changed = do_init; + + /* scroller settings are currently not set here... that is left for regions... */ + break; + } + /* 'stack view' - practically the same as list/channel view, + * except is located in the pos y half instead. + * Zoom, aspect ratio, and alignment restrictions are set here. */ + case V2D_COMMONVIEW_STACK: { + /* zoom + aspect ratio are locked */ + v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); + v2d->minzoom = v2d->maxzoom = 1.0f; + + /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */ + v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); + v2d->keeptot = V2D_KEEPTOT_STRICT; + tot_changed = do_init; + + /* scroller settings are currently not set here... that is left for regions... */ + break; + } + /* 'header' regions - zoom, aspect ratio, + * alignment, and panning restrictions are set here */ + case V2D_COMMONVIEW_HEADER: { + /* zoom + aspect ratio are locked */ + v2d->keepzoom = (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y | V2D_LIMITZOOM | V2D_KEEPASPECT); + v2d->minzoom = v2d->maxzoom = 1.0f; + + if (do_init) { + v2d->tot.xmin = 0.0f; + v2d->tot.xmax = winx; + v2d->tot.ymin = 0.0f; + v2d->tot.ymax = winy; + v2d->cur = v2d->tot; + + v2d->min[0] = v2d->max[0] = (float)(winx - 1); + v2d->min[1] = v2d->max[1] = (float)(winy - 1); + } + /* tot rect has strictly regulated placement, and must only occur in +/+ quadrant */ + v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_NEG_Y); + v2d->keeptot = V2D_KEEPTOT_STRICT; + tot_changed = do_init; + + /* panning in y-axis is prohibited */ + v2d->keepofs = V2D_LOCKOFS_Y; + + /* absolutely no scrollers allowed */ + v2d->scroll = 0; + break; + } + /* panels view, with horizontal/vertical align */ + case V2D_COMMONVIEW_PANELS_UI: { + + /* for now, aspect ratio should be maintained, + * and zoom is clamped within sane default limits */ + v2d->keepzoom = (V2D_KEEPASPECT | V2D_LIMITZOOM | V2D_KEEPZOOM); + v2d->minzoom = 0.5f; + v2d->maxzoom = 2.0f; + + v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y); + v2d->keeptot = V2D_KEEPTOT_BOUNDS; + + /* NOTE: scroll is being flipped in #ED_region_panels() drawing. */ + v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE); + + if (do_init) { + const float panelzoom = (style) ? style->panelzoom : 1.0f; + + v2d->tot.xmin = 0.0f; + v2d->tot.xmax = winx; + + v2d->tot.ymax = 0.0f; + v2d->tot.ymin = -winy; + + v2d->cur.xmin = 0.0f; + v2d->cur.xmax = (winx)*panelzoom; + + v2d->cur.ymax = 0.0f; + v2d->cur.ymin = (-winy) * panelzoom; + } + break; + } + /* other view types are completely defined using their own settings already */ + default: + /* we don't do anything here, + * as settings should be fine, but just make sure that rect */ + break; + } + + /* set initialized flag so that View2D doesn't get reinitialized next time again */ + v2d->flag |= V2D_IS_INIT; + + /* store view size */ + v2d->winx = winx; + v2d->winy = winy; + + view2d_masks(v2d, nullptr); + + if (do_init) { + /* Visible by default. */ + v2d->alpha_hor = v2d->alpha_vert = 255; + } + + /* set 'tot' rect before setting cur? */ + /* XXX confusing stuff here still */ + if (tot_changed) { + UI_view2d_totRect_set_resize(v2d, winx, winy, !do_init); + } + else { + ui_view2d_curRect_validate_resize(v2d, !do_init); + } +} + +/** + * Ensure View2D rects remain in a viable configuration + * 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot' + */ +/* XXX pre2.5 -> this used to be called test_view2d() */ +static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize) +{ + float totwidth, totheight, curwidth, curheight, width, height; + float winx, winy; + rctf *cur, *tot; + + /* use mask as size of region that View2D resides in, as it takes into account + * scrollbars already - keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */ + winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1); + winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1); + + /* get pointers to rcts for less typing */ + cur = &v2d->cur; + tot = &v2d->tot; + + /* we must satisfy the following constraints (in decreasing order of importance): + * - alignment restrictions are respected + * - cur must not fall outside of tot + * - axis locks (zoom and offset) must be maintained + * - zoom must not be excessive (check either sizes or zoom values) + * - aspect ratio should be respected (NOTE: this is quite closely related to zoom too) + */ + + /* Step 1: if keepzoom, adjust the sizes of the rects only + * - firstly, we calculate the sizes of the rects + * - curwidth and curheight are saved as reference... modify width and height values here + */ + totwidth = BLI_rctf_size_x(tot); + totheight = BLI_rctf_size_y(tot); + /* keep in sync with zoomx/zoomy in view_zoomstep_apply_ex! */ + curwidth = width = BLI_rctf_size_x(cur); + curheight = height = BLI_rctf_size_y(cur); + + /* if zoom is locked, size on the appropriate axis is reset to mask size */ + if (v2d->keepzoom & V2D_LOCKZOOM_X) { + width = winx; + } + if (v2d->keepzoom & V2D_LOCKZOOM_Y) { + height = winy; + } + + /* values used to divide, so make it safe + * NOTE: width and height must use FLT_MIN instead of 1, otherwise it is impossible to + * get enough resolution in Graph Editor for editing some curves + */ + if (width < FLT_MIN) { + width = 1; + } + if (height < FLT_MIN) { + height = 1; + } + if (winx < 1) { + winx = 1; + } + if (winy < 1) { + winy = 1; + } + + /* V2D_LIMITZOOM indicates that zoom level should be preserved when the window size changes */ + if (resize && (v2d->keepzoom & V2D_KEEPZOOM)) { + float zoom, oldzoom; + + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + zoom = winx / width; + oldzoom = v2d->oldwinx / curwidth; + + if (oldzoom != zoom) { + width *= zoom / oldzoom; + } + } + + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + zoom = winy / height; + oldzoom = v2d->oldwiny / curheight; + + if (oldzoom != zoom) { + height *= zoom / oldzoom; + } + } + } + /* keepzoom (V2D_LIMITZOOM set), indicates that zoom level on each axis must not exceed limits + * NOTE: in general, it is not expected that the lock-zoom will be used in conjunction with this + */ + else if (v2d->keepzoom & V2D_LIMITZOOM) { + + /* check if excessive zoom on x-axis */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + const float zoom = winx / width; + if (zoom < v2d->minzoom) { + width = winx / v2d->minzoom; + } + else if (zoom > v2d->maxzoom) { + width = winx / v2d->maxzoom; + } + } + + /* check if excessive zoom on y-axis */ + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + const float zoom = winy / height; + if (zoom < v2d->minzoom) { + height = winy / v2d->minzoom; + } + else if (zoom > v2d->maxzoom) { + height = winy / v2d->maxzoom; + } + } + } + else { + /* make sure sizes don't exceed that of the min/max sizes + * (even though we're not doing zoom clamping) */ + CLAMP(width, v2d->min[0], v2d->max[0]); + CLAMP(height, v2d->min[1], v2d->max[1]); + } + + /* check if we should restore aspect ratio (if view size changed) */ + if (v2d->keepzoom & V2D_KEEPASPECT) { + bool do_x = false, do_y = false, do_cur /* , do_win */ /* UNUSED */; + float curRatio, winRatio; + + /* when a window edge changes, the aspect ratio can't be used to + * find which is the best new 'cur' rect. that's why it stores 'old' + */ + if (winx != v2d->oldwinx) { + do_x = true; + } + if (winy != v2d->oldwiny) { + do_y = true; + } + + curRatio = height / width; + winRatio = winy / winx; + + /* Both sizes change (area/region maximized). */ + if (do_x == do_y) { + if (do_x && do_y) { + /* here is 1,1 case, so all others must be 0,0 */ + if (fabsf(winx - v2d->oldwinx) > fabsf(winy - v2d->oldwiny)) { + do_y = false; + } + else { + do_x = false; + } + } + else if (winRatio > curRatio) { + do_x = false; + } + else { + do_x = true; + } + } + do_cur = do_x; + /* do_win = do_y; */ /* UNUSED */ + + if (do_cur) { + if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winx != v2d->oldwinx)) { + /* Special exception for Outliner (and later channel-lists): + * - The view may be moved left to avoid contents + * being pushed out of view when view shrinks. + * - The keeptot code will make sure cur->xmin will not be less than tot->xmin + * (which cannot be allowed). + * - width is not adjusted for changed ratios here. + */ + if (winx < v2d->oldwinx) { + const float temp = v2d->oldwinx - winx; + + cur->xmin -= temp; + cur->xmax -= temp; + + /* width does not get modified, as keepaspect here is just set to make + * sure visible area adjusts to changing view shape! + */ + } + } + else { + /* portrait window: correct for x */ + width = height / winRatio; + } + } + else { + if ((v2d->keeptot == V2D_KEEPTOT_STRICT) && (winy != v2d->oldwiny)) { + /* special exception for Outliner (and later channel-lists): + * - Currently, no actions need to be taken here... + */ + + if (winy < v2d->oldwiny) { + const float temp = v2d->oldwiny - winy; + + if (v2d->align & V2D_ALIGN_NO_NEG_Y) { + cur->ymin -= temp; + cur->ymax -= temp; + } + else { /* Assume V2D_ALIGN_NO_POS_Y or combination */ + cur->ymin += temp; + cur->ymax += temp; + } + } + } + else { + /* landscape window: correct for y */ + height = width * winRatio; + } + } + + /* store region size for next time */ + v2d->oldwinx = (short)winx; + v2d->oldwiny = (short)winy; + } + + /* Step 2: apply new sizes to cur rect, + * but need to take into account alignment settings here... */ + if ((width != curwidth) || (height != curheight)) { + float temp, dh; + + /* Resize from center-point, unless otherwise specified. */ + if (width != curwidth) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + cur->xmax += width - BLI_rctf_size_x(cur); + } + else if (v2d->keepofs & V2D_KEEPOFS_X) { + if (v2d->align & V2D_ALIGN_NO_POS_X) { + cur->xmin -= width - BLI_rctf_size_x(cur); + } + else { + cur->xmax += width - BLI_rctf_size_x(cur); + } + } + else { + temp = BLI_rctf_cent_x(cur); + dh = width * 0.5f; + + cur->xmin = temp - dh; + cur->xmax = temp + dh; + } + } + if (height != curheight) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + cur->ymax += height - BLI_rctf_size_y(cur); + } + else if (v2d->keepofs & V2D_KEEPOFS_Y) { + if (v2d->align & V2D_ALIGN_NO_POS_Y) { + cur->ymin -= height - BLI_rctf_size_y(cur); + } + else { + cur->ymax += height - BLI_rctf_size_y(cur); + } + } + else { + temp = BLI_rctf_cent_y(cur); + dh = height * 0.5f; + + cur->ymin = temp - dh; + cur->ymax = temp + dh; + } + } + } + + /* Step 3: adjust so that it doesn't fall outside of bounds of 'tot' */ + if (v2d->keeptot) { + float temp, diff; + + /* recalculate extents of cur */ + curwidth = BLI_rctf_size_x(cur); + curheight = BLI_rctf_size_y(cur); + + /* width */ + if ((curwidth > totwidth) && + !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_X | V2D_LIMITZOOM))) { + /* if zoom doesn't have to be maintained, just clamp edges */ + if (cur->xmin < tot->xmin) { + cur->xmin = tot->xmin; + } + if (cur->xmax > tot->xmax) { + cur->xmax = tot->xmax; + } + } + else if (v2d->keeptot == V2D_KEEPTOT_STRICT) { + /* This is an exception for the outliner (and later channel-lists, headers) + * - must clamp within tot rect (absolutely no excuses) + * --> therefore, cur->xmin must not be less than tot->xmin + */ + if (cur->xmin < tot->xmin) { + /* move cur across so that it sits at minimum of tot */ + temp = tot->xmin - cur->xmin; + + cur->xmin += temp; + cur->xmax += temp; + } + else if (cur->xmax > tot->xmax) { + /* - only offset by difference of cur-xmax and tot-xmax if that would not move + * cur-xmin to lie past tot-xmin + * - otherwise, simply shift to tot-xmin??? + */ + temp = cur->xmax - tot->xmax; + + if ((cur->xmin - temp) < tot->xmin) { + /* only offset by difference from cur-min and tot-min */ + temp = cur->xmin - tot->xmin; + + cur->xmin -= temp; + cur->xmax -= temp; + } + else { + cur->xmin -= temp; + cur->xmax -= temp; + } + } + } + else { + /* This here occurs when: + * - width too big, but maintaining zoom (i.e. widths cannot be changed) + * - width is OK, but need to check if outside of boundaries + * + * So, resolution is to just shift view by the gap between the extremities. + * We favor moving the 'minimum' across, as that's origin for most things. + * (XXX: in the past, max was favored... if there are bugs, swap!) + */ + if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) { + /* outside boundaries on both sides, + * so take middle-point of tot, and place in balanced way */ + temp = BLI_rctf_cent_x(tot); + diff = curwidth * 0.5f; + + cur->xmin = temp - diff; + cur->xmax = temp + diff; + } + else if (cur->xmin < tot->xmin) { + /* move cur across so that it sits at minimum of tot */ + temp = tot->xmin - cur->xmin; + + cur->xmin += temp; + cur->xmax += temp; + } + else if (cur->xmax > tot->xmax) { + /* - only offset by difference of cur-xmax and tot-xmax if that would not move + * cur-xmin to lie past tot-xmin + * - otherwise, simply shift to tot-xmin??? + */ + temp = cur->xmax - tot->xmax; + + if ((cur->xmin - temp) < tot->xmin) { + /* only offset by difference from cur-min and tot-min */ + temp = cur->xmin - tot->xmin; + + cur->xmin -= temp; + cur->xmax -= temp; + } + else { + cur->xmin -= temp; + cur->xmax -= temp; + } + } + } + + /* height */ + if ((curheight > totheight) && + !(v2d->keepzoom & (V2D_KEEPZOOM | V2D_LOCKZOOM_Y | V2D_LIMITZOOM))) { + /* if zoom doesn't have to be maintained, just clamp edges */ + if (cur->ymin < tot->ymin) { + cur->ymin = tot->ymin; + } + if (cur->ymax > tot->ymax) { + cur->ymax = tot->ymax; + } + } + else { + /* This here occurs when: + * - height too big, but maintaining zoom (i.e. heights cannot be changed) + * - height is OK, but need to check if outside of boundaries + * + * So, resolution is to just shift view by the gap between the extremities. + * We favor moving the 'minimum' across, as that's origin for most things. + */ + if ((cur->ymin < tot->ymin) && (cur->ymax > tot->ymax)) { + /* outside boundaries on both sides, + * so take middle-point of tot, and place in balanced way */ + temp = BLI_rctf_cent_y(tot); + diff = curheight * 0.5f; + + cur->ymin = temp - diff; + cur->ymax = temp + diff; + } + else if (cur->ymin < tot->ymin) { + /* there's still space remaining, so shift up */ + temp = tot->ymin - cur->ymin; + + cur->ymin += temp; + cur->ymax += temp; + } + else if (cur->ymax > tot->ymax) { + /* there's still space remaining, so shift down */ + temp = cur->ymax - tot->ymax; + + cur->ymin -= temp; + cur->ymax -= temp; + } + } + } + + /* Step 4: Make sure alignment restrictions are respected */ + if (v2d->align) { + /* If alignment flags are set (but keeptot is not), they must still be respected, as although + * they don't specify any particular bounds to stay within, they do define ranges which are + * invalid. + * + * Here, we only check to make sure that on each axis, the 'cur' rect doesn't stray into these + * invalid zones, otherwise we offset. + */ + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + if (v2d->cur.xmax > 0) { + v2d->cur.xmin -= v2d->cur.xmax; + v2d->cur.xmax = 0.0f; + } + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + if (v2d->cur.xmin < 0) { + v2d->cur.xmax -= v2d->cur.xmin; + v2d->cur.xmin = 0.0f; + } + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + if (v2d->cur.ymax > 0) { + v2d->cur.ymin -= v2d->cur.ymax; + v2d->cur.ymax = 0.0f; + } + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + if (v2d->cur.ymin < 0) { + v2d->cur.ymax -= v2d->cur.ymin; + v2d->cur.ymin = 0.0f; + } + } + } + + /* set masks */ + view2d_masks(v2d, nullptr); +} + +void UI_view2d_curRect_validate(View2D *v2d) +{ + ui_view2d_curRect_validate_resize(v2d, false); +} + +void UI_view2d_curRect_changed(const bContext *C, View2D *v2d) +{ + UI_view2d_curRect_validate(v2d); + + ARegion *region = CTX_wm_region(C); + + if (region->type->on_view2d_changed != nullptr) { + region->type->on_view2d_changed(C, region); + } +} + +/* ------------------ */ + +bool UI_view2d_area_supports_sync(ScrArea *area) +{ + return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH); +} + +void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) +{ + /* don't continue if no view syncing to be done */ + if ((v2dcur->flag & (V2D_VIEWSYNC_SCREEN_TIME | V2D_VIEWSYNC_AREA_VERTICAL)) == 0) { + return; + } + + /* check if doing within area syncing (i.e. channels/vertical) */ + if ((v2dcur->flag & V2D_VIEWSYNC_AREA_VERTICAL) && (area)) { + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + /* don't operate on self */ + if (v2dcur != ®ion->v2d) { + /* only if view has vertical locks enabled */ + if (region->v2d.flag & V2D_VIEWSYNC_AREA_VERTICAL) { + if (flag == V2D_LOCK_COPY) { + /* other views with locks on must copy active */ + region->v2d.cur.ymin = v2dcur->cur.ymin; + region->v2d.cur.ymax = v2dcur->cur.ymax; + } + else { /* V2D_LOCK_SET */ + /* active must copy others */ + v2dcur->cur.ymin = region->v2d.cur.ymin; + v2dcur->cur.ymax = region->v2d.cur.ymax; + } + + /* region possibly changed, so refresh */ + ED_region_tag_redraw_no_rebuild(region); + } + } + } + } + + /* check if doing whole screen syncing (i.e. time/horizontal) */ + if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { + LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { + if (!UI_view2d_area_supports_sync(area_iter)) { + continue; + } + LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) { + /* don't operate on self */ + if (v2dcur != ®ion->v2d) { + /* only if view has horizontal locks enabled */ + if (region->v2d.flag & V2D_VIEWSYNC_SCREEN_TIME) { + if (flag == V2D_LOCK_COPY) { + /* other views with locks on must copy active */ + region->v2d.cur.xmin = v2dcur->cur.xmin; + region->v2d.cur.xmax = v2dcur->cur.xmax; + } + else { /* V2D_LOCK_SET */ + /* active must copy others */ + v2dcur->cur.xmin = region->v2d.cur.xmin; + v2dcur->cur.xmax = region->v2d.cur.xmax; + } + + /* region possibly changed, so refresh */ + ED_region_tag_redraw_no_rebuild(region); + } + } + } + } + } +} + +void UI_view2d_curRect_reset(View2D *v2d) +{ + float width, height; + + /* assume width and height of 'cur' rect by default, should be same size as mask */ + width = (float)(BLI_rcti_size_x(&v2d->mask) + 1); + height = (float)(BLI_rcti_size_y(&v2d->mask) + 1); + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + v2d->cur.xmin = -width; + v2d->cur.xmax = 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + v2d->cur.xmin = 0.0f; + v2d->cur.xmax = width; + } + else { + /* width is centered around (x == 0) */ + const float dx = width / 2.0f; + + v2d->cur.xmin = -dx; + v2d->cur.xmax = dx; + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + v2d->cur.ymin = -height; + v2d->cur.ymax = 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + v2d->cur.ymin = 0.0f; + v2d->cur.ymax = height; + } + else { + /* height is centered around (y == 0) */ + const float dy = height / 2.0f; + + v2d->cur.ymin = -dy; + v2d->cur.ymax = dy; + } +} + +/* ------------------ */ + +void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resize) +{ + /* don't do anything if either value is 0 */ + width = abs(width); + height = abs(height); + + if (ELEM(0, width, height)) { + if (G.debug & G_DEBUG) { + /* XXX: temp debug info. */ + printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d\n", + (void *)v2d, + width, + height); + } + return; + } + + /* handle width - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + /* width is in negative-x half */ + v2d->tot.xmin = (float)-width; + v2d->tot.xmax = 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + /* width is in positive-x half */ + v2d->tot.xmin = 0.0f; + v2d->tot.xmax = (float)width; + } + else { + /* width is centered around (x == 0) */ + const float dx = (float)width / 2.0f; + + v2d->tot.xmin = -dx; + v2d->tot.xmax = dx; + } + + /* handle height - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + /* height is in negative-y half */ + v2d->tot.ymin = (float)-height; + v2d->tot.ymax = 0.0f; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + /* height is in positive-y half */ + v2d->tot.ymin = 0.0f; + v2d->tot.ymax = (float)height; + } + else { + /* height is centered around (y == 0) */ + const float dy = (float)height / 2.0f; + + v2d->tot.ymin = -dy; + v2d->tot.ymax = dy; + } + + /* make sure that 'cur' rect is in a valid state as a result of these changes */ + ui_view2d_curRect_validate_resize(v2d, resize); +} + +void UI_view2d_totRect_set(View2D *v2d, int width, int height) +{ + UI_view2d_totRect_set_resize(v2d, width, height, false); +} + +void UI_view2d_zoom_cache_reset(void) +{ + /* TODO(sergey): This way we avoid threading conflict with sequencer rendering + * text strip. But ideally we want to make glyph cache to be fully safe + * for threading. + */ + if (G.is_rendering) { + return; + } + /* While scaling we can accumulate fonts at many sizes (~20 or so). + * Not an issue with embedded font, but can use over 500Mb with i18n ones! See T38244. */ + + /* NOTE: only some views draw text, we could check for this case to avoid cleaning cache. */ + BLF_cache_clear(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View2D Matrix Setup + * \{ */ + +/* mapping function to ensure 'cur' draws extended over the area where sliders are */ +static void view2d_map_cur_using_mask(const View2D *v2d, rctf *r_curmasked) +{ + *r_curmasked = v2d->cur; + + if (view2d_scroll_mapped(v2d->scroll)) { + const float sizex = BLI_rcti_size_x(&v2d->mask); + const float sizey = BLI_rcti_size_y(&v2d->mask); + + /* prevent tiny or narrow regions to get + * invalid coordinates - mask can get negative even... */ + if (sizex > 0.0f && sizey > 0.0f) { + const float dx = BLI_rctf_size_x(&v2d->cur) / (sizex + 1); + const float dy = BLI_rctf_size_y(&v2d->cur) / (sizey + 1); + + if (v2d->mask.xmin != 0) { + r_curmasked->xmin -= dx * (float)v2d->mask.xmin; + } + if (v2d->mask.xmax + 1 != v2d->winx) { + r_curmasked->xmax += dx * (float)(v2d->winx - v2d->mask.xmax - 1); + } + + if (v2d->mask.ymin != 0) { + r_curmasked->ymin -= dy * (float)v2d->mask.ymin; + } + if (v2d->mask.ymax + 1 != v2d->winy) { + r_curmasked->ymax += dy * (float)(v2d->winy - v2d->mask.ymax - 1); + } + } + } +} + +void UI_view2d_view_ortho(const View2D *v2d) +{ + rctf curmasked; + const int sizex = BLI_rcti_size_x(&v2d->mask); + const int sizey = BLI_rcti_size_y(&v2d->mask); + const float eps = 0.001f; + float xofs = 0.0f, yofs = 0.0f; + + /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1 + * correspondence with pixels for smooth UI drawing, + * but only applied where requested. + */ + /* XXX brecht: instead of zero at least use a tiny offset, otherwise + * pixel rounding is effectively random due to float inaccuracy */ + if (sizex > 0) { + xofs = eps * BLI_rctf_size_x(&v2d->cur) / sizex; + } + if (sizey > 0) { + yofs = eps * BLI_rctf_size_y(&v2d->cur) / sizey; + } + + /* apply mask-based adjustments to cur rect (due to scrollers), + * to eliminate scaling artifacts */ + view2d_map_cur_using_mask(v2d, &curmasked); + + BLI_rctf_translate(&curmasked, -xofs, -yofs); + + /* XXX ton: this flag set by outliner, for icons */ + if (v2d->flag & V2D_PIXELOFS_X) { + curmasked.xmin = floorf(curmasked.xmin) - (eps + xofs); + curmasked.xmax = floorf(curmasked.xmax) - (eps + xofs); + } + if (v2d->flag & V2D_PIXELOFS_Y) { + curmasked.ymin = floorf(curmasked.ymin) - (eps + yofs); + curmasked.ymax = floorf(curmasked.ymax) - (eps + yofs); + } + + /* set matrix on all appropriate axes */ + wmOrtho2(curmasked.xmin, curmasked.xmax, curmasked.ymin, curmasked.ymax); +} + +void UI_view2d_view_orthoSpecial(ARegion *region, View2D *v2d, const bool xaxis) +{ + rctf curmasked; + float xofs, yofs; + + /* Pixel offsets (-GLA_PIXEL_OFS) are needed to get 1:1 + * correspondence with pixels for smooth UI drawing, + * but only applied where requested. + */ + /* XXX(ton): temp. */ + xofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f; + yofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f; + + /* apply mask-based adjustments to cur rect (due to scrollers), + * to eliminate scaling artifacts */ + view2d_map_cur_using_mask(v2d, &curmasked); + + /* only set matrix with 'cur' coordinates on relevant axes */ + if (xaxis) { + wmOrtho2(curmasked.xmin - xofs, curmasked.xmax - xofs, -yofs, region->winy - yofs); + } + else { + wmOrtho2(-xofs, region->winx - xofs, curmasked.ymin - yofs, curmasked.ymax - yofs); + } +} + +void UI_view2d_view_restore(const bContext *C) +{ + ARegion *region = CTX_wm_region(C); + const int width = BLI_rcti_size_x(®ion->winrct) + 1; + const int height = BLI_rcti_size_y(®ion->winrct) + 1; + + wmOrtho2(0.0f, (float)width, 0.0f, (float)height); + GPU_matrix_identity_set(); + + // ED_region_pixelspace(CTX_wm_region(C)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Grid-Line Drawing + * \{ */ + +void UI_view2d_multi_grid_draw( + const View2D *v2d, int colorid, float step, int level_size, int totlevels) +{ + /* Exit if there is nothing to draw */ + if (totlevels == 0) { + return; + } + + int offset = -10; + float lstep = step; + uchar grid_line_color[3]; + + /* Make an estimate of at least how many vertices will be needed */ + uint vertex_count = 4; + vertex_count += 2 * ((int)((v2d->cur.xmax - v2d->cur.xmin) / lstep) + 1); + vertex_count += 2 * ((int)((v2d->cur.ymax - v2d->cur.ymin) / lstep) + 1); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint color = GPU_vertformat_attr_add( + format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT); + + GPU_line_width(1.0f); + + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + immBeginAtMost(GPU_PRIM_LINES, vertex_count); + + for (int level = 0; level < totlevels; level++) { + /* Blend the background color (colorid) with the grid color, to avoid either too low contrast + * or high contrast grid lines. This only has an effect if colorid != TH_GRID. */ + UI_GetThemeColorBlendShade3ubv(colorid, TH_GRID, 0.25f, offset, grid_line_color); + + int i = (int)(v2d->cur.xmin / lstep); + if (v2d->cur.xmin > 0.0f) { + i++; + } + float start = i * lstep; + + for (; start < v2d->cur.xmax; start += lstep, i++) { + if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) { + continue; + } + + immAttrSkip(color); + immVertex2f(pos, start, v2d->cur.ymin); + immAttr3ubv(color, grid_line_color); + immVertex2f(pos, start, v2d->cur.ymax); + } + + i = (int)(v2d->cur.ymin / lstep); + if (v2d->cur.ymin > 0.0f) { + i++; + } + start = i * lstep; + + for (; start < v2d->cur.ymax; start += lstep, i++) { + if (i == 0 || (level < totlevels - 1 && i % level_size == 0)) { + continue; + } + + immAttrSkip(color); + immVertex2f(pos, v2d->cur.xmin, start); + immAttr3ubv(color, grid_line_color); + immVertex2f(pos, v2d->cur.xmax, start); + } + + lstep *= level_size; + offset -= 6; + } + + /* X and Y axis */ + UI_GetThemeColorBlendShade3ubv( + colorid, TH_GRID, 0.5f, -18 + ((totlevels - 1) * -6), grid_line_color); + + immAttrSkip(color); + immVertex2f(pos, 0.0f, v2d->cur.ymin); + immAttr3ubv(color, grid_line_color); + immVertex2f(pos, 0.0f, v2d->cur.ymax); + + immAttrSkip(color); + immVertex2f(pos, v2d->cur.xmin, 0.0f); + immAttr3ubv(color, grid_line_color); + immVertex2f(pos, v2d->cur.xmax, 0.0f); + + immEnd(); + immUnbindProgram(); +} + +static void grid_axis_start_and_count( + const float step, const float min, const float max, float *r_start, int *r_count) +{ + *r_start = min; + if (*r_start < 0.0f) { + *r_start += -(float)fmod(min, step); + } + else { + *r_start += step - (float)fabs(fmod(min, step)); + } + + if (*r_start > max) { + *r_count = 0; + } + else { + *r_count = (max - *r_start) / step + 1; + } +} + +void UI_view2d_dot_grid_draw(const View2D *v2d, + const int grid_color_id, + const float min_step, + const int grid_subdivisions) +{ + BLI_assert(grid_subdivisions >= 0 && grid_subdivisions < 4); + if (grid_subdivisions == 0) { + return; + } + + const float zoom_x = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + + /* Scaling the dots fully with the zoom looks too busy, but a bit of size variation is nice. */ + const float min_point_size = 2.0f * UI_DPI_FAC; + const float point_size_factor = 1.5f; + const float max_point_size = point_size_factor * min_point_size; + + /* Each consecutive grid level is five times larger than the previous. */ + const int subdivision_scale = 5; + + const float view_level = logf(min_step / zoom_x) / logf(subdivision_scale); + const int largest_visible_level = (int)view_level; + + for (int level_offset = 0; level_offset <= grid_subdivisions; level_offset++) { + const int level = largest_visible_level - level_offset; + + if (level < 0) { + break; + } + + const float level_scale = powf(subdivision_scale, level); + const float point_size_precise = min_point_size * level_scale * zoom_x; + const float point_size_draw = ceilf( + clamp_f(point_size_precise, min_point_size, max_point_size)); + + /* To compensate the for the clamped point_size we adjust the alpha to make the overall + * brightness of the grid background more consistent. */ + const float alpha = pow2f(point_size_precise / point_size_draw); + + /* Make sure we don't draw points once the alpha gets too low. */ + const float alpha_cutoff = 0.01f; + if (alpha < alpha_cutoff) { + break; + } + const float alpha_clamped = clamp_f((1.0f + alpha_cutoff) * alpha - alpha_cutoff, 0.0f, 1.0f); + + /* If we have don't draw enough subdivision levels so they fade out naturally, we apply an + * additional fade to the last level to avoid pop in. */ + const bool last_level = level_offset == grid_subdivisions; + const float subdivision_fade = last_level ? (1.0f - fractf(view_level)) : 1.0f; + + float color[4]; + UI_GetThemeColor3fv(grid_color_id, color); + color[3] = alpha_clamped * subdivision_fade; + + const float step = min_step * level_scale; + int count_x; + float start_x; + grid_axis_start_and_count(step, v2d->cur.xmin, v2d->cur.xmax, &start_x, &count_x); + int count_y; + float start_y; + grid_axis_start_and_count(step, v2d->cur.ymin, v2d->cur.ymax, &start_y, &count_y); + if (count_x == 0 || count_y == 0) { + continue; + } + + GPU_point_size(point_size_draw); + immBegin(GPU_PRIM_POINTS, count_x * count_y); + + /* Theoretically drawing on top of lower grid levels could be avoided, but it would also + * increase the complexity of this loop, which isn't worth the time at the moment. */ + for (int i_y = 0; i_y < count_y; i_y++) { + const float y = start_y + step * i_y; + for (int i_x = 0; i_x < count_x; i_x++) { + const float x = start_x + step * i_x; + immAttr4fv(color_id, color); + immVertex2f(pos, x, y); + } + } + + immEnd(); + } + + immUnbindProgram(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Scrollers + * \{ */ + +/** + * View2DScrollers is typedef'd in UI_view2d.h + * + * \warning The start of this struct must not change, as view2d_ops.c uses this too. + * For now, we don't need to have a separate (internal) header for structs like this... + */ +struct View2DScrollers { + /* focus bubbles */ + /* focus bubbles */ + /* focus bubbles */ + int vert_min, vert_max; /* vertical scrollbar */ + int hor_min, hor_max; /* horizontal scrollbar */ + + /** Exact size of slider backdrop. */ + rcti hor, vert; + /* set if sliders are full, we don't draw them */ + /* int horfull, vertfull; */ /* UNUSED */ +}; + +void UI_view2d_scrollers_calc(View2D *v2d, + const rcti *mask_custom, + struct View2DScrollers *r_scrollers) +{ + rcti vert, hor; + float fac1, fac2, totsize, scrollsize; + const int scroll = view2d_scroll_mapped(v2d->scroll); + int smaller; + + /* Always update before drawing (for dynamically sized scrollers). */ + view2d_masks(v2d, mask_custom); + + vert = v2d->vert; + hor = v2d->hor; + + /* slider rects need to be smaller than region and not interfere with splitter areas */ + hor.xmin += UI_HEADER_OFFSET; + hor.xmax -= UI_HEADER_OFFSET; + vert.ymin += UI_HEADER_OFFSET; + vert.ymax -= UI_HEADER_OFFSET; + + /* width of sliders */ + smaller = (int)(0.1f * U.widget_unit); + if (scroll & V2D_SCROLL_BOTTOM) { + hor.ymin += smaller; + } + else { + hor.ymax -= smaller; + } + + if (scroll & V2D_SCROLL_LEFT) { + vert.xmin += smaller; + } + else { + vert.xmax -= smaller; + } + + CLAMP_MAX(vert.ymin, vert.ymax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); + CLAMP_MAX(hor.xmin, hor.xmax - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); + + /* store in scrollers, used for drawing */ + r_scrollers->vert = vert; + r_scrollers->hor = hor; + + /* scroller 'buttons': + * - These should always remain within the visible region of the scrollbar + * - They represent the region of 'tot' that is visible in 'cur' + */ + + /* horizontal scrollers */ + if (scroll & V2D_SCROLL_HORIZONTAL) { + /* scroller 'button' extents */ + totsize = BLI_rctf_size_x(&v2d->tot); + scrollsize = (float)BLI_rcti_size_x(&hor); + if (totsize == 0.0f) { + totsize = 1.0f; /* avoid divide by zero */ + } + + fac1 = (v2d->cur.xmin - v2d->tot.xmin) / totsize; + if (fac1 <= 0.0f) { + r_scrollers->hor_min = hor.xmin; + } + else { + r_scrollers->hor_min = (int)(hor.xmin + (fac1 * scrollsize)); + } + + fac2 = (v2d->cur.xmax - v2d->tot.xmin) / totsize; + if (fac2 >= 1.0f) { + r_scrollers->hor_max = hor.xmax; + } + else { + r_scrollers->hor_max = (int)(hor.xmin + (fac2 * scrollsize)); + } + + /* prevent inverted sliders */ + if (r_scrollers->hor_min > r_scrollers->hor_max) { + r_scrollers->hor_min = r_scrollers->hor_max; + } + /* prevent sliders from being too small to grab */ + if ((r_scrollers->hor_max - r_scrollers->hor_min) < V2D_SCROLL_THUMB_SIZE_MIN) { + r_scrollers->hor_max = r_scrollers->hor_min + V2D_SCROLL_THUMB_SIZE_MIN; + + CLAMP(r_scrollers->hor_max, hor.xmin + V2D_SCROLL_THUMB_SIZE_MIN, hor.xmax); + CLAMP(r_scrollers->hor_min, hor.xmin, hor.xmax - V2D_SCROLL_THUMB_SIZE_MIN); + } + } + + /* vertical scrollers */ + if (scroll & V2D_SCROLL_VERTICAL) { + /* scroller 'button' extents */ + totsize = BLI_rctf_size_y(&v2d->tot); + scrollsize = (float)BLI_rcti_size_y(&vert); + if (totsize == 0.0f) { + totsize = 1.0f; /* avoid divide by zero */ + } + + fac1 = (v2d->cur.ymin - v2d->tot.ymin) / totsize; + if (fac1 <= 0.0f) { + r_scrollers->vert_min = vert.ymin; + } + else { + r_scrollers->vert_min = (int)(vert.ymin + (fac1 * scrollsize)); + } + + fac2 = (v2d->cur.ymax - v2d->tot.ymin) / totsize; + if (fac2 >= 1.0f) { + r_scrollers->vert_max = vert.ymax; + } + else { + r_scrollers->vert_max = (int)(vert.ymin + (fac2 * scrollsize)); + } + + /* prevent inverted sliders */ + if (r_scrollers->vert_min > r_scrollers->vert_max) { + r_scrollers->vert_min = r_scrollers->vert_max; + } + /* prevent sliders from being too small to grab */ + if ((r_scrollers->vert_max - r_scrollers->vert_min) < V2D_SCROLL_THUMB_SIZE_MIN) { + r_scrollers->vert_max = r_scrollers->vert_min + V2D_SCROLL_THUMB_SIZE_MIN; + + CLAMP(r_scrollers->vert_max, vert.ymin + V2D_SCROLL_THUMB_SIZE_MIN, vert.ymax); + CLAMP(r_scrollers->vert_min, vert.ymin, vert.ymax - V2D_SCROLL_THUMB_SIZE_MIN); + } + } +} + +void UI_view2d_scrollers_draw(View2D *v2d, const rcti *mask_custom) +{ + View2DScrollers scrollers; + UI_view2d_scrollers_calc(v2d, mask_custom, &scrollers); + bTheme *btheme = UI_GetTheme(); + rcti vert, hor; + const int scroll = view2d_scroll_mapped(v2d->scroll); + const char emboss_alpha = btheme->tui.widget_emboss[3]; + uchar scrollers_back_color[4]; + + /* Color for scrollbar backs */ + UI_GetThemeColor4ubv(TH_BACK, scrollers_back_color); + + /* make copies of rects for less typing */ + vert = scrollers.vert; + hor = scrollers.hor; + + /* horizontal scrollbar */ + if (scroll & V2D_SCROLL_HORIZONTAL) { + uiWidgetColors wcol = btheme->tui.wcol_scroll; + const float alpha_fac = v2d->alpha_hor / 255.0f; + rcti slider; + int state; + + slider.xmin = scrollers.hor_min; + slider.xmax = scrollers.hor_max; + slider.ymin = hor.ymin; + slider.ymax = hor.ymax; + + state = (v2d->scroll_ui & V2D_SCROLL_H_ACTIVE) ? UI_SCROLL_PRESSED : 0; + + wcol.inner[3] *= alpha_fac; + wcol.item[3] *= alpha_fac; + wcol.outline[3] *= alpha_fac; + btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */ + + /* show zoom handles if: + * - zooming on x-axis is allowed (no scroll otherwise) + * - slider bubble is large enough (no overdraw confusion) + * - scale is shown on the scroller + * (workaround to make sure that button windows don't show these, + * and only the time-grids with their zoom-ability can do so). + */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) && + (BLI_rcti_size_x(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) { + state |= UI_SCROLL_ARROWS; + } + + UI_draw_widget_scroll(&wcol, &hor, &slider, state); + } + + /* vertical scrollbar */ + if (scroll & V2D_SCROLL_VERTICAL) { + uiWidgetColors wcol = btheme->tui.wcol_scroll; + rcti slider; + const float alpha_fac = v2d->alpha_vert / 255.0f; + int state; + + slider.xmin = vert.xmin; + slider.xmax = vert.xmax; + slider.ymin = scrollers.vert_min; + slider.ymax = scrollers.vert_max; + + state = (v2d->scroll_ui & V2D_SCROLL_V_ACTIVE) ? UI_SCROLL_PRESSED : 0; + + wcol.inner[3] *= alpha_fac; + wcol.item[3] *= alpha_fac; + wcol.outline[3] *= alpha_fac; + btheme->tui.widget_emboss[3] *= alpha_fac; /* will be reset later */ + + /* show zoom handles if: + * - zooming on y-axis is allowed (no scroll otherwise) + * - slider bubble is large enough (no overdraw confusion) + * - scale is shown on the scroller + * (workaround to make sure that button windows don't show these, + * and only the time-grids with their zoomability can do so) + */ + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) && + (BLI_rcti_size_y(&slider) > V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) { + state |= UI_SCROLL_ARROWS; + } + + UI_draw_widget_scroll(&wcol, &vert, &slider, state); + } + + /* Was changed above, so reset. */ + btheme->tui.widget_emboss[3] = emboss_alpha; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name List View Utilities + * \{ */ + +void UI_view2d_listview_view_to_cell(float columnwidth, + float rowheight, + float startx, + float starty, + float viewx, + float viewy, + int *r_column, + int *r_row) +{ + if (r_column) { + if (columnwidth > 0) { + /* Columns go from left to right (x increases). */ + *r_column = floorf((viewx - startx) / columnwidth); + } + else { + *r_column = 0; + } + } + + if (r_row) { + if (rowheight > 0) { + /* Rows got from top to bottom (y decreases). */ + *r_row = floorf((starty - viewy) / rowheight); + } + else { + *r_row = 0; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Coordinate Conversions + * \{ */ + +float UI_view2d_region_to_view_x(const struct View2D *v2d, float x) +{ + return (v2d->cur.xmin + + (BLI_rctf_size_x(&v2d->cur) * (x - v2d->mask.xmin) / BLI_rcti_size_x(&v2d->mask))); +} +float UI_view2d_region_to_view_y(const struct View2D *v2d, float y) +{ + return (v2d->cur.ymin + + (BLI_rctf_size_y(&v2d->cur) * (y - v2d->mask.ymin) / BLI_rcti_size_y(&v2d->mask))); +} + +void UI_view2d_region_to_view( + const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) +{ + *r_view_x = UI_view2d_region_to_view_x(v2d, x); + *r_view_y = UI_view2d_region_to_view_y(v2d, y); +} + +void UI_view2d_region_to_view_rctf(const View2D *v2d, const rctf *rect_src, rctf *rect_dst) +{ + const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; + const float mask_size[2] = {(float)BLI_rcti_size_x(&v2d->mask), + (float)BLI_rcti_size_y(&v2d->mask)}; + + rect_dst->xmin = (v2d->cur.xmin + + (cur_size[0] * (rect_src->xmin - v2d->mask.xmin) / mask_size[0])); + rect_dst->xmax = (v2d->cur.xmin + + (cur_size[0] * (rect_src->xmax - v2d->mask.xmin) / mask_size[0])); + rect_dst->ymin = (v2d->cur.ymin + + (cur_size[1] * (rect_src->ymin - v2d->mask.ymin) / mask_size[1])); + rect_dst->ymax = (v2d->cur.ymin + + (cur_size[1] * (rect_src->ymax - v2d->mask.ymin) / mask_size[1])); +} + +float UI_view2d_view_to_region_x(const View2D *v2d, float x) +{ + return (v2d->mask.xmin + + (((x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur)) * BLI_rcti_size_x(&v2d->mask))); +} +float UI_view2d_view_to_region_y(const View2D *v2d, float y) +{ + return (v2d->mask.ymin + + (((y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur)) * BLI_rcti_size_y(&v2d->mask))); +} + +bool UI_view2d_view_to_region_clip( + const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) +{ + /* express given coordinates as proportional values */ + x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); + y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); + + /* check if values are within bounds */ + if ((x >= 0.0f) && (x <= 1.0f) && (y >= 0.0f) && (y <= 1.0f)) { + *r_region_x = (int)(v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask))); + *r_region_y = (int)(v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask))); + + return true; + } + + /* set initial value in case coordinate lies outside of bounds */ + *r_region_x = *r_region_y = V2D_IS_CLIPPED; + + return false; +} + +void UI_view2d_view_to_region( + const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y) +{ + /* Step 1: express given coordinates as proportional values. */ + x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); + y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); + + /* Step 2: convert proportional distances to screen coordinates. */ + x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)); + y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); + + /* Although we don't clamp to lie within region bounds, we must avoid exceeding size of ints. */ + *r_region_x = clamp_float_to_int(x); + *r_region_y = clamp_float_to_int(y); +} + +void UI_view2d_view_to_region_fl( + const View2D *v2d, float x, float y, float *r_region_x, float *r_region_y) +{ + /* express given coordinates as proportional values */ + x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); + y = (y - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); + + /* convert proportional distances to screen coordinates */ + *r_region_x = v2d->mask.xmin + (x * BLI_rcti_size_x(&v2d->mask)); + *r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask)); +} + +void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) +{ + const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; + const float mask_size[2] = {(float)BLI_rcti_size_x(&v2d->mask), + (float)BLI_rcti_size_y(&v2d->mask)}; + rctf rect_tmp; + + /* Step 1: express given coordinates as proportional values. */ + rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0]; + rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0]; + rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1]; + rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1]; + + /* Step 2: convert proportional distances to screen coordinates. */ + rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]); + rect_tmp.xmax = v2d->mask.xmin + (rect_tmp.xmax * mask_size[0]); + rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]); + rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]); + + clamp_rctf_to_rcti(rect_dst, &rect_tmp); +} + +void UI_view2d_view_to_region_m4(const View2D *v2d, float matrix[4][4]) +{ + rctf mask; + unit_m4(matrix); + BLI_rctf_rcti_copy(&mask, &v2d->mask); + BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &mask, matrix); +} + +bool UI_view2d_view_to_region_rcti_clip(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) +{ + const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)}; + const float mask_size[2] = {(float)BLI_rcti_size_x(&v2d->mask), + (float)BLI_rcti_size_y(&v2d->mask)}; + rctf rect_tmp; + + BLI_assert(rect_src->xmin <= rect_src->xmax && rect_src->ymin <= rect_src->ymax); + + /* Step 1: express given coordinates as proportional values. */ + rect_tmp.xmin = (rect_src->xmin - v2d->cur.xmin) / cur_size[0]; + rect_tmp.xmax = (rect_src->xmax - v2d->cur.xmin) / cur_size[0]; + rect_tmp.ymin = (rect_src->ymin - v2d->cur.ymin) / cur_size[1]; + rect_tmp.ymax = (rect_src->ymax - v2d->cur.ymin) / cur_size[1]; + + if (((rect_tmp.xmax < 0.0f) || (rect_tmp.xmin > 1.0f) || (rect_tmp.ymax < 0.0f) || + (rect_tmp.ymin > 1.0f)) == 0) { + /* Step 2: convert proportional distances to screen coordinates. */ + rect_tmp.xmin = v2d->mask.xmin + (rect_tmp.xmin * mask_size[0]); + rect_tmp.xmax = v2d->mask.ymin + (rect_tmp.xmax * mask_size[0]); + rect_tmp.ymin = v2d->mask.ymin + (rect_tmp.ymin * mask_size[1]); + rect_tmp.ymax = v2d->mask.ymin + (rect_tmp.ymax * mask_size[1]); + + clamp_rctf_to_rcti(rect_dst, &rect_tmp); + + return true; + } + + rect_dst->xmin = rect_dst->xmax = rect_dst->ymin = rect_dst->ymax = V2D_IS_CLIPPED; + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +View2D *UI_view2d_fromcontext(const bContext *C) +{ + ScrArea *area = CTX_wm_area(C); + ARegion *region = CTX_wm_region(C); + + if (area == nullptr) { + return nullptr; + } + if (region == nullptr) { + return nullptr; + } + return &(region->v2d); +} + +View2D *UI_view2d_fromcontext_rwin(const bContext *C) +{ + ScrArea *area = CTX_wm_area(C); + ARegion *region = CTX_wm_region(C); + + if (area == nullptr) { + return nullptr; + } + if (region == nullptr) { + return nullptr; + } + if (region->regiontype != RGN_TYPE_WINDOW) { + ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + return region_win ? &(region_win->v2d) : nullptr; + } + return &(region->v2d); +} + +void UI_view2d_scroller_size_get(const View2D *v2d, float *r_x, float *r_y) +{ + const int scroll = view2d_scroll_mapped(v2d->scroll); + + if (r_x) { + if (scroll & V2D_SCROLL_VERTICAL) { + *r_x = (scroll & V2D_SCROLL_VERTICAL_HANDLES) ? V2D_SCROLL_HANDLE_WIDTH : V2D_SCROLL_WIDTH; + } + else { + *r_x = 0; + } + } + if (r_y) { + if (scroll & V2D_SCROLL_HORIZONTAL) { + *r_y = (scroll & V2D_SCROLL_HORIZONTAL_HANDLES) ? V2D_SCROLL_HANDLE_HEIGHT : + V2D_SCROLL_HEIGHT; + } + else { + *r_y = 0; + } + } +} + +void UI_view2d_scale_get(const View2D *v2d, float *r_x, float *r_y) +{ + if (r_x) { + *r_x = UI_view2d_scale_get_x(v2d); + } + if (r_y) { + *r_y = UI_view2d_scale_get_y(v2d); + } +} +float UI_view2d_scale_get_x(const View2D *v2d) +{ + return BLI_rcti_size_x(&v2d->mask) / BLI_rctf_size_x(&v2d->cur); +} +float UI_view2d_scale_get_y(const View2D *v2d) +{ + return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur); +} +void UI_view2d_scale_get_inverse(const View2D *v2d, float *r_x, float *r_y) +{ + if (r_x) { + *r_x = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask); + } + if (r_y) { + *r_y = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask); + } +} + +void UI_view2d_center_get(const struct View2D *v2d, float *r_x, float *r_y) +{ + /* get center */ + if (r_x) { + *r_x = BLI_rctf_cent_x(&v2d->cur); + } + if (r_y) { + *r_y = BLI_rctf_cent_y(&v2d->cur); + } +} +void UI_view2d_center_set(struct View2D *v2d, float x, float y) +{ + BLI_rctf_recenter(&v2d->cur, x, y); + + /* make sure that 'cur' rect is in a valid state as a result of these changes */ + UI_view2d_curRect_validate(v2d); +} + +void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac) +{ + if (xfac != -1.0f) { + const float xsize = BLI_rctf_size_x(&v2d->cur); + const float xmin = v2d->tot.xmin; + const float xmax = v2d->tot.xmax - xsize; + + v2d->cur.xmin = (xmin * (1.0f - xfac)) + (xmax * xfac); + v2d->cur.xmax = v2d->cur.xmin + xsize; + } + + if (yfac != -1.0f) { + const float ysize = BLI_rctf_size_y(&v2d->cur); + const float ymin = v2d->tot.ymin; + const float ymax = v2d->tot.ymax - ysize; + + v2d->cur.ymin = (ymin * (1.0f - yfac)) + (ymax * yfac); + v2d->cur.ymax = v2d->cur.ymin + ysize; + } + + UI_view2d_curRect_validate(v2d); +} + +char UI_view2d_mouse_in_scrollers_ex(const ARegion *region, + const View2D *v2d, + const int xy[2], + int *r_scroll) +{ + const int scroll = view2d_scroll_mapped(v2d->scroll); + *r_scroll = scroll; + + if (scroll) { + /* Move to region-coordinates. */ + const int co[2] = { + xy[0] - region->winrct.xmin, + xy[1] - region->winrct.ymin, + }; + if (scroll & V2D_SCROLL_HORIZONTAL) { + if (IN_2D_HORIZ_SCROLL(v2d, co)) { + return 'h'; + } + } + if (scroll & V2D_SCROLL_VERTICAL) { + if (IN_2D_VERT_SCROLL(v2d, co)) { + return 'v'; + } + } + } + + return 0; +} + +char UI_view2d_rect_in_scrollers_ex(const ARegion *region, + const View2D *v2d, + const rcti *rect, + int *r_scroll) +{ + const int scroll = view2d_scroll_mapped(v2d->scroll); + *r_scroll = scroll; + + if (scroll) { + /* Move to region-coordinates. */ + rcti rect_region = *rect; + BLI_rcti_translate(&rect_region, -region->winrct.xmin, region->winrct.ymin); + if (scroll & V2D_SCROLL_HORIZONTAL) { + if (IN_2D_HORIZ_SCROLL_RECT(v2d, &rect_region)) { + return 'h'; + } + } + if (scroll & V2D_SCROLL_VERTICAL) { + if (IN_2D_VERT_SCROLL_RECT(v2d, &rect_region)) { + return 'v'; + } + } + } + + return 0; +} + +char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, const int xy[2]) +{ + int scroll_dummy = 0; + return UI_view2d_mouse_in_scrollers_ex(region, v2d, xy, &scroll_dummy); +} + +char UI_view2d_rect_in_scrollers(const ARegion *region, const View2D *v2d, const rcti *rect) +{ + int scroll_dummy = 0; + return UI_view2d_rect_in_scrollers_ex(region, v2d, rect, &scroll_dummy); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View2D Text Drawing Cache + * \{ */ + +struct View2DString { + struct View2DString *next; + union { + uchar ub[4]; + int pack; + } col; + rcti rect; + int mval[2]; + + /* str is allocated past the end */ + char str[0]; +}; + +/* assumes caches are used correctly, so for time being no local storage in v2d */ +static MemArena *g_v2d_strings_arena = nullptr; +static View2DString *g_v2d_strings = nullptr; + +void UI_view2d_text_cache_add( + View2D *v2d, float x, float y, const char *str, size_t str_len, const uchar col[4]) +{ + int mval[2]; + + BLI_assert(str_len == strlen(str)); + + if (UI_view2d_view_to_region_clip(v2d, x, y, &mval[0], &mval[1])) { + const int alloc_len = str_len + 1; + + if (g_v2d_strings_arena == nullptr) { + g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__); + } + + View2DString *v2s = static_cast( + BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len)); + + BLI_LINKS_PREPEND(g_v2d_strings, v2s); + + v2s->col.pack = *((const int *)col); + + memset(&v2s->rect, 0, sizeof(v2s->rect)); + + v2s->mval[0] = mval[0]; + v2s->mval[1] = mval[1]; + + memcpy(v2s->str, str, alloc_len); + } +} + +void UI_view2d_text_cache_add_rectf( + View2D *v2d, const rctf *rect_view, const char *str, size_t str_len, const uchar col[4]) +{ + rcti rect; + + BLI_assert(str_len == strlen(str)); + + if (UI_view2d_view_to_region_rcti_clip(v2d, rect_view, &rect)) { + const int alloc_len = str_len + 1; + + if (g_v2d_strings_arena == nullptr) { + g_v2d_strings_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 14), __func__); + } + + View2DString *v2s = static_cast( + BLI_memarena_alloc(g_v2d_strings_arena, sizeof(View2DString) + alloc_len)); + + BLI_LINKS_PREPEND(g_v2d_strings, v2s); + + v2s->col.pack = *((const int *)col); + + v2s->rect = rect; + + v2s->mval[0] = v2s->rect.xmin; + v2s->mval[1] = v2s->rect.ymin; + + memcpy(v2s->str, str, alloc_len); + } +} + +void UI_view2d_text_cache_draw(ARegion *region) +{ + View2DString *v2s; + int col_pack_prev = 0; + + /* investigate using BLF_ascender() */ + const int font_id = BLF_default(); + + BLF_set_default(); + const float default_height = g_v2d_strings ? BLF_height(font_id, "28", 3) : 0.0f; + + wmOrtho2_region_pixelspace(region); + + for (v2s = g_v2d_strings; v2s; v2s = v2s->next) { + int xofs = 0, yofs; + + yofs = ceil(0.5f * (BLI_rcti_size_y(&v2s->rect) - default_height)); + if (yofs < 1) { + yofs = 1; + } + + if (col_pack_prev != v2s->col.pack) { + BLF_color3ubv(font_id, v2s->col.ub); + col_pack_prev = v2s->col.pack; + } + + if (v2s->rect.xmin >= v2s->rect.xmax) { + BLF_draw_default((float)(v2s->mval[0] + xofs), + (float)(v2s->mval[1] + yofs), + 0.0, + v2s->str, + BLF_DRAW_STR_DUMMY_MAX); + } + else { + BLF_enable(font_id, BLF_CLIPPING); + BLF_clipping( + font_id, v2s->rect.xmin - 4, v2s->rect.ymin - 4, v2s->rect.xmax + 4, v2s->rect.ymax + 4); + BLF_draw_default( + v2s->rect.xmin + xofs, v2s->rect.ymin + yofs, 0.0f, v2s->str, BLF_DRAW_STR_DUMMY_MAX); + BLF_disable(font_id, BLF_CLIPPING); + } + } + g_v2d_strings = nullptr; + + if (g_v2d_strings_arena) { + BLI_memarena_free(g_v2d_strings_arena); + g_v2d_strings_arena = nullptr; + } +} + +/** \} */ diff --git a/source/blender/editors/interface/view2d_draw.c b/source/blender/editors/interface/view2d_draw.c deleted file mode 100644 index 600aa3b522b..00000000000 --- a/source/blender/editors/interface/view2d_draw.c +++ /dev/null @@ -1,600 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_scene_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_array.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_timecode.h" -#include "BLI_utildefines.h" - -#include "GPU_immediate.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "WM_api.h" - -#include "BLF_api.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "interface_intern.h" - -/* Compute display grid resolution - ********************************************************/ - -#define MIN_MAJOR_LINE_DISTANCE (U.v2d_min_gridsize * UI_DPI_FAC) - -static float select_major_distance(const float *possible_distances, - uint amount, - float pixel_width, - float view_width) -{ - BLI_assert(amount >= 1); - - if (IS_EQF(view_width, 0.0f)) { - return possible_distances[0]; - } - - const float pixels_per_view_unit = pixel_width / view_width; - - for (uint i = 0; i < amount; i++) { - const float distance = possible_distances[i]; - if (pixels_per_view_unit * distance >= MIN_MAJOR_LINE_DISTANCE) { - return distance; - } - } - return possible_distances[amount - 1]; -} - -static const float discrete_value_scales[] = { - 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000}; - -static const float continuous_value_scales[] = {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, - 5, 10, 20, 50, 100, 200, 500, 1000, - 2000, 5000, 10000, 20000, 50000, 100000}; - -static uint view2d_major_step_x__discrete(const View2D *v2d) -{ - return select_major_distance(discrete_value_scales, - ARRAY_SIZE(discrete_value_scales), - BLI_rcti_size_x(&v2d->mask), - BLI_rctf_size_x(&v2d->cur)); -} - -static float view2d_major_step_x__continuous(const View2D *v2d) -{ - return select_major_distance(continuous_value_scales, - ARRAY_SIZE(continuous_value_scales), - BLI_rcti_size_x(&v2d->mask), - BLI_rctf_size_x(&v2d->cur)); -} - -static float view2d_major_step_y__continuous(const View2D *v2d) -{ - return select_major_distance(continuous_value_scales, - ARRAY_SIZE(continuous_value_scales), - BLI_rcti_size_y(&v2d->mask), - BLI_rctf_size_y(&v2d->cur)); -} - -static float view2d_major_step_x__time(const View2D *v2d, const Scene *scene) -{ - const double fps = FPS; - - float *possible_distances = NULL; - BLI_array_staticdeclare(possible_distances, 32); - - for (uint step = 1; step < fps; step *= 2) { - BLI_array_append(possible_distances, step); - } - BLI_array_append(possible_distances, fps); - BLI_array_append(possible_distances, 2 * fps); - BLI_array_append(possible_distances, 5 * fps); - BLI_array_append(possible_distances, 10 * fps); - BLI_array_append(possible_distances, 30 * fps); - BLI_array_append(possible_distances, 60 * fps); - BLI_array_append(possible_distances, 2 * 60 * fps); - BLI_array_append(possible_distances, 5 * 60 * fps); - BLI_array_append(possible_distances, 10 * 60 * fps); - BLI_array_append(possible_distances, 30 * 60 * fps); - BLI_array_append(possible_distances, 60 * 60 * fps); - - float distance = select_major_distance(possible_distances, - BLI_array_len(possible_distances), - BLI_rcti_size_x(&v2d->mask), - BLI_rctf_size_x(&v2d->cur)); - - BLI_array_free(possible_distances); - return distance; -} - -/* Draw parallel lines - ************************************/ - -typedef struct ParallelLinesSet { - float offset; - float distance; -} ParallelLinesSet; - -static void get_parallel_lines_draw_steps(const ParallelLinesSet *lines, - float region_start, - float region_end, - float *r_first, - uint *r_steps) -{ - if (region_start >= region_end) { - *r_first = 0; - *r_steps = 0; - return; - } - - BLI_assert(lines->distance > 0); - BLI_assert(region_start <= region_end); - - *r_first = ceilf((region_start - lines->offset) / lines->distance) * lines->distance + - lines->offset; - - if (region_start <= *r_first && region_end >= *r_first) { - *r_steps = MAX2(0, floorf((region_end - *r_first) / lines->distance)) + 1; - } - else { - *r_steps = 0; - } -} - -/** - * \param rect_mask: Region size in pixels. - */ -static void draw_parallel_lines(const ParallelLinesSet *lines, - const rctf *rect, - const rcti *rect_mask, - const uchar color[3], - char direction) -{ - float first; - uint steps, steps_max; - - if (direction == 'v') { - get_parallel_lines_draw_steps(lines, rect->xmin, rect->xmax, &first, &steps); - steps_max = BLI_rcti_size_x(rect_mask); - } - else { - BLI_assert(direction == 'h'); - get_parallel_lines_draw_steps(lines, rect->ymin, rect->ymax, &first, &steps); - steps_max = BLI_rcti_size_y(rect_mask); - } - - if (steps == 0) { - return; - } - - if (UNLIKELY(steps >= steps_max)) { - /* Note that we could draw a solid color, - * however this flickers because of numeric instability when zoomed out. */ - return; - } - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - if (U.pixelsize > 1.0f) { - float viewport[4]; - GPU_viewport_size_get_f(viewport); - - immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); - immUniform2fv("viewportSize", &viewport[2]); - /* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing. - * TODO: make a variant or a uniform to toggle it off. */ - immUniform1f("lineWidth", U.pixelsize - 1.0f); - } - else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - } - immUniformColor3ubv(color); - immBegin(GPU_PRIM_LINES, steps * 2); - - if (direction == 'v') { - for (uint i = 0; i < steps; i++) { - const float xpos = first + i * lines->distance; - immVertex2f(pos, xpos, rect->ymin); - immVertex2f(pos, xpos, rect->ymax); - } - } - else { - for (uint i = 0; i < steps; i++) { - const float ypos = first + i * lines->distance; - immVertex2f(pos, rect->xmin, ypos); - immVertex2f(pos, rect->xmax, ypos); - } - } - - immEnd(); - immUnbindProgram(); -} - -static void view2d_draw_lines_internal(const View2D *v2d, - const ParallelLinesSet *lines, - const uchar color[3], - char direction) -{ - GPU_matrix_push_projection(); - UI_view2d_view_ortho(v2d); - draw_parallel_lines(lines, &v2d->cur, &v2d->mask, color, direction); - GPU_matrix_pop_projection(); -} - -static void view2d_draw_lines(const View2D *v2d, - float major_distance, - bool display_minor_lines, - char direction) -{ - { - uchar major_color[3]; - UI_GetThemeColor3ubv(TH_GRID, major_color); - ParallelLinesSet major_lines; - major_lines.distance = major_distance; - major_lines.offset = 0; - view2d_draw_lines_internal(v2d, &major_lines, major_color, direction); - } - - if (display_minor_lines) { - uchar minor_color[3]; - UI_GetThemeColorShade3ubv(TH_GRID, 16, minor_color); - ParallelLinesSet minor_lines; - minor_lines.distance = major_distance; - minor_lines.offset = major_distance / 2.0f; - view2d_draw_lines_internal(v2d, &minor_lines, minor_color, direction); - } -} - -/* Scale indicator text drawing - **************************************************/ - -typedef void (*PositionToString)( - void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str); - -static void draw_horizontal_scale_indicators(const ARegion *region, - const View2D *v2d, - float distance, - const rcti *rect, - PositionToString to_string, - void *to_string_data, - int colorid) -{ - if (UI_view2d_scale_get_x(v2d) <= 0.0f) { - return; - } - - float start; - uint steps; - { - ParallelLinesSet lines; - lines.distance = distance; - lines.offset = 0; - get_parallel_lines_draw_steps(&lines, - UI_view2d_region_to_view_x(v2d, rect->xmin), - UI_view2d_region_to_view_x(v2d, rect->xmax), - &start, - &steps); - const uint steps_max = BLI_rcti_size_x(&v2d->mask); - if (UNLIKELY(steps >= steps_max)) { - return; - } - } - - GPU_matrix_push_projection(); - wmOrtho2_region_pixelspace(region); - - const int font_id = BLF_default(); - UI_FontThemeColor(font_id, colorid); - - BLF_batch_draw_begin(); - - const float ypos = rect->ymin + 4 * UI_DPI_FAC; - const float xmin = rect->xmin; - const float xmax = rect->xmax; - - char text[32]; - - /* Calculate max_label_count and draw_frequency based on largest visible label. */ - int draw_frequency; - { - to_string(to_string_data, start, 0, sizeof(text), text); - const float left_text_width = BLF_width(font_id, text, strlen(text)); - to_string(to_string_data, start + steps * distance, 0, sizeof(text), text); - const float right_text_width = BLF_width(font_id, text, strlen(text)); - const float max_text_width = max_ff(left_text_width, right_text_width); - const float max_label_count = BLI_rcti_size_x(&v2d->mask) / (max_text_width + 10.0f); - draw_frequency = ceil((float)steps / max_label_count); - } - - if (draw_frequency != 0) { - const int start_index = abs((int)(start / distance)) % draw_frequency; - for (uint i = start_index; i < steps; i += draw_frequency) { - const float xpos_view = start + i * distance; - const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view); - to_string(to_string_data, xpos_view, distance, sizeof(text), text); - const float text_width = BLF_width(font_id, text, strlen(text)); - - if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) { - BLF_draw_default(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text)); - } - } - } - - BLF_batch_draw_end(); - GPU_matrix_pop_projection(); -} - -static void draw_vertical_scale_indicators(const ARegion *region, - const View2D *v2d, - float distance, - float display_offset, - const rcti *rect, - PositionToString to_string, - void *to_string_data, - int colorid) -{ - if (UI_view2d_scale_get_y(v2d) <= 0.0f) { - return; - } - - float start; - uint steps; - { - ParallelLinesSet lines; - lines.distance = distance; - lines.offset = 0; - get_parallel_lines_draw_steps(&lines, - UI_view2d_region_to_view_y(v2d, rect->ymin), - UI_view2d_region_to_view_y(v2d, rect->ymax), - &start, - &steps); - const uint steps_max = BLI_rcti_size_y(&v2d->mask); - if (UNLIKELY(steps >= steps_max)) { - return; - } - } - - GPU_matrix_push_projection(); - wmOrtho2_region_pixelspace(region); - - const int font_id = BLF_default(); - UI_FontThemeColor(font_id, colorid); - - BLF_batch_draw_begin(); - - BLF_enable(font_id, BLF_SHADOW); - const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; - BLF_shadow(font_id, 5, shadow_color); - BLF_shadow_offset(font_id, 1, -1); - - const float x_offset = 8.0f; - const float xpos = (rect->xmin + x_offset) * UI_DPI_FAC; - const float ymin = rect->ymin; - const float ymax = rect->ymax; - const float y_offset = (BLF_height(font_id, "0", 1) / 2.0f) - U.pixelsize; - - for (uint i = 0; i < steps; i++) { - const float ypos_view = start + i * distance; - const float ypos_region = UI_view2d_view_to_region_y(v2d, ypos_view + display_offset); - char text[32]; - to_string(to_string_data, ypos_view, distance, sizeof(text), text); - - if (ypos_region - y_offset >= ymin && ypos_region + y_offset <= ymax) { - BLF_draw_default(xpos, ypos_region - y_offset, 0.0f, text, sizeof(text)); - } - } - - BLF_disable(font_id, BLF_SHADOW); - - BLF_batch_draw_end(); - - GPU_matrix_pop_projection(); -} - -static void view_to_string__frame_number( - void *UNUSED(user_data), float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str) -{ - BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos); -} - -static void view_to_string__time( - void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str) -{ - const Scene *scene = (const Scene *)user_data; - - int brevity_level = 0; - if (U.timecode_style == USER_TIMECODE_MINIMAL && v2d_step >= FPS) { - brevity_level = 1; - } - - BLI_timecode_string_from_time( - r_str, max_len, brevity_level, v2d_pos / (float)FPS, FPS, U.timecode_style); -} - -static void view_to_string__value( - void *UNUSED(user_data), float v2d_pos, float v2d_step, uint max_len, char *r_str) -{ - if (v2d_step >= 1.0f) { - BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos); - } - else if (v2d_step >= 0.1f) { - BLI_snprintf(r_str, max_len, "%.1f", v2d_pos); - } - else if (v2d_step >= 0.01f) { - BLI_snprintf(r_str, max_len, "%.2f", v2d_pos); - } - else { - BLI_snprintf(r_str, max_len, "%.3f", v2d_pos); - } -} - -/* Grid Resolution API - **************************************************/ - -float UI_view2d_grid_resolution_x__frames_or_seconds(const struct View2D *v2d, - const struct Scene *scene, - bool display_seconds) -{ - if (display_seconds) { - return view2d_major_step_x__time(v2d, scene); - } - return view2d_major_step_x__continuous(v2d); -} - -float UI_view2d_grid_resolution_y__values(const struct View2D *v2d) -{ - return view2d_major_step_y__continuous(v2d); -} - -/* Line Drawing API - **************************************************/ - -void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d, bool display_minor_lines) -{ - const uint major_line_distance = view2d_major_step_x__discrete(v2d); - view2d_draw_lines( - v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); -} - -void UI_view2d_draw_lines_x__values(const View2D *v2d) -{ - const float major_line_distance = view2d_major_step_x__continuous(v2d); - view2d_draw_lines(v2d, major_line_distance, true, 'v'); -} - -void UI_view2d_draw_lines_y__values(const View2D *v2d) -{ - const float major_line_distance = view2d_major_step_y__continuous(v2d); - view2d_draw_lines(v2d, major_line_distance, true, 'h'); -} - -void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, - const Scene *scene, - bool display_minor_lines) -{ - const float major_line_distance = view2d_major_step_x__time(v2d, scene); - view2d_draw_lines( - v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); -} - -void UI_view2d_draw_lines_x__discrete_frames_or_seconds(const View2D *v2d, - const Scene *scene, - bool display_seconds, - bool display_minor_lines) -{ - if (display_seconds) { - UI_view2d_draw_lines_x__discrete_time(v2d, scene, display_minor_lines); - } - else { - UI_view2d_draw_lines_x__discrete_values(v2d, display_minor_lines); - } -} - -void UI_view2d_draw_lines_x__frames_or_seconds(const View2D *v2d, - const Scene *scene, - bool display_seconds) -{ - if (display_seconds) { - UI_view2d_draw_lines_x__discrete_time(v2d, scene, true); - } - else { - UI_view2d_draw_lines_x__values(v2d); - } -} - -/* Scale indicator text drawing API - **************************************************/ - -static void UI_view2d_draw_scale_x__discrete_values(const ARegion *region, - const View2D *v2d, - const rcti *rect, - int colorid) -{ - const float number_step = view2d_major_step_x__discrete(v2d); - draw_horizontal_scale_indicators( - region, v2d, number_step, rect, view_to_string__frame_number, NULL, colorid); -} - -static void UI_view2d_draw_scale_x__discrete_time( - const ARegion *region, const View2D *v2d, const rcti *rect, const Scene *scene, int colorid) -{ - const float step = view2d_major_step_x__time(v2d, scene); - draw_horizontal_scale_indicators( - region, v2d, step, rect, view_to_string__time, (void *)scene, colorid); -} - -static void UI_view2d_draw_scale_x__values(const ARegion *region, - const View2D *v2d, - const rcti *rect, - int colorid) -{ - const float step = view2d_major_step_x__continuous(v2d); - draw_horizontal_scale_indicators(region, v2d, step, rect, view_to_string__value, NULL, colorid); -} - -void UI_view2d_draw_scale_y__values(const ARegion *region, - const View2D *v2d, - const rcti *rect, - int colorid) -{ - const float step = view2d_major_step_y__continuous(v2d); - draw_vertical_scale_indicators( - region, v2d, step, 0.0f, rect, view_to_string__value, NULL, colorid); -} - -void UI_view2d_draw_scale_y__block(const ARegion *region, - const View2D *v2d, - const rcti *rect, - int colorid) -{ - draw_vertical_scale_indicators( - region, v2d, 1.0f, 0.5f, rect, view_to_string__value, NULL, colorid); -} - -void UI_view2d_draw_scale_x__discrete_frames_or_seconds(const struct ARegion *region, - const struct View2D *v2d, - const struct rcti *rect, - const struct Scene *scene, - bool display_seconds, - int colorid) -{ - if (display_seconds) { - UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid); - } - else { - UI_view2d_draw_scale_x__discrete_values(region, v2d, rect, colorid); - } -} - -void UI_view2d_draw_scale_x__frames_or_seconds(const struct ARegion *region, - const struct View2D *v2d, - const struct rcti *rect, - const struct Scene *scene, - bool display_seconds, - int colorid) -{ - if (display_seconds) { - UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid); - } - else { - UI_view2d_draw_scale_x__values(region, v2d, rect, colorid); - } -} diff --git a/source/blender/editors/interface/view2d_draw.cc b/source/blender/editors/interface/view2d_draw.cc new file mode 100644 index 00000000000..d76230ba99c --- /dev/null +++ b/source/blender/editors/interface/view2d_draw.cc @@ -0,0 +1,599 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_timecode.h" +#include "BLI_utildefines.h" +#include "BLI_vector.hh" + +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "WM_api.h" + +#include "BLF_api.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "interface_intern.h" + +/* Compute display grid resolution + ********************************************************/ + +#define MIN_MAJOR_LINE_DISTANCE (U.v2d_min_gridsize * UI_DPI_FAC) + +static float select_major_distance(const float *possible_distances, + uint amount, + float pixel_width, + float view_width) +{ + BLI_assert(amount >= 1); + + if (IS_EQF(view_width, 0.0f)) { + return possible_distances[0]; + } + + const float pixels_per_view_unit = pixel_width / view_width; + + for (uint i = 0; i < amount; i++) { + const float distance = possible_distances[i]; + if (pixels_per_view_unit * distance >= MIN_MAJOR_LINE_DISTANCE) { + return distance; + } + } + return possible_distances[amount - 1]; +} + +static const float discrete_value_scales[] = { + 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000}; + +static const float continuous_value_scales[] = {0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, + 5, 10, 20, 50, 100, 200, 500, 1000, + 2000, 5000, 10000, 20000, 50000, 100000}; + +static uint view2d_major_step_x__discrete(const View2D *v2d) +{ + return select_major_distance(discrete_value_scales, + ARRAY_SIZE(discrete_value_scales), + BLI_rcti_size_x(&v2d->mask), + BLI_rctf_size_x(&v2d->cur)); +} + +static float view2d_major_step_x__continuous(const View2D *v2d) +{ + return select_major_distance(continuous_value_scales, + ARRAY_SIZE(continuous_value_scales), + BLI_rcti_size_x(&v2d->mask), + BLI_rctf_size_x(&v2d->cur)); +} + +static float view2d_major_step_y__continuous(const View2D *v2d) +{ + return select_major_distance(continuous_value_scales, + ARRAY_SIZE(continuous_value_scales), + BLI_rcti_size_y(&v2d->mask), + BLI_rctf_size_y(&v2d->cur)); +} + +static float view2d_major_step_x__time(const View2D *v2d, const Scene *scene) +{ + const double fps = FPS; + + blender::Vector possible_distances; + + for (int step = 1; step < fps; step *= 2) { + possible_distances.append(step); + } + possible_distances.append(fps); + possible_distances.append(2 * fps); + possible_distances.append(5 * fps); + possible_distances.append(10 * fps); + possible_distances.append(30 * fps); + possible_distances.append(60 * fps); + possible_distances.append(2 * 60 * fps); + possible_distances.append(5 * 60 * fps); + possible_distances.append(10 * 60 * fps); + possible_distances.append(30 * 60 * fps); + possible_distances.append(60 * 60 * fps); + + float distance = select_major_distance(possible_distances.data(), + possible_distances.size(), + BLI_rcti_size_x(&v2d->mask), + BLI_rctf_size_x(&v2d->cur)); + + return distance; +} + +/* Draw parallel lines + ************************************/ + +struct ParallelLinesSet { + float offset; + float distance; +}; + +static void get_parallel_lines_draw_steps(const ParallelLinesSet *lines, + float region_start, + float region_end, + float *r_first, + uint *r_steps) +{ + if (region_start >= region_end) { + *r_first = 0; + *r_steps = 0; + return; + } + + BLI_assert(lines->distance > 0); + BLI_assert(region_start <= region_end); + + *r_first = ceilf((region_start - lines->offset) / lines->distance) * lines->distance + + lines->offset; + + if (region_start <= *r_first && region_end >= *r_first) { + *r_steps = MAX2(0, floorf((region_end - *r_first) / lines->distance)) + 1; + } + else { + *r_steps = 0; + } +} + +/** + * \param rect_mask: Region size in pixels. + */ +static void draw_parallel_lines(const ParallelLinesSet *lines, + const rctf *rect, + const rcti *rect_mask, + const uchar color[3], + char direction) +{ + float first; + uint steps, steps_max; + + if (direction == 'v') { + get_parallel_lines_draw_steps(lines, rect->xmin, rect->xmax, &first, &steps); + steps_max = BLI_rcti_size_x(rect_mask); + } + else { + BLI_assert(direction == 'h'); + get_parallel_lines_draw_steps(lines, rect->ymin, rect->ymax, &first, &steps); + steps_max = BLI_rcti_size_y(rect_mask); + } + + if (steps == 0) { + return; + } + + if (UNLIKELY(steps >= steps_max)) { + /* Note that we could draw a solid color, + * however this flickers because of numeric instability when zoomed out. */ + return; + } + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + if (U.pixelsize > 1.0f) { + float viewport[4]; + GPU_viewport_size_get_f(viewport); + + immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); + immUniform2fv("viewportSize", &viewport[2]); + /* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing. + * TODO: make a variant or a uniform to toggle it off. */ + immUniform1f("lineWidth", U.pixelsize - 1.0f); + } + else { + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + } + immUniformColor3ubv(color); + immBegin(GPU_PRIM_LINES, steps * 2); + + if (direction == 'v') { + for (uint i = 0; i < steps; i++) { + const float xpos = first + i * lines->distance; + immVertex2f(pos, xpos, rect->ymin); + immVertex2f(pos, xpos, rect->ymax); + } + } + else { + for (uint i = 0; i < steps; i++) { + const float ypos = first + i * lines->distance; + immVertex2f(pos, rect->xmin, ypos); + immVertex2f(pos, rect->xmax, ypos); + } + } + + immEnd(); + immUnbindProgram(); +} + +static void view2d_draw_lines_internal(const View2D *v2d, + const ParallelLinesSet *lines, + const uchar color[3], + char direction) +{ + GPU_matrix_push_projection(); + UI_view2d_view_ortho(v2d); + draw_parallel_lines(lines, &v2d->cur, &v2d->mask, color, direction); + GPU_matrix_pop_projection(); +} + +static void view2d_draw_lines(const View2D *v2d, + float major_distance, + bool display_minor_lines, + char direction) +{ + { + uchar major_color[3]; + UI_GetThemeColor3ubv(TH_GRID, major_color); + ParallelLinesSet major_lines; + major_lines.distance = major_distance; + major_lines.offset = 0; + view2d_draw_lines_internal(v2d, &major_lines, major_color, direction); + } + + if (display_minor_lines) { + uchar minor_color[3]; + UI_GetThemeColorShade3ubv(TH_GRID, 16, minor_color); + ParallelLinesSet minor_lines; + minor_lines.distance = major_distance; + minor_lines.offset = major_distance / 2.0f; + view2d_draw_lines_internal(v2d, &minor_lines, minor_color, direction); + } +} + +/* Scale indicator text drawing + **************************************************/ + +using PositionToString = + void (*)(void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str); + +static void draw_horizontal_scale_indicators(const ARegion *region, + const View2D *v2d, + float distance, + const rcti *rect, + PositionToString to_string, + void *to_string_data, + int colorid) +{ + if (UI_view2d_scale_get_x(v2d) <= 0.0f) { + return; + } + + float start; + uint steps; + { + ParallelLinesSet lines; + lines.distance = distance; + lines.offset = 0; + get_parallel_lines_draw_steps(&lines, + UI_view2d_region_to_view_x(v2d, rect->xmin), + UI_view2d_region_to_view_x(v2d, rect->xmax), + &start, + &steps); + const uint steps_max = BLI_rcti_size_x(&v2d->mask); + if (UNLIKELY(steps >= steps_max)) { + return; + } + } + + GPU_matrix_push_projection(); + wmOrtho2_region_pixelspace(region); + + const int font_id = BLF_default(); + UI_FontThemeColor(font_id, colorid); + + BLF_batch_draw_begin(); + + const float ypos = rect->ymin + 4 * UI_DPI_FAC; + const float xmin = rect->xmin; + const float xmax = rect->xmax; + + char text[32]; + + /* Calculate max_label_count and draw_frequency based on largest visible label. */ + int draw_frequency; + { + to_string(to_string_data, start, 0, sizeof(text), text); + const float left_text_width = BLF_width(font_id, text, strlen(text)); + to_string(to_string_data, start + steps * distance, 0, sizeof(text), text); + const float right_text_width = BLF_width(font_id, text, strlen(text)); + const float max_text_width = max_ff(left_text_width, right_text_width); + const float max_label_count = BLI_rcti_size_x(&v2d->mask) / (max_text_width + 10.0f); + draw_frequency = ceil((float)steps / max_label_count); + } + + if (draw_frequency != 0) { + const int start_index = abs((int)(start / distance)) % draw_frequency; + for (uint i = start_index; i < steps; i += draw_frequency) { + const float xpos_view = start + i * distance; + const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view); + to_string(to_string_data, xpos_view, distance, sizeof(text), text); + const float text_width = BLF_width(font_id, text, strlen(text)); + + if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) { + BLF_draw_default(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text)); + } + } + } + + BLF_batch_draw_end(); + GPU_matrix_pop_projection(); +} + +static void draw_vertical_scale_indicators(const ARegion *region, + const View2D *v2d, + float distance, + float display_offset, + const rcti *rect, + PositionToString to_string, + void *to_string_data, + int colorid) +{ + if (UI_view2d_scale_get_y(v2d) <= 0.0f) { + return; + } + + float start; + uint steps; + { + ParallelLinesSet lines; + lines.distance = distance; + lines.offset = 0; + get_parallel_lines_draw_steps(&lines, + UI_view2d_region_to_view_y(v2d, rect->ymin), + UI_view2d_region_to_view_y(v2d, rect->ymax), + &start, + &steps); + const uint steps_max = BLI_rcti_size_y(&v2d->mask); + if (UNLIKELY(steps >= steps_max)) { + return; + } + } + + GPU_matrix_push_projection(); + wmOrtho2_region_pixelspace(region); + + const int font_id = BLF_default(); + UI_FontThemeColor(font_id, colorid); + + BLF_batch_draw_begin(); + + BLF_enable(font_id, BLF_SHADOW); + const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + BLF_shadow(font_id, 5, shadow_color); + BLF_shadow_offset(font_id, 1, -1); + + const float x_offset = 8.0f; + const float xpos = (rect->xmin + x_offset) * UI_DPI_FAC; + const float ymin = rect->ymin; + const float ymax = rect->ymax; + const float y_offset = (BLF_height(font_id, "0", 1) / 2.0f) - U.pixelsize; + + for (uint i = 0; i < steps; i++) { + const float ypos_view = start + i * distance; + const float ypos_region = UI_view2d_view_to_region_y(v2d, ypos_view + display_offset); + char text[32]; + to_string(to_string_data, ypos_view, distance, sizeof(text), text); + + if (ypos_region - y_offset >= ymin && ypos_region + y_offset <= ymax) { + BLF_draw_default(xpos, ypos_region - y_offset, 0.0f, text, sizeof(text)); + } + } + + BLF_disable(font_id, BLF_SHADOW); + + BLF_batch_draw_end(); + + GPU_matrix_pop_projection(); +} + +static void view_to_string__frame_number( + void *UNUSED(user_data), float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str) +{ + BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos); +} + +static void view_to_string__time( + void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str) +{ + const Scene *scene = (const Scene *)user_data; + + int brevity_level = 0; + if (U.timecode_style == USER_TIMECODE_MINIMAL && v2d_step >= FPS) { + brevity_level = 1; + } + + BLI_timecode_string_from_time( + r_str, max_len, brevity_level, v2d_pos / (float)FPS, FPS, U.timecode_style); +} + +static void view_to_string__value( + void *UNUSED(user_data), float v2d_pos, float v2d_step, uint max_len, char *r_str) +{ + if (v2d_step >= 1.0f) { + BLI_snprintf(r_str, max_len, "%d", (int)v2d_pos); + } + else if (v2d_step >= 0.1f) { + BLI_snprintf(r_str, max_len, "%.1f", v2d_pos); + } + else if (v2d_step >= 0.01f) { + BLI_snprintf(r_str, max_len, "%.2f", v2d_pos); + } + else { + BLI_snprintf(r_str, max_len, "%.3f", v2d_pos); + } +} + +/* Grid Resolution API + **************************************************/ + +float UI_view2d_grid_resolution_x__frames_or_seconds(const struct View2D *v2d, + const struct Scene *scene, + bool display_seconds) +{ + if (display_seconds) { + return view2d_major_step_x__time(v2d, scene); + } + return view2d_major_step_x__continuous(v2d); +} + +float UI_view2d_grid_resolution_y__values(const struct View2D *v2d) +{ + return view2d_major_step_y__continuous(v2d); +} + +/* Line Drawing API + **************************************************/ + +void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d, bool display_minor_lines) +{ + const uint major_line_distance = view2d_major_step_x__discrete(v2d); + view2d_draw_lines( + v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); +} + +void UI_view2d_draw_lines_x__values(const View2D *v2d) +{ + const float major_line_distance = view2d_major_step_x__continuous(v2d); + view2d_draw_lines(v2d, major_line_distance, true, 'v'); +} + +void UI_view2d_draw_lines_y__values(const View2D *v2d) +{ + const float major_line_distance = view2d_major_step_y__continuous(v2d); + view2d_draw_lines(v2d, major_line_distance, true, 'h'); +} + +void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, + const Scene *scene, + bool display_minor_lines) +{ + const float major_line_distance = view2d_major_step_x__time(v2d, scene); + view2d_draw_lines( + v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v'); +} + +void UI_view2d_draw_lines_x__discrete_frames_or_seconds(const View2D *v2d, + const Scene *scene, + bool display_seconds, + bool display_minor_lines) +{ + if (display_seconds) { + UI_view2d_draw_lines_x__discrete_time(v2d, scene, display_minor_lines); + } + else { + UI_view2d_draw_lines_x__discrete_values(v2d, display_minor_lines); + } +} + +void UI_view2d_draw_lines_x__frames_or_seconds(const View2D *v2d, + const Scene *scene, + bool display_seconds) +{ + if (display_seconds) { + UI_view2d_draw_lines_x__discrete_time(v2d, scene, true); + } + else { + UI_view2d_draw_lines_x__values(v2d); + } +} + +/* Scale indicator text drawing API + **************************************************/ + +static void UI_view2d_draw_scale_x__discrete_values(const ARegion *region, + const View2D *v2d, + const rcti *rect, + int colorid) +{ + const float number_step = view2d_major_step_x__discrete(v2d); + draw_horizontal_scale_indicators( + region, v2d, number_step, rect, view_to_string__frame_number, nullptr, colorid); +} + +static void UI_view2d_draw_scale_x__discrete_time( + const ARegion *region, const View2D *v2d, const rcti *rect, const Scene *scene, int colorid) +{ + const float step = view2d_major_step_x__time(v2d, scene); + draw_horizontal_scale_indicators( + region, v2d, step, rect, view_to_string__time, (void *)scene, colorid); +} + +static void UI_view2d_draw_scale_x__values(const ARegion *region, + const View2D *v2d, + const rcti *rect, + int colorid) +{ + const float step = view2d_major_step_x__continuous(v2d); + draw_horizontal_scale_indicators( + region, v2d, step, rect, view_to_string__value, nullptr, colorid); +} + +void UI_view2d_draw_scale_y__values(const ARegion *region, + const View2D *v2d, + const rcti *rect, + int colorid) +{ + const float step = view2d_major_step_y__continuous(v2d); + draw_vertical_scale_indicators( + region, v2d, step, 0.0f, rect, view_to_string__value, nullptr, colorid); +} + +void UI_view2d_draw_scale_y__block(const ARegion *region, + const View2D *v2d, + const rcti *rect, + int colorid) +{ + draw_vertical_scale_indicators( + region, v2d, 1.0f, 0.5f, rect, view_to_string__value, nullptr, colorid); +} + +void UI_view2d_draw_scale_x__discrete_frames_or_seconds(const struct ARegion *region, + const struct View2D *v2d, + const struct rcti *rect, + const struct Scene *scene, + bool display_seconds, + int colorid) +{ + if (display_seconds) { + UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid); + } + else { + UI_view2d_draw_scale_x__discrete_values(region, v2d, rect, colorid); + } +} + +void UI_view2d_draw_scale_x__frames_or_seconds(const struct ARegion *region, + const struct View2D *v2d, + const struct rcti *rect, + const struct Scene *scene, + bool display_seconds, + int colorid) +{ + if (display_seconds) { + UI_view2d_draw_scale_x__discrete_time(region, v2d, rect, scene, colorid); + } + else { + UI_view2d_draw_scale_x__values(region, v2d, rect, colorid); + } +} diff --git a/source/blender/editors/interface/view2d_edge_pan.c b/source/blender/editors/interface/view2d_edge_pan.c deleted file mode 100644 index 1f54fd6f669..00000000000 --- a/source/blender/editors/interface/view2d_edge_pan.c +++ /dev/null @@ -1,382 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2021 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup spnode - */ - -#include "BKE_context.h" - -#include "BLI_math.h" -#include "BLI_rect.h" - -#include "ED_screen.h" - -#include "MEM_guardedalloc.h" - -#include "PIL_time.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "WM_api.h" -#include "WM_types.h" - -/* -------------------------------------------------------------------- */ -/** \name Edge Pan Operator Utilities - * \{ */ - -bool UI_view2d_edge_pan_poll(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - /* Check if there's a region in context to work with. */ - if (region == NULL) { - return false; - } - - View2D *v2d = ®ion->v2d; - - /* Check that 2d-view can pan. */ - if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) { - return false; - } - - /* View can pan. */ - return true; -} - -void UI_view2d_edge_pan_init(bContext *C, - View2DEdgePanData *vpd, - float inside_pad, - float outside_pad, - float speed_ramp, - float max_speed, - float delay, - float zoom_influence) -{ - if (!UI_view2d_edge_pan_poll(C)) { - return; - } - - /* Set pointers to owners. */ - vpd->screen = CTX_wm_screen(C); - vpd->area = CTX_wm_area(C); - vpd->region = CTX_wm_region(C); - vpd->v2d = &vpd->region->v2d; - - BLI_assert(speed_ramp > 0.0f); - vpd->inside_pad = inside_pad; - vpd->outside_pad = outside_pad; - vpd->speed_ramp = speed_ramp; - vpd->max_speed = max_speed; - vpd->delay = delay; - vpd->zoom_influence = zoom_influence; - - vpd->enabled = false; - - /* Calculate translation factor, based on size of view. */ - const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1); - const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); - vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; - vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; - - UI_view2d_edge_pan_reset(vpd); -} - -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; -} - -/** - * Reset the edge pan timers if the mouse isn't in the scroll zone and - * start the timers when the mouse enters a scroll zone. - */ -static void edge_pan_manage_delay_timers(View2DEdgePanData *vpd, - int pan_dir_x, - int pan_dir_y, - const double current_time) -{ - if (pan_dir_x == 0) { - vpd->edge_pan_start_time_x = 0.0; - } - else if (vpd->edge_pan_start_time_x == 0.0) { - vpd->edge_pan_start_time_x = current_time; - } - if (pan_dir_y == 0) { - vpd->edge_pan_start_time_y = 0.0; - } - else if (vpd->edge_pan_start_time_y == 0.0) { - vpd->edge_pan_start_time_y = current_time; - } -} - -/** - * Used to calculate a "fade in" factor for edge panning to make the interaction feel smooth - * and more purposeful. - * - * \note Assumes a domain_min of 0.0f. - */ -static float smootherstep(const float domain_max, float x) -{ - x = clamp_f(x / domain_max, 0.0, 1.0); - return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); -} - -static float edge_pan_speed(View2DEdgePanData *vpd, - int event_loc, - bool x_dir, - const double current_time) -{ - ARegion *region = vpd->region; - - /* Find the distance from the start of the drag zone. */ - const int pad = vpd->inside_pad * U.widget_unit; - const int min = (x_dir ? region->winrct.xmin : region->winrct.ymin) + pad; - const int max = (x_dir ? region->winrct.xmax : region->winrct.ymax) - pad; - int distance = 0.0; - if (event_loc > max) { - distance = event_loc - max; - } - else if (event_loc < min) { - distance = min - event_loc; - } - else { - BLI_assert_msg(0, "Calculating speed outside of pan zones"); - return 0.0f; - } - float distance_factor = distance / (vpd->speed_ramp * U.widget_unit); - CLAMP(distance_factor, 0.0f, 1.0f); - - /* 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 = vpd->delay > 0.01f ? - smootherstep(vpd->delay, (float)(current_time - start_time)) : - 1.0f; - - /* Zoom factor increases speed when zooming in and decreases speed when zooming out. */ - const float zoomx = (float)(BLI_rcti_size_x(®ion->winrct) + 1) / - BLI_rctf_size_x(®ion->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) -{ - View2D *v2d = vpd->v2d; - if (!v2d) { - return; - } - - /* Calculate amount to move view by. */ - dx *= vpd->facx; - dy *= vpd->facy; - - /* Only move view on an axis if change is allowed. */ - if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) { - v2d->cur.xmin += dx; - v2d->cur.xmax += dx; - } - if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) { - v2d->cur.ymin += dy; - v2d->cur.ymax += dy; - } - - /* 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_apply(bContext *C, View2DEdgePanData *vpd, const int xy[2]) -{ - ARegion *region = vpd->region; - - rcti inside_rect, outside_rect; - inside_rect = region->winrct; - outside_rect = region->winrct; - BLI_rcti_pad(&inside_rect, -vpd->inside_pad * U.widget_unit, -vpd->inside_pad * U.widget_unit); - BLI_rcti_pad(&outside_rect, vpd->outside_pad * U.widget_unit, vpd->outside_pad * U.widget_unit); - - /* Check if we can actually start the edge pan (e.g. adding nodes outside the view will start - * disabled). */ - if (BLI_rcti_isect_pt_v(&inside_rect, xy)) { - /* We are inside once, can start. */ - vpd->enabled = true; - } - - int pan_dir_x = 0; - int pan_dir_y = 0; - if (vpd->enabled && ((vpd->outside_pad == 0) || BLI_rcti_isect_pt_v(&outside_rect, xy))) { - /* Find whether the mouse is beyond X and Y edges. */ - if (xy[0] > inside_rect.xmax) { - pan_dir_x = 1; - } - else if (xy[0] < inside_rect.xmin) { - pan_dir_x = -1; - } - if (xy[1] > inside_rect.ymax) { - pan_dir_y = 1; - } - else if (xy[1] < inside_rect.ymin) { - pan_dir_y = -1; - } - } - - const double current_time = PIL_check_seconds_timer(); - edge_pan_manage_delay_timers(vpd, pan_dir_x, pan_dir_y, current_time); - - /* Calculate the delta since the last time the operator was called. */ - const float dtime = (float)(current_time - vpd->edge_pan_last_time); - float dx = 0.0f, dy = 0.0f; - if (pan_dir_x != 0) { - const float speed = edge_pan_speed(vpd, xy[0], true, current_time); - dx = dtime * speed * (float)pan_dir_x; - } - if (pan_dir_y != 0) { - const float speed = edge_pan_speed(vpd, xy[1], false, current_time); - dy = dtime * speed * (float)pan_dir_y; - } - vpd->edge_pan_last_time = current_time; - - /* Pan, clamping inside the regions total bounds. */ - edge_pan_apply_delta(C, vpd, dx, dy); -} - -void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const wmEvent *event) -{ - /* Only mouse-move events matter here, ignore others. */ - if (event->type != MOUSEMOVE) { - return; - } - - UI_view2d_edge_pan_apply(C, vpd, event->xy); -} - -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. */ - UI_view2d_edge_pan_operator_properties_ex(ot, - /*inside_pad*/ 1.0f, - /*outside_pad*/ 0.0f, - /*speed_ramp*/ 1.0f, - /*max_speed*/ 500.0f, - /*delay*/ 1.0f, - /*zoom_influence*/ 0.0f); -} - -void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot, - float inside_pad, - float outside_pad, - float speed_ramp, - float max_speed, - float delay, - float zoom_influence) -{ - RNA_def_float( - ot->srna, - "inside_padding", - inside_pad, - 0.0f, - 100.0f, - "Inside Padding", - "Inside distance in UI units from the edge of the region within which to start panning", - 0.0f, - 100.0f); - RNA_def_float( - ot->srna, - "outside_padding", - outside_pad, - 0.0f, - 100.0f, - "Outside Padding", - "Outside distance in UI units from the edge of the region at which to stop panning", - 0.0f, - 100.0f); - RNA_def_float(ot->srna, - "speed_ramp", - speed_ramp, - 0.0f, - 100.0f, - "Speed Ramp", - "Width of the zone in UI units where speed increases with distance from the edge", - 0.0f, - 100.0f); - RNA_def_float(ot->srna, - "max_speed", - max_speed, - 0.0f, - 10000.0f, - "Max Speed", - "Maximum speed in UI units per second", - 0.0f, - 10000.0f); - RNA_def_float(ot->srna, - "delay", - delay, - 0.0f, - 10.0f, - "Delay", - "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) -{ - UI_view2d_edge_pan_init(C, - vpd, - RNA_float_get(op->ptr, "inside_padding"), - 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, "zoom_influence")); -} - -/** \} */ diff --git a/source/blender/editors/interface/view2d_edge_pan.cc b/source/blender/editors/interface/view2d_edge_pan.cc new file mode 100644 index 00000000000..ab1672ae8f6 --- /dev/null +++ b/source/blender/editors/interface/view2d_edge_pan.cc @@ -0,0 +1,382 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2021 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup spnode + */ + +#include "BKE_context.h" + +#include "BLI_math.h" +#include "BLI_rect.h" + +#include "ED_screen.h" + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "WM_api.h" +#include "WM_types.h" + +/* -------------------------------------------------------------------- */ +/** \name Edge Pan Operator Utilities + * \{ */ + +bool UI_view2d_edge_pan_poll(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + /* Check if there's a region in context to work with. */ + if (region == nullptr) { + return false; + } + + View2D *v2d = ®ion->v2d; + + /* Check that 2d-view can pan. */ + if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) { + return false; + } + + /* View can pan. */ + return true; +} + +void UI_view2d_edge_pan_init(bContext *C, + View2DEdgePanData *vpd, + float inside_pad, + float outside_pad, + float speed_ramp, + float max_speed, + float delay, + float zoom_influence) +{ + if (!UI_view2d_edge_pan_poll(C)) { + return; + } + + /* Set pointers to owners. */ + vpd->screen = CTX_wm_screen(C); + vpd->area = CTX_wm_area(C); + vpd->region = CTX_wm_region(C); + vpd->v2d = &vpd->region->v2d; + + BLI_assert(speed_ramp > 0.0f); + vpd->inside_pad = inside_pad; + vpd->outside_pad = outside_pad; + vpd->speed_ramp = speed_ramp; + vpd->max_speed = max_speed; + vpd->delay = delay; + vpd->zoom_influence = zoom_influence; + + vpd->enabled = false; + + /* Calculate translation factor, based on size of view. */ + const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1); + const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); + vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; + vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; + + UI_view2d_edge_pan_reset(vpd); +} + +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; +} + +/** + * Reset the edge pan timers if the mouse isn't in the scroll zone and + * start the timers when the mouse enters a scroll zone. + */ +static void edge_pan_manage_delay_timers(View2DEdgePanData *vpd, + int pan_dir_x, + int pan_dir_y, + const double current_time) +{ + if (pan_dir_x == 0) { + vpd->edge_pan_start_time_x = 0.0; + } + else if (vpd->edge_pan_start_time_x == 0.0) { + vpd->edge_pan_start_time_x = current_time; + } + if (pan_dir_y == 0) { + vpd->edge_pan_start_time_y = 0.0; + } + else if (vpd->edge_pan_start_time_y == 0.0) { + vpd->edge_pan_start_time_y = current_time; + } +} + +/** + * Used to calculate a "fade in" factor for edge panning to make the interaction feel smooth + * and more purposeful. + * + * \note Assumes a domain_min of 0.0f. + */ +static float smootherstep(const float domain_max, float x) +{ + x = clamp_f(x / domain_max, 0.0, 1.0); + return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); +} + +static float edge_pan_speed(View2DEdgePanData *vpd, + int event_loc, + bool x_dir, + const double current_time) +{ + ARegion *region = vpd->region; + + /* Find the distance from the start of the drag zone. */ + const int pad = vpd->inside_pad * U.widget_unit; + const int min = (x_dir ? region->winrct.xmin : region->winrct.ymin) + pad; + const int max = (x_dir ? region->winrct.xmax : region->winrct.ymax) - pad; + int distance = 0.0; + if (event_loc > max) { + distance = event_loc - max; + } + else if (event_loc < min) { + distance = min - event_loc; + } + else { + BLI_assert_msg(0, "Calculating speed outside of pan zones"); + return 0.0f; + } + float distance_factor = distance / (vpd->speed_ramp * U.widget_unit); + CLAMP(distance_factor, 0.0f, 1.0f); + + /* 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 = vpd->delay > 0.01f ? + smootherstep(vpd->delay, (float)(current_time - start_time)) : + 1.0f; + + /* Zoom factor increases speed when zooming in and decreases speed when zooming out. */ + const float zoomx = (float)(BLI_rcti_size_x(®ion->winrct) + 1) / + BLI_rctf_size_x(®ion->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) +{ + View2D *v2d = vpd->v2d; + if (!v2d) { + return; + } + + /* Calculate amount to move view by. */ + dx *= vpd->facx; + dy *= vpd->facy; + + /* Only move view on an axis if change is allowed. */ + if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) { + v2d->cur.xmin += dx; + v2d->cur.xmax += dx; + } + if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) { + v2d->cur.ymin += dy; + v2d->cur.ymax += dy; + } + + /* 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_apply(bContext *C, View2DEdgePanData *vpd, const int xy[2]) +{ + ARegion *region = vpd->region; + + rcti inside_rect, outside_rect; + inside_rect = region->winrct; + outside_rect = region->winrct; + BLI_rcti_pad(&inside_rect, -vpd->inside_pad * U.widget_unit, -vpd->inside_pad * U.widget_unit); + BLI_rcti_pad(&outside_rect, vpd->outside_pad * U.widget_unit, vpd->outside_pad * U.widget_unit); + + /* Check if we can actually start the edge pan (e.g. adding nodes outside the view will start + * disabled). */ + if (BLI_rcti_isect_pt_v(&inside_rect, xy)) { + /* We are inside once, can start. */ + vpd->enabled = true; + } + + int pan_dir_x = 0; + int pan_dir_y = 0; + if (vpd->enabled && ((vpd->outside_pad == 0) || BLI_rcti_isect_pt_v(&outside_rect, xy))) { + /* Find whether the mouse is beyond X and Y edges. */ + if (xy[0] > inside_rect.xmax) { + pan_dir_x = 1; + } + else if (xy[0] < inside_rect.xmin) { + pan_dir_x = -1; + } + if (xy[1] > inside_rect.ymax) { + pan_dir_y = 1; + } + else if (xy[1] < inside_rect.ymin) { + pan_dir_y = -1; + } + } + + const double current_time = PIL_check_seconds_timer(); + edge_pan_manage_delay_timers(vpd, pan_dir_x, pan_dir_y, current_time); + + /* Calculate the delta since the last time the operator was called. */ + const float dtime = (float)(current_time - vpd->edge_pan_last_time); + float dx = 0.0f, dy = 0.0f; + if (pan_dir_x != 0) { + const float speed = edge_pan_speed(vpd, xy[0], true, current_time); + dx = dtime * speed * (float)pan_dir_x; + } + if (pan_dir_y != 0) { + const float speed = edge_pan_speed(vpd, xy[1], false, current_time); + dy = dtime * speed * (float)pan_dir_y; + } + vpd->edge_pan_last_time = current_time; + + /* Pan, clamping inside the regions total bounds. */ + edge_pan_apply_delta(C, vpd, dx, dy); +} + +void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const wmEvent *event) +{ + /* Only mouse-move events matter here, ignore others. */ + if (event->type != MOUSEMOVE) { + return; + } + + UI_view2d_edge_pan_apply(C, vpd, event->xy); +} + +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. */ + UI_view2d_edge_pan_operator_properties_ex(ot, + /*inside_pad*/ 1.0f, + /*outside_pad*/ 0.0f, + /*speed_ramp*/ 1.0f, + /*max_speed*/ 500.0f, + /*delay*/ 1.0f, + /*zoom_influence*/ 0.0f); +} + +void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot, + float inside_pad, + float outside_pad, + float speed_ramp, + float max_speed, + float delay, + float zoom_influence) +{ + RNA_def_float( + ot->srna, + "inside_padding", + inside_pad, + 0.0f, + 100.0f, + "Inside Padding", + "Inside distance in UI units from the edge of the region within which to start panning", + 0.0f, + 100.0f); + RNA_def_float( + ot->srna, + "outside_padding", + outside_pad, + 0.0f, + 100.0f, + "Outside Padding", + "Outside distance in UI units from the edge of the region at which to stop panning", + 0.0f, + 100.0f); + RNA_def_float(ot->srna, + "speed_ramp", + speed_ramp, + 0.0f, + 100.0f, + "Speed Ramp", + "Width of the zone in UI units where speed increases with distance from the edge", + 0.0f, + 100.0f); + RNA_def_float(ot->srna, + "max_speed", + max_speed, + 0.0f, + 10000.0f, + "Max Speed", + "Maximum speed in UI units per second", + 0.0f, + 10000.0f); + RNA_def_float(ot->srna, + "delay", + delay, + 0.0f, + 10.0f, + "Delay", + "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) +{ + UI_view2d_edge_pan_init(C, + vpd, + RNA_float_get(op->ptr, "inside_padding"), + 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, "zoom_influence")); +} + +/** \} */ diff --git a/source/blender/editors/interface/view2d_gizmo_navigate.c b/source/blender/editors/interface/view2d_gizmo_navigate.c deleted file mode 100644 index bc7d21270e8..00000000000 --- a/source/blender/editors/interface/view2d_gizmo_navigate.c +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" - -#include "ED_gizmo_library.h" -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "MEM_guardedalloc.h" - -#include "RNA_access.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_view2d.h" - -/* -------------------------------------------------------------------- */ -/** \name View2D Navigation Gizmo Group - * - * A simpler version of #VIEW3D_GGT_navigate - * - * Written to be used by different kinds of 2D view types. - * \{ */ - -/* Size of main icon. */ -#define GIZMO_SIZE 80 -/* Factor for size of smaller button. */ -#define GIZMO_MINI_FAC 0.35f -/* How much mini buttons offset from the primary. */ -#define GIZMO_MINI_OFFSET_FAC 0.38f - -enum { - GZ_INDEX_MOVE = 0, - GZ_INDEX_ZOOM = 1, - - GZ_INDEX_TOTAL = 2, -}; - -struct NavigateGizmoInfo { - const char *opname; - const char *gizmo; - uint icon; -}; - -static struct NavigateGizmoInfo g_navigate_params_for_space_image[GZ_INDEX_TOTAL] = { - { - .opname = "IMAGE_OT_view_pan", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_PAN, - }, - { - .opname = "IMAGE_OT_view_zoom", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_ZOOM, - }, -}; - -static struct NavigateGizmoInfo g_navigate_params_for_space_clip[GZ_INDEX_TOTAL] = { - { - .opname = "CLIP_OT_view_pan", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_PAN, - }, - { - .opname = "CLIP_OT_view_zoom", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_ZOOM, - }, -}; - -static struct NavigateGizmoInfo g_navigate_params_for_view2d[GZ_INDEX_TOTAL] = { - { - .opname = "VIEW2D_OT_pan", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_PAN, - }, - { - .opname = "VIEW2D_OT_zoom", - .gizmo = "GIZMO_GT_button_2d", - ICON_VIEW_ZOOM, - }, -}; - -static struct NavigateGizmoInfo *navigate_params_from_space_type(short space_type) -{ - switch (space_type) { - case SPACE_IMAGE: - return g_navigate_params_for_space_image; - case SPACE_CLIP: - return g_navigate_params_for_space_clip; - default: - /* Used for sequencer. */ - return g_navigate_params_for_view2d; - } -} - -struct NavigateWidgetGroup { - wmGizmo *gz_array[GZ_INDEX_TOTAL]; - /* Store the view state to check for changes. */ - struct { - rcti rect_visible; - } state; - int region_size[2]; -}; - -static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) -{ - if ((U.uiflag & USER_SHOW_GIZMO_NAVIGATE) == 0) { - return false; - } - ScrArea *area = CTX_wm_area(C); - if (area == NULL) { - return false; - } - switch (area->spacetype) { - case SPACE_SEQ: { - const SpaceSeq *sseq = area->spacedata.first; - if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_NAVIGATE)) { - return false; - } - break; - } - } - return true; -} - -static void WIDGETGROUP_navigate_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) -{ - struct NavigateWidgetGroup *navgroup = MEM_callocN(sizeof(struct NavigateWidgetGroup), __func__); - - navgroup->region_size[0] = -1; - navgroup->region_size[1] = -1; - - const struct NavigateGizmoInfo *navigate_params = navigate_params_from_space_type( - gzgroup->type->gzmap_params.spaceid); - - for (int i = 0; i < GZ_INDEX_TOTAL; i++) { - const struct NavigateGizmoInfo *info = &navigate_params[i]; - navgroup->gz_array[i] = WM_gizmo_new(info->gizmo, gzgroup, NULL); - wmGizmo *gz = navgroup->gz_array[i]; - gz->flag |= WM_GIZMO_MOVE_CURSOR | WM_GIZMO_DRAW_MODAL; - - { - uchar icon_color[3]; - UI_GetThemeColor3ubv(TH_TEXT, icon_color); - int color_tint, color_tint_hi; - if (icon_color[0] > 128) { - color_tint = -40; - color_tint_hi = 60; - gz->color[3] = 0.5f; - gz->color_hi[3] = 0.5f; - } - else { - color_tint = 60; - color_tint_hi = 60; - gz->color[3] = 0.5f; - gz->color_hi[3] = 0.75f; - } - UI_GetThemeColorShade3fv(TH_HEADER, color_tint, gz->color); - UI_GetThemeColorShade3fv(TH_HEADER, color_tint_hi, gz->color_hi); - } - - /* may be overwritten later */ - gz->scale_basis = (GIZMO_SIZE * GIZMO_MINI_FAC) / 2; - if (info->icon != 0) { - PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon"); - RNA_property_enum_set(gz->ptr, prop, info->icon); - RNA_enum_set( - gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_OUTLINE | ED_GIZMO_BUTTON_SHOW_BACKDROP); - } - - wmOperatorType *ot = WM_operatortype_find(info->opname, true); - WM_gizmo_operator_set(gz, 0, ot, NULL); - } - - /* Modal operators, don't use initial mouse location since we're clicking on a button. */ - { - int gz_ids[] = {GZ_INDEX_ZOOM}; - for (int i = 0; i < ARRAY_SIZE(gz_ids); i++) { - wmGizmo *gz = navgroup->gz_array[gz_ids[i]]; - wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0); - RNA_boolean_set(&gzop->ptr, "use_cursor_init", false); - } - } - - gzgroup->customdata = navgroup; -} - -static void WIDGETGROUP_navigate_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) -{ - struct NavigateWidgetGroup *navgroup = gzgroup->customdata; - ARegion *region = CTX_wm_region(C); - - const rcti *rect_visible = ED_region_visible_rect(region); - - if ((navgroup->state.rect_visible.xmax == rect_visible->xmax) && - (navgroup->state.rect_visible.ymax == rect_visible->ymax)) { - return; - } - - navgroup->state.rect_visible = *rect_visible; - - const float icon_size = GIZMO_SIZE; - const float icon_offset_mini = icon_size * GIZMO_MINI_OFFSET_FAC * UI_DPI_FAC; - const float co[2] = { - roundf(rect_visible->xmax - (icon_offset_mini * 0.75f)), - roundf(rect_visible->ymax - (icon_offset_mini * 0.75f)), - }; - - wmGizmo *gz; - - for (uint i = 0; i < ARRAY_SIZE(navgroup->gz_array); i++) { - gz = navgroup->gz_array[i]; - WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true); - } - - int icon_mini_slot = 0; - - gz = navgroup->gz_array[GZ_INDEX_ZOOM]; - gz->matrix_basis[3][0] = roundf(co[0]); - gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++)); - WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false); - - gz = navgroup->gz_array[GZ_INDEX_MOVE]; - gz->matrix_basis[3][0] = roundf(co[0]); - gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++)); - WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false); -} - -void VIEW2D_GGT_navigate_impl(wmGizmoGroupType *gzgt, const char *idname) -{ - gzgt->name = "View2D Navigate"; - gzgt->idname = idname; - - gzgt->flag |= (WM_GIZMOGROUPTYPE_PERSISTENT | WM_GIZMOGROUPTYPE_SCALE | - WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL); - - gzgt->poll = WIDGETGROUP_navigate_poll; - gzgt->setup = WIDGETGROUP_navigate_setup; - gzgt->draw_prepare = WIDGETGROUP_navigate_draw_prepare; -} - -/** \} */ diff --git a/source/blender/editors/interface/view2d_gizmo_navigate.cc b/source/blender/editors/interface/view2d_gizmo_navigate.cc new file mode 100644 index 00000000000..01729e35246 --- /dev/null +++ b/source/blender/editors/interface/view2d_gizmo_navigate.cc @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" + +#include "ED_gizmo_library.h" +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_view2d.h" + +/* -------------------------------------------------------------------- */ +/** \name View2D Navigation Gizmo Group + * + * A simpler version of #VIEW3D_GGT_navigate + * + * Written to be used by different kinds of 2D view types. + * \{ */ + +/* Size of main icon. */ +#define GIZMO_SIZE 80 +/* Factor for size of smaller button. */ +#define GIZMO_MINI_FAC 0.35f +/* How much mini buttons offset from the primary. */ +#define GIZMO_MINI_OFFSET_FAC 0.38f + +enum { + GZ_INDEX_MOVE = 0, + GZ_INDEX_ZOOM = 1, + + GZ_INDEX_TOTAL = 2, +}; + +struct NavigateGizmoInfo { + const char *opname; + const char *gizmo; + uint icon; +}; + +static struct NavigateGizmoInfo g_navigate_params_for_space_image[GZ_INDEX_TOTAL] = { + { + "IMAGE_OT_view_pan", + "GIZMO_GT_button_2d", + ICON_VIEW_PAN, + }, + { + "IMAGE_OT_view_zoom", + "GIZMO_GT_button_2d", + ICON_VIEW_ZOOM, + }, +}; + +static struct NavigateGizmoInfo g_navigate_params_for_space_clip[GZ_INDEX_TOTAL] = { + { + "CLIP_OT_view_pan", + "GIZMO_GT_button_2d", + ICON_VIEW_PAN, + }, + { + "CLIP_OT_view_zoom", + "GIZMO_GT_button_2d", + ICON_VIEW_ZOOM, + }, +}; + +static struct NavigateGizmoInfo g_navigate_params_for_view2d[GZ_INDEX_TOTAL] = { + { + "VIEW2D_OT_pan", + "GIZMO_GT_button_2d", + ICON_VIEW_PAN, + }, + { + "VIEW2D_OT_zoom", + "GIZMO_GT_button_2d", + ICON_VIEW_ZOOM, + }, +}; + +static struct NavigateGizmoInfo *navigate_params_from_space_type(short space_type) +{ + switch (space_type) { + case SPACE_IMAGE: + return g_navigate_params_for_space_image; + case SPACE_CLIP: + return g_navigate_params_for_space_clip; + default: + /* Used for sequencer. */ + return g_navigate_params_for_view2d; + } +} + +struct NavigateWidgetGroup { + wmGizmo *gz_array[GZ_INDEX_TOTAL]; + /* Store the view state to check for changes. */ + struct { + rcti rect_visible; + } state; + int region_size[2]; +}; + +static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt)) +{ + if ((U.uiflag & USER_SHOW_GIZMO_NAVIGATE) == 0) { + return false; + } + ScrArea *area = CTX_wm_area(C); + if (area == nullptr) { + return false; + } + switch (area->spacetype) { + case SPACE_SEQ: { + const SpaceSeq *sseq = static_cast(area->spacedata.first); + if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_NAVIGATE)) { + return false; + } + break; + } + } + return true; +} + +static void WIDGETGROUP_navigate_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) +{ + NavigateWidgetGroup *navgroup = MEM_cnew(__func__); + + navgroup->region_size[0] = -1; + navgroup->region_size[1] = -1; + + const struct NavigateGizmoInfo *navigate_params = navigate_params_from_space_type( + gzgroup->type->gzmap_params.spaceid); + + for (int i = 0; i < GZ_INDEX_TOTAL; i++) { + const struct NavigateGizmoInfo *info = &navigate_params[i]; + navgroup->gz_array[i] = WM_gizmo_new(info->gizmo, gzgroup, nullptr); + wmGizmo *gz = navgroup->gz_array[i]; + gz->flag |= WM_GIZMO_MOVE_CURSOR | WM_GIZMO_DRAW_MODAL; + + { + uchar icon_color[3]; + UI_GetThemeColor3ubv(TH_TEXT, icon_color); + int color_tint, color_tint_hi; + if (icon_color[0] > 128) { + color_tint = -40; + color_tint_hi = 60; + gz->color[3] = 0.5f; + gz->color_hi[3] = 0.5f; + } + else { + color_tint = 60; + color_tint_hi = 60; + gz->color[3] = 0.5f; + gz->color_hi[3] = 0.75f; + } + UI_GetThemeColorShade3fv(TH_HEADER, color_tint, gz->color); + UI_GetThemeColorShade3fv(TH_HEADER, color_tint_hi, gz->color_hi); + } + + /* may be overwritten later */ + gz->scale_basis = (GIZMO_SIZE * GIZMO_MINI_FAC) / 2; + if (info->icon != 0) { + PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon"); + RNA_property_enum_set(gz->ptr, prop, info->icon); + RNA_enum_set( + gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_OUTLINE | ED_GIZMO_BUTTON_SHOW_BACKDROP); + } + + wmOperatorType *ot = WM_operatortype_find(info->opname, true); + WM_gizmo_operator_set(gz, 0, ot, nullptr); + } + + /* Modal operators, don't use initial mouse location since we're clicking on a button. */ + { + int gz_ids[] = {GZ_INDEX_ZOOM}; + for (int i = 0; i < ARRAY_SIZE(gz_ids); i++) { + wmGizmo *gz = navgroup->gz_array[gz_ids[i]]; + wmGizmoOpElem *gzop = WM_gizmo_operator_get(gz, 0); + RNA_boolean_set(&gzop->ptr, "use_cursor_init", false); + } + } + + gzgroup->customdata = navgroup; +} + +static void WIDGETGROUP_navigate_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) +{ + NavigateWidgetGroup *navgroup = static_cast(gzgroup->customdata); + ARegion *region = CTX_wm_region(C); + + const rcti *rect_visible = ED_region_visible_rect(region); + + if ((navgroup->state.rect_visible.xmax == rect_visible->xmax) && + (navgroup->state.rect_visible.ymax == rect_visible->ymax)) { + return; + } + + navgroup->state.rect_visible = *rect_visible; + + const float icon_size = GIZMO_SIZE; + const float icon_offset_mini = icon_size * GIZMO_MINI_OFFSET_FAC * UI_DPI_FAC; + const float co[2] = { + roundf(rect_visible->xmax - (icon_offset_mini * 0.75f)), + roundf(rect_visible->ymax - (icon_offset_mini * 0.75f)), + }; + + wmGizmo *gz; + + for (uint i = 0; i < ARRAY_SIZE(navgroup->gz_array); i++) { + gz = navgroup->gz_array[i]; + WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true); + } + + int icon_mini_slot = 0; + + gz = navgroup->gz_array[GZ_INDEX_ZOOM]; + gz->matrix_basis[3][0] = roundf(co[0]); + gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++)); + WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false); + + gz = navgroup->gz_array[GZ_INDEX_MOVE]; + gz->matrix_basis[3][0] = roundf(co[0]); + gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++)); + WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false); +} + +void VIEW2D_GGT_navigate_impl(wmGizmoGroupType *gzgt, const char *idname) +{ + gzgt->name = "View2D Navigate"; + gzgt->idname = idname; + + gzgt->flag |= (WM_GIZMOGROUPTYPE_PERSISTENT | WM_GIZMOGROUPTYPE_SCALE | + WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL); + + gzgt->poll = WIDGETGROUP_navigate_poll; + gzgt->setup = WIDGETGROUP_navigate_setup; + gzgt->draw_prepare = WIDGETGROUP_navigate_draw_prepare; +} + +/** \} */ diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c deleted file mode 100644 index 0b4d00a7def..00000000000 --- a/source/blender/editors/interface/view2d_ops.c +++ /dev/null @@ -1,2282 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" -#include "DNA_windowmanager_types.h" - -#include "BLI_blenlib.h" -#include "BLI_math_base.h" -#include "BLI_math_vector.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" - -#include "RNA_access.h" -#include "RNA_define.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "PIL_time.h" /* USER_ZOOM_CONTINUE */ - -/* -------------------------------------------------------------------- */ -/** \name Internal Utilities - * \{ */ - -static bool view2d_poll(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - return (region != NULL) && (region->v2d.flag & V2D_IS_INIT); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Pan Shared Utilities - * \{ */ - -/** - * This group of operators come in several forms: - * -# Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by - * -# Scroll-wheel 'steps' - rolling mouse-wheel by one step moves view by predefined amount - * - * In order to make sure this works, each operator must define the following RNA-Operator Props: - * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor) - */ - -/** - * Temporary custom-data for operator. - */ -typedef struct v2dViewPanData { - /** screen where view pan was initiated */ - bScreen *screen; - /** area where view pan was initiated */ - ScrArea *area; - /** region where view pan was initiated */ - ARegion *region; - /** view2d we're operating in */ - View2D *v2d; - - /** amount to move view relative to zoom */ - float facx, facy; - - /* options for version 1 */ - /** mouse x/y values in window when operator was initiated */ - int startx, starty; - /** previous x/y values of mouse in window */ - int lastx, lasty; - /** event starting pan, for modal exit */ - int invoke_event; - - /** for MMB in scrollers (old feature in past, but now not that useful) */ - short in_scroller; - - /* View2D Edge Panning */ - double edge_pan_last_time; - double edge_pan_start_time_x, edge_pan_start_time_y; -} v2dViewPanData; - -static bool view_pan_poll(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - /* check if there's a region in context to work with */ - if (region == NULL) { - return false; - } - - View2D *v2d = ®ion->v2d; - - /* check that 2d-view can pan */ - if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) { - return false; - } - - /* view can pan */ - return true; -} - -/* initialize panning customdata */ -static void view_pan_init(bContext *C, wmOperator *op) -{ - /* Should've been checked before. */ - BLI_assert(view_pan_poll(C)); - - /* set custom-data for operator */ - v2dViewPanData *vpd = MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData"); - op->customdata = vpd; - - /* set pointers to owners */ - vpd->screen = CTX_wm_screen(C); - vpd->area = CTX_wm_area(C); - vpd->region = CTX_wm_region(C); - vpd->v2d = &vpd->region->v2d; - - /* calculate translation factor - based on size of view */ - const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1); - const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); - vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; - vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; - - vpd->v2d->flag |= V2D_IS_NAVIGATING; -} - -/* apply transform to view (i.e. adjust 'cur' rect) */ -static void view_pan_apply_ex(bContext *C, v2dViewPanData *vpd, float dx, float dy) -{ - View2D *v2d = vpd->v2d; - - /* calculate amount to move view by */ - dx *= vpd->facx; - dy *= vpd->facy; - - /* only move view on an axis if change is allowed */ - if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) { - v2d->cur.xmin += dx; - v2d->cur.xmax += dx; - } - if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) { - v2d->cur.ymin += dy; - v2d->cur.ymax += dy; - } - - /* 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); -} - -static void view_pan_apply(bContext *C, wmOperator *op) -{ - v2dViewPanData *vpd = op->customdata; - - view_pan_apply_ex(C, vpd, RNA_int_get(op->ptr, "deltax"), RNA_int_get(op->ptr, "deltay")); -} - -/* Cleanup temp custom-data. */ -static void view_pan_exit(wmOperator *op) -{ - v2dViewPanData *vpd = op->customdata; - vpd->v2d->flag &= ~V2D_IS_NAVIGATING; - MEM_SAFE_FREE(op->customdata); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Pan Operator (modal drag-pan) - * \{ */ - -/* for 'redo' only, with no user input */ -static int view_pan_exec(bContext *C, wmOperator *op) -{ - view_pan_init(C, op); - view_pan_apply(C, op); - view_pan_exit(op); - return OPERATOR_FINISHED; -} - -/* set up modal operator and relevant settings */ -static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmWindow *window = CTX_wm_window(C); - - /* set up customdata */ - view_pan_init(C, op); - - v2dViewPanData *vpd = op->customdata; - View2D *v2d = vpd->v2d; - - /* set initial settings */ - vpd->startx = vpd->lastx = event->xy[0]; - vpd->starty = vpd->lasty = event->xy[1]; - vpd->invoke_event = event->type; - - if (event->type == MOUSEPAN) { - RNA_int_set(op->ptr, "deltax", event->prev_xy[0] - event->xy[0]); - RNA_int_set(op->ptr, "deltay", event->prev_xy[1] - event->xy[1]); - - view_pan_apply(C, op); - view_pan_exit(op); - return OPERATOR_FINISHED; - } - - RNA_int_set(op->ptr, "deltax", 0); - RNA_int_set(op->ptr, "deltay", 0); - - if (v2d->keepofs & V2D_LOCKOFS_X) { - WM_cursor_modal_set(window, WM_CURSOR_NS_SCROLL); - } - else if (v2d->keepofs & V2D_LOCKOFS_Y) { - WM_cursor_modal_set(window, WM_CURSOR_EW_SCROLL); - } - else { - WM_cursor_modal_set(window, WM_CURSOR_NSEW_SCROLL); - } - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - return OPERATOR_RUNNING_MODAL; -} - -/* handle user input - calculations of mouse-movement - * need to be done here, not in the apply callback! */ -static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - v2dViewPanData *vpd = op->customdata; - - /* execute the events */ - switch (event->type) { - case MOUSEMOVE: { - /* calculate new delta transform, then store mouse-coordinates for next-time */ - RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->xy[0])); - RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->xy[1])); - - vpd->lastx = event->xy[0]; - vpd->lasty = event->xy[1]; - - view_pan_apply(C, op); - break; - } - /* XXX: Mode switching isn't implemented. See comments in 36818. - * switch to zoom */ -#if 0 - case LEFTMOUSE: - if (event->val == KM_PRESS) { - /* calculate overall delta mouse-movement for redo */ - RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx)); - RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty)); - - view_pan_exit(op); - WM_cursor_modal_restore(CTX_wm_window(C)); - WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL, event); - return OPERATOR_FINISHED; - } -#endif - default: - if (ELEM(event->type, vpd->invoke_event, EVT_ESCKEY)) { - if (event->val == KM_RELEASE) { - /* calculate overall delta mouse-movement for redo */ - RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx)); - RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty)); - - view_pan_exit(op); - WM_cursor_modal_restore(CTX_wm_window(C)); - - return OPERATOR_FINISHED; - } - } - break; - } - - return OPERATOR_RUNNING_MODAL; -} - -static void view_pan_cancel(bContext *UNUSED(C), wmOperator *op) -{ - view_pan_exit(op); -} - -static void VIEW2D_OT_pan(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Pan View"; - ot->description = "Pan the view"; - ot->idname = "VIEW2D_OT_pan"; - - /* api callbacks */ - ot->exec = view_pan_exec; - ot->invoke = view_pan_invoke; - ot->modal = view_pan_modal; - ot->cancel = view_pan_cancel; - ot->poll = view_pan_poll; - - /* operator is modal */ - ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY; - - /* rna - must keep these in sync with the other operators */ - RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); - RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Edge Pan Operator (modal) - * - * Scroll the region if the mouse is dragged to an edge. "Invisible" operator that always - * passes through. - * \{ */ - -/* set up modal operator and relevant settings */ -static int view_edge_pan_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - op->customdata = MEM_callocN(sizeof(View2DEdgePanData), "View2DEdgePanData"); - View2DEdgePanData *vpd = op->customdata; - UI_view2d_edge_pan_operator_init(C, vpd, op); - - WM_event_add_modal_handler(C, op); - - return (OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH); -} - -static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - View2DEdgePanData *vpd = op->customdata; - - if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) { - vpd->v2d->flag &= ~V2D_IS_NAVIGATING; - MEM_SAFE_FREE(op->customdata); - return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); - } - - UI_view2d_edge_pan_apply_event(C, vpd, event); - - /* This operator is supposed to run together with some drag action. - * On successful handling, always pass events on to other handlers. */ - return OPERATOR_PASS_THROUGH; -} - -static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op) -{ - v2dViewPanData *vpd = op->customdata; - vpd->v2d->flag &= ~V2D_IS_NAVIGATING; - MEM_SAFE_FREE(op->customdata); -} - -static void VIEW2D_OT_edge_pan(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "View Edge Pan"; - ot->description = "Pan the view when the mouse is held at an edge"; - ot->idname = "VIEW2D_OT_edge_pan"; - - /* api callbacks */ - ot->invoke = view_edge_pan_invoke; - ot->modal = view_edge_pan_modal; - ot->cancel = view_edge_pan_cancel; - ot->poll = UI_view2d_edge_pan_poll; - - /* operator is modal */ - ot->flag = OPTYPE_INTERNAL; - UI_view2d_edge_pan_operator_properties(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Pan Operator (single step) - * \{ */ - -/* this operator only needs this single callback, where it calls the view_pan_*() methods */ -static int view_scrollright_exec(bContext *C, wmOperator *op) -{ - v2dViewPanData *vpd; - - /* initialize default settings (and validate if ok to run) */ - view_pan_init(C, op); - - /* also, check if can pan in horizontal axis */ - vpd = op->customdata; - if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { - view_pan_exit(op); - return OPERATOR_PASS_THROUGH; - } - - /* set RNA-Props - only movement in positive x-direction */ - RNA_int_set(op->ptr, "deltax", 40); - RNA_int_set(op->ptr, "deltay", 0); - - /* apply movement, then we're done */ - view_pan_apply(C, op); - view_pan_exit(op); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_scroll_right(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Scroll Right"; - ot->description = "Scroll the view right"; - ot->idname = "VIEW2D_OT_scroll_right"; - - /* api callbacks */ - ot->exec = view_scrollright_exec; - ot->poll = view_pan_poll; - - /* rna - must keep these in sync with the other operators */ - RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); - RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); -} - -/* this operator only needs this single callback, where it calls the view_pan_*() methods */ -static int view_scrollleft_exec(bContext *C, wmOperator *op) -{ - v2dViewPanData *vpd; - - /* initialize default settings (and validate if ok to run) */ - view_pan_init(C, op); - - /* also, check if can pan in horizontal axis */ - vpd = op->customdata; - if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { - view_pan_exit(op); - return OPERATOR_PASS_THROUGH; - } - - /* set RNA-Props - only movement in negative x-direction */ - RNA_int_set(op->ptr, "deltax", -40); - RNA_int_set(op->ptr, "deltay", 0); - - /* apply movement, then we're done */ - view_pan_apply(C, op); - view_pan_exit(op); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_scroll_left(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Scroll Left"; - ot->description = "Scroll the view left"; - ot->idname = "VIEW2D_OT_scroll_left"; - - /* api callbacks */ - ot->exec = view_scrollleft_exec; - ot->poll = view_pan_poll; - - /* rna - must keep these in sync with the other operators */ - RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); - RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); -} - -/* this operator only needs this single callback, where it calls the view_pan_*() methods */ -static int view_scrolldown_exec(bContext *C, wmOperator *op) -{ - v2dViewPanData *vpd; - - /* initialize default settings (and validate if ok to run) */ - view_pan_init(C, op); - - /* also, check if can pan in vertical axis */ - vpd = op->customdata; - if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { - view_pan_exit(op); - return OPERATOR_PASS_THROUGH; - } - - /* set RNA-Props */ - RNA_int_set(op->ptr, "deltax", 0); - RNA_int_set(op->ptr, "deltay", -40); - - PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page"); - if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) { - ARegion *region = CTX_wm_region(C); - RNA_int_set(op->ptr, "deltay", region->v2d.mask.ymin - region->v2d.mask.ymax); - } - - /* apply movement, then we're done */ - view_pan_apply(C, op); - view_pan_exit(op); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_scroll_down(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Scroll Down"; - ot->description = "Scroll the view down"; - ot->idname = "VIEW2D_OT_scroll_down"; - - /* api callbacks */ - ot->exec = view_scrolldown_exec; - ot->poll = view_pan_poll; - - /* rna - must keep these in sync with the other operators */ - RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); - RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); - RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll down one page"); -} - -/* this operator only needs this single callback, where it calls the view_pan_*() methods */ -static int view_scrollup_exec(bContext *C, wmOperator *op) -{ - v2dViewPanData *vpd; - - /* initialize default settings (and validate if ok to run) */ - view_pan_init(C, op); - - /* also, check if can pan in vertical axis */ - vpd = op->customdata; - if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { - view_pan_exit(op); - return OPERATOR_PASS_THROUGH; - } - - /* set RNA-Props */ - RNA_int_set(op->ptr, "deltax", 0); - RNA_int_set(op->ptr, "deltay", 40); - - PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page"); - if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) { - ARegion *region = CTX_wm_region(C); - RNA_int_set(op->ptr, "deltay", BLI_rcti_size_y(®ion->v2d.mask)); - } - - /* apply movement, then we're done */ - view_pan_apply(C, op); - view_pan_exit(op); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_scroll_up(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Scroll Up"; - ot->description = "Scroll the view up"; - ot->idname = "VIEW2D_OT_scroll_up"; - - /* api callbacks */ - ot->exec = view_scrollup_exec; - ot->poll = view_pan_poll; - - /* rna - must keep these in sync with the other operators */ - RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); - RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); - RNA_def_boolean(ot->srna, "page", 0, "Page", "Scroll up one page"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Zoom Shared Utilities - * \{ */ - -/** - * This group of operators come in several forms: - * -# Scroll-wheel 'steps' - rolling mouse-wheel by one step zooms view by predefined amount. - * -# Scroll-wheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y). - * XXX this could be implemented... - * -# Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount. - * - * In order to make sure this works, each operator must define the following RNA-Operator Props: - * - * - zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling. - * It is safe to scale by 0, as these factors are used to determine. - * amount to enlarge 'cur' by. - */ - -/** - * Temporary custom-data for operator. - */ -typedef struct v2dViewZoomData { - View2D *v2d; /* view2d we're operating in */ - ARegion *region; - - /* needed for continuous zoom */ - wmTimer *timer; - double timer_lastdraw; - - int lastx, lasty; /* previous x/y values of mouse in window */ - int invoke_event; /* event type that invoked, for modal exits */ - float dx, dy; /* running tally of previous delta values (for obtaining final zoom) */ - float mx_2d, my_2d; /* initial mouse location in v2d coords */ - bool zoom_to_mouse_pos; -} v2dViewZoomData; - -/** - * Clamp by convention rather than locking flags, - * for ndof and +/- keys - */ -static void view_zoom_axis_lock_defaults(bContext *C, bool r_do_zoom_xy[2]) -{ - ScrArea *area = CTX_wm_area(C); - - r_do_zoom_xy[0] = true; - r_do_zoom_xy[1] = true; - - /* default not to zoom the sequencer vertically */ - if (area && area->spacetype == SPACE_SEQ) { - ARegion *region = CTX_wm_region(C); - - if (region && region->regiontype == RGN_TYPE_WINDOW) { - r_do_zoom_xy[1] = false; - } - } -} - -/* check if step-zoom can be applied */ -static bool view_zoom_poll(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - /* check if there's a region in context to work with */ - if (region == NULL) { - return false; - } - - /* Do not show that in 3DView context. */ - if (CTX_wm_region_view3d(C)) { - return false; - } - - View2D *v2d = ®ion->v2d; - - /* check that 2d-view is zoomable */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) { - return false; - } - - /* view is zoomable */ - return true; -} - -/* initialize panning customdata */ -static void view_zoomdrag_init(bContext *C, wmOperator *op) -{ - /* Should've been checked before. */ - BLI_assert(view_zoom_poll(C)); - - /* set custom-data for operator */ - v2dViewZoomData *vzd = MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData"); - op->customdata = vzd; - - /* set pointers to owners */ - vzd->region = CTX_wm_region(C); - vzd->v2d = &vzd->region->v2d; - /* False by default. Interactive callbacks (ie invoke()) can set it to true. */ - vzd->zoom_to_mouse_pos = false; - - vzd->v2d->flag |= V2D_IS_NAVIGATING; -} - -/* apply transform to view (i.e. adjust 'cur' rect) */ -static void view_zoomstep_apply_ex(bContext *C, - v2dViewZoomData *vzd, - const float facx, - const float facy) -{ - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - const rctf cur_old = v2d->cur; - const int snap_test = ED_region_snap_size_test(region); - - /* calculate amount to move view by, ensuring symmetry so the - * old zoom level is restored after zooming back the same amount - */ - float dx, dy; - if (facx >= 0.0f) { - dx = BLI_rctf_size_x(&v2d->cur) * facx; - dy = BLI_rctf_size_y(&v2d->cur) * facy; - } - else { - dx = (BLI_rctf_size_x(&v2d->cur) / (1.0f + 2.0f * facx)) * facx; - dy = (BLI_rctf_size_y(&v2d->cur) / (1.0f + 2.0f * facy)) * facy; - } - - /* only resize view on an axis if change is allowed */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - if (v2d->keepofs & V2D_LOCKOFS_X) { - v2d->cur.xmax -= 2 * dx; - } - else if (v2d->keepofs & V2D_KEEPOFS_X) { - if (v2d->align & V2D_ALIGN_NO_POS_X) { - v2d->cur.xmin += 2 * dx; - } - else { - v2d->cur.xmax -= 2 * dx; - } - } - else { - - v2d->cur.xmin += dx; - v2d->cur.xmax -= dx; - - if (vzd->zoom_to_mouse_pos) { - /* get zoom fac the same way as in - * ui_view2d_curRect_validate_resize - better keep in sync! */ - const float zoomx = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); - - /* only move view to mouse if zoom fac is inside minzoom/maxzoom */ - if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) || - IN_RANGE_INCL(zoomx, v2d->minzoom, v2d->maxzoom)) { - const float mval_fac = (vzd->mx_2d - cur_old.xmin) / BLI_rctf_size_x(&cur_old); - const float mval_faci = 1.0f - mval_fac; - const float ofs = (mval_fac * dx) - (mval_faci * dx); - - v2d->cur.xmin += ofs; - v2d->cur.xmax += ofs; - } - } - } - } - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - if (v2d->keepofs & V2D_LOCKOFS_Y) { - v2d->cur.ymax -= 2 * dy; - } - else if (v2d->keepofs & V2D_KEEPOFS_Y) { - if (v2d->align & V2D_ALIGN_NO_POS_Y) { - v2d->cur.ymin += 2 * dy; - } - else { - v2d->cur.ymax -= 2 * dy; - } - } - else { - - v2d->cur.ymin += dy; - v2d->cur.ymax -= dy; - - if (vzd->zoom_to_mouse_pos) { - /* get zoom fac the same way as in - * ui_view2d_curRect_validate_resize - better keep in sync! */ - const float zoomy = (float)(BLI_rcti_size_y(&v2d->mask) + 1) / BLI_rctf_size_y(&v2d->cur); - - /* only move view to mouse if zoom fac is inside minzoom/maxzoom */ - if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) || - IN_RANGE_INCL(zoomy, v2d->minzoom, v2d->maxzoom)) { - const float mval_fac = (vzd->my_2d - cur_old.ymin) / BLI_rctf_size_y(&cur_old); - const float mval_faci = 1.0f - mval_fac; - const float ofs = (mval_fac * dy) - (mval_faci * dy); - - v2d->cur.ymin += ofs; - v2d->cur.ymax += ofs; - } - } - } - } - - /* Inform v2d about changes after this operation. */ - UI_view2d_curRect_changed(C, v2d); - - if (ED_region_snap_size_apply(region, snap_test)) { - ScrArea *area = CTX_wm_area(C); - ED_area_tag_redraw(area); - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); - } - - /* request updates to be done... */ - ED_region_tag_redraw_no_rebuild(vzd->region); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); -} - -static void view_zoomstep_apply(bContext *C, wmOperator *op) -{ - v2dViewZoomData *vzd = op->customdata; - view_zoomstep_apply_ex( - C, vzd, RNA_float_get(op->ptr, "zoomfacx"), RNA_float_get(op->ptr, "zoomfacy")); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Zoom Operator (single step) - * \{ */ - -/* Cleanup temp custom-data. */ -static void view_zoomstep_exit(wmOperator *op) -{ - UI_view2d_zoom_cache_reset(); - v2dViewZoomData *vzd = op->customdata; - vzd->v2d->flag &= ~V2D_IS_NAVIGATING; - MEM_SAFE_FREE(op->customdata); -} - -/* this operator only needs this single callback, where it calls the view_zoom_*() methods */ -static int view_zoomin_exec(bContext *C, wmOperator *op) -{ - if (op->customdata == NULL) { /* Might have been setup in _invoke() already. */ - view_zoomdrag_init(C, op); - } - - bool do_zoom_xy[2]; - view_zoom_axis_lock_defaults(C, do_zoom_xy); - - /* set RNA-Props - zooming in by uniform factor */ - RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f); - RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f); - - /* apply movement, then we're done */ - view_zoomstep_apply(C, op); - - view_zoomstep_exit(op); - - return OPERATOR_FINISHED; -} - -static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - view_zoomdrag_init(C, op); - - v2dViewZoomData *vzd = op->customdata; - - if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { - ARegion *region = CTX_wm_region(C); - - /* store initial mouse position (in view space) */ - UI_view2d_region_to_view( - ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); - vzd->zoom_to_mouse_pos = true; - } - - return view_zoomin_exec(C, op); -} - -static void VIEW2D_OT_zoom_in(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Zoom In"; - ot->description = "Zoom in the view"; - ot->idname = "VIEW2D_OT_zoom_in"; - - /* api callbacks */ - ot->invoke = view_zoomin_invoke; - ot->exec = view_zoomin_exec; - ot->poll = view_zoom_poll; - - /* rna - must keep these in sync with the other operators */ - prop = RNA_def_float( - ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); - prop = RNA_def_float( - ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); -} - -/* this operator only needs this single callback, where it calls the view_zoom_*() methods */ -static int view_zoomout_exec(bContext *C, wmOperator *op) -{ - bool do_zoom_xy[2]; - - if (op->customdata == NULL) { /* Might have been setup in _invoke() already. */ - view_zoomdrag_init(C, op); - } - - view_zoom_axis_lock_defaults(C, do_zoom_xy); - - /* set RNA-Props - zooming in by uniform factor */ - RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f); - RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f); - - /* apply movement, then we're done */ - view_zoomstep_apply(C, op); - - view_zoomstep_exit(op); - - return OPERATOR_FINISHED; -} - -static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - view_zoomdrag_init(C, op); - - v2dViewZoomData *vzd = op->customdata; - - if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { - ARegion *region = CTX_wm_region(C); - - /* store initial mouse position (in view space) */ - UI_view2d_region_to_view( - ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); - vzd->zoom_to_mouse_pos = true; - } - - return view_zoomout_exec(C, op); -} - -static void VIEW2D_OT_zoom_out(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Zoom Out"; - ot->description = "Zoom out the view"; - ot->idname = "VIEW2D_OT_zoom_out"; - - /* api callbacks */ - ot->invoke = view_zoomout_invoke; - ot->exec = view_zoomout_exec; - - ot->poll = view_zoom_poll; - - /* rna - must keep these in sync with the other operators */ - prop = RNA_def_float( - ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); - prop = RNA_def_float( - ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Zoom Operator (modal drag-zoom) - * \{ */ - -/** - * MMB Drag - allows non-uniform scaling by dragging mouse - * - * In order to make sure this works, each operator must define the following RNA-Operator Props: - * - `deltax, deltay` - amounts to add to each side of the 'cur' rect - */ - -/* apply transform to view (i.e. adjust 'cur' rect) */ -static void view_zoomdrag_apply(bContext *C, wmOperator *op) -{ - v2dViewZoomData *vzd = op->customdata; - View2D *v2d = vzd->v2d; - const int snap_test = ED_region_snap_size_test(vzd->region); - - const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init"); - const bool zoom_to_pos = use_cursor_init && vzd->zoom_to_mouse_pos; - - /* get amount to move view by */ - float dx = RNA_float_get(op->ptr, "deltax") / U.dpi_fac; - float dy = RNA_float_get(op->ptr, "deltay") / U.dpi_fac; - - /* Check if the 'timer' is initialized, as zooming with the trackpad - * never uses the "Continuous" zoom method, and the 'timer' is not initialized. */ - if ((U.viewzoom == USER_ZOOM_CONTINUE) && vzd->timer) { /* XXX store this setting as RNA prop? */ - const double time = PIL_check_seconds_timer(); - const float time_step = (float)(time - vzd->timer_lastdraw); - - dx *= time_step * 0.5f; - dy *= time_step * 0.5f; - - vzd->timer_lastdraw = time; - } - - /* only move view on an axis if change is allowed */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - if (v2d->keepofs & V2D_LOCKOFS_X) { - v2d->cur.xmax -= 2 * dx; - } - else { - if (zoom_to_pos) { - const float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); - const float mval_faci = 1.0f - mval_fac; - const float ofs = (mval_fac * dx) - (mval_faci * dx); - - v2d->cur.xmin += ofs + dx; - v2d->cur.xmax += ofs - dx; - } - else { - v2d->cur.xmin += dx; - v2d->cur.xmax -= dx; - } - } - } - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - if (v2d->keepofs & V2D_LOCKOFS_Y) { - v2d->cur.ymax -= 2 * dy; - } - else { - if (zoom_to_pos) { - const float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); - const float mval_faci = 1.0f - mval_fac; - const float ofs = (mval_fac * dy) - (mval_faci * dy); - - v2d->cur.ymin += ofs + dy; - v2d->cur.ymax += ofs - dy; - } - else { - v2d->cur.ymin += dy; - v2d->cur.ymax -= dy; - } - } - } - - /* Inform v2d about changes after this operation. */ - UI_view2d_curRect_changed(C, v2d); - - if (ED_region_snap_size_apply(vzd->region, snap_test)) { - ScrArea *area = CTX_wm_area(C); - ED_area_tag_redraw(area); - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); - } - - /* request updates to be done... */ - ED_region_tag_redraw_no_rebuild(vzd->region); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); -} - -/* Cleanup temp custom-data. */ -static void view_zoomdrag_exit(bContext *C, wmOperator *op) -{ - UI_view2d_zoom_cache_reset(); - - if (op->customdata) { - v2dViewZoomData *vzd = op->customdata; - vzd->v2d->flag &= ~V2D_IS_NAVIGATING; - - if (vzd->timer) { - WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer); - } - - MEM_freeN(op->customdata); - op->customdata = NULL; - } -} - -static void view_zoomdrag_cancel(bContext *C, wmOperator *op) -{ - view_zoomdrag_exit(C, op); -} - -/* for 'redo' only, with no user input */ -static int view_zoomdrag_exec(bContext *C, wmOperator *op) -{ - view_zoomdrag_init(C, op); - view_zoomdrag_apply(C, op); - view_zoomdrag_exit(C, op); - return OPERATOR_FINISHED; -} - -/* set up modal operator and relevant settings */ -static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - wmWindow *window = CTX_wm_window(C); - - /* set up customdata */ - view_zoomdrag_init(C, op); - - v2dViewZoomData *vzd = op->customdata; - View2D *v2d = vzd->v2d; - - if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { - ARegion *region = CTX_wm_region(C); - - /* Store initial mouse position (in view space). */ - UI_view2d_region_to_view( - ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); - vzd->zoom_to_mouse_pos = true; - } - - if (ELEM(event->type, MOUSEZOOM, MOUSEPAN)) { - vzd->lastx = event->prev_xy[0]; - vzd->lasty = event->prev_xy[1]; - - float facx, facy; - float zoomfac = 0.01f; - - /* Some view2d's (graph) don't have min/max zoom, or extreme ones. */ - if (v2d->maxzoom > 0.0f) { - zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f); - } - - if (event->type == MOUSEPAN) { - facx = zoomfac * WM_event_absolute_delta_x(event); - facy = zoomfac * WM_event_absolute_delta_y(event); - - if (U.uiflag & USER_ZOOM_INVERT) { - facx *= -1.0f; - facy *= -1.0f; - } - } - else { /* MOUSEZOOM */ - facx = facy = zoomfac * WM_event_absolute_delta_x(event); - } - - /* Only respect user setting zoom axis if the view does not have any zoom restrictions - * any will be scaled uniformly. */ - if (((v2d->keepzoom & (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y)) == 0) && - (v2d->keepzoom & V2D_KEEPASPECT)) { - if (U.uiflag & USER_ZOOM_HORIZ) { - facy = 0.0f; - } - else { - facx = 0.0f; - } - } - - /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or - * landscape exceptions */ - if (v2d->keepzoom & V2D_KEEPASPECT) { - if (fabsf(facx) > fabsf(facy)) { - facy = facx; - } - else { - facx = facy; - } - } - - const float dx = facx * BLI_rctf_size_x(&v2d->cur); - const float dy = facy * BLI_rctf_size_y(&v2d->cur); - - RNA_float_set(op->ptr, "deltax", dx); - RNA_float_set(op->ptr, "deltay", dy); - - view_zoomdrag_apply(C, op); - view_zoomdrag_exit(C, op); - return OPERATOR_FINISHED; - } - - /* set initial settings */ - vzd->lastx = event->xy[0]; - vzd->lasty = event->xy[1]; - RNA_float_set(op->ptr, "deltax", 0); - RNA_float_set(op->ptr, "deltay", 0); - - /* for modal exit test */ - vzd->invoke_event = event->type; - - if (v2d->keepofs & V2D_LOCKOFS_X) { - WM_cursor_modal_set(window, WM_CURSOR_NS_SCROLL); - } - else if (v2d->keepofs & V2D_LOCKOFS_Y) { - WM_cursor_modal_set(window, WM_CURSOR_EW_SCROLL); - } - else { - WM_cursor_modal_set(window, WM_CURSOR_NSEW_SCROLL); - } - - /* add temp handler */ - WM_event_add_modal_handler(C, op); - - if (U.viewzoom == USER_ZOOM_CONTINUE) { - /* needs a timer to continue redrawing */ - vzd->timer = WM_event_add_timer(CTX_wm_manager(C), window, TIMER, 0.01f); - vzd->timer_lastdraw = PIL_check_seconds_timer(); - } - - return OPERATOR_RUNNING_MODAL; -} - -/* handle user input - calculations of mouse-movement need to be done here, - * not in the apply callback! */ -static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - v2dViewZoomData *vzd = op->customdata; - View2D *v2d = vzd->v2d; - - /* execute the events */ - if (event->type == TIMER && event->customdata == vzd->timer) { - view_zoomdrag_apply(C, op); - } - else if (event->type == MOUSEMOVE) { - float dx, dy; - float zoomfac = 0.01f; - - /* some view2d's (graph) don't have min/max zoom, or extreme ones */ - if (v2d->maxzoom > 0.0f) { - zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f); - } - - /* calculate new delta transform, based on zooming mode */ - if (U.viewzoom == USER_ZOOM_SCALE) { - /* 'scale' zooming */ - float dist; - float len_old[2]; - float len_new[2]; - - /* x-axis transform */ - dist = BLI_rcti_size_x(&v2d->mask) / 2.0f; - len_old[0] = zoomfac * fabsf(vzd->lastx - vzd->region->winrct.xmin - dist); - len_new[0] = zoomfac * fabsf(event->xy[0] - vzd->region->winrct.xmin - dist); - - /* y-axis transform */ - dist = BLI_rcti_size_y(&v2d->mask) / 2.0f; - len_old[1] = zoomfac * fabsf(vzd->lasty - vzd->region->winrct.ymin - dist); - len_new[1] = zoomfac * fabsf(event->xy[1] - vzd->region->winrct.ymin - dist); - - /* Calculate distance */ - if (v2d->keepzoom & V2D_KEEPASPECT) { - dist = len_v2(len_new) - len_v2(len_old); - dx = dy = dist; - } - else { - dx = len_new[0] - len_old[0]; - dy = len_new[1] - len_old[1]; - } - - dx *= BLI_rctf_size_x(&v2d->cur); - dy *= BLI_rctf_size_y(&v2d->cur); - } - else { /* USER_ZOOM_CONTINUE or USER_ZOOM_DOLLY */ - float facx = zoomfac * (event->xy[0] - vzd->lastx); - float facy = zoomfac * (event->xy[1] - vzd->lasty); - - /* Only respect user setting zoom axis if the view does not have any zoom restrictions - * any will be scaled uniformly */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && - (v2d->keepzoom & V2D_KEEPASPECT)) { - if (U.uiflag & USER_ZOOM_HORIZ) { - facy = 0.0f; - } - else { - facx = 0.0f; - } - } - - /* support zoom to always zoom entirely - the v2d code uses portrait or - * landscape exceptions */ - if (v2d->keepzoom & V2D_KEEPASPECT) { - if (fabsf(facx) > fabsf(facy)) { - facy = facx; - } - else { - facx = facy; - } - } - - dx = facx * BLI_rctf_size_x(&v2d->cur); - dy = facy * BLI_rctf_size_y(&v2d->cur); - } - - if (U.uiflag & USER_ZOOM_INVERT) { - dx *= -1.0f; - dy *= -1.0f; - } - - /* set transform amount, and add current deltas to stored total delta (for redo) */ - RNA_float_set(op->ptr, "deltax", dx); - RNA_float_set(op->ptr, "deltay", dy); - - vzd->dx += dx; - vzd->dy += dy; - - /* Store mouse coordinates for next time, if not doing continuous zoom: - * - Continuous zoom only depends on distance of mouse - * to starting point to determine rate of change. - */ - if (U.viewzoom != USER_ZOOM_CONTINUE) { /* XXX store this setting as RNA prop? */ - vzd->lastx = event->xy[0]; - vzd->lasty = event->xy[1]; - } - - /* apply zooming */ - view_zoomdrag_apply(C, op); - } - else if (ELEM(event->type, vzd->invoke_event, EVT_ESCKEY)) { - if (event->val == KM_RELEASE) { - - /* for redo, store the overall deltas - need to respect zoom-locks here... */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - RNA_float_set(op->ptr, "deltax", vzd->dx); - } - else { - RNA_float_set(op->ptr, "deltax", 0); - } - - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - RNA_float_set(op->ptr, "deltay", vzd->dy); - } - else { - RNA_float_set(op->ptr, "deltay", 0); - } - - /* free customdata */ - view_zoomdrag_exit(C, op); - WM_cursor_modal_restore(CTX_wm_window(C)); - - return OPERATOR_FINISHED; - } - } - - return OPERATOR_RUNNING_MODAL; -} - -static void VIEW2D_OT_zoom(wmOperatorType *ot) -{ - PropertyRNA *prop; - /* identifiers */ - ot->name = "Zoom 2D View"; - ot->description = "Zoom in/out the view"; - ot->idname = "VIEW2D_OT_zoom"; - - /* api callbacks */ - ot->exec = view_zoomdrag_exec; - ot->invoke = view_zoomdrag_invoke; - ot->modal = view_zoomdrag_modal; - ot->cancel = view_zoomdrag_cancel; - - ot->poll = view_zoom_poll; - - /* operator is repeatable */ - ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY; - - /* rna - must keep these in sync with the other operators */ - prop = RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); - prop = RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN); - - WM_operator_properties_use_cursor_init(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Border Zoom Operator - * \{ */ - -/** - * The user defines a rect using standard box select tools, and we use this rect to - * define the new zoom-level of the view in the following ways: - * - * -# LEFTMOUSE - zoom in to view - * -# RIGHTMOUSE - zoom out of view - * - * Currently, these key mappings are hardcoded, but it shouldn't be too important to - * have custom keymappings for this... - */ - -static int view_borderzoom_exec(bContext *C, wmOperator *op) -{ - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - rctf cur_new = v2d->cur; - const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); - - /* convert coordinates of rect to 'tot' rect coordinates */ - rctf rect; - WM_operator_properties_border_to_rctf(op, &rect); - UI_view2d_region_to_view_rctf(v2d, &rect, &rect); - - /* check if zooming in/out view */ - const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out"); - - if (zoom_in) { - /* zoom in: - * - 'cur' rect will be defined by the coordinates of the border region - * - just set the 'cur' rect to have the same coordinates as the border region - * if zoom is allowed to be changed - */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - cur_new.xmin = rect.xmin; - cur_new.xmax = rect.xmax; - } - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - cur_new.ymin = rect.ymin; - cur_new.ymax = rect.ymax; - } - } - else { - /* zoom out: - * - the current 'cur' rect coordinates are going to end up where the 'rect' ones are, - * but the 'cur' rect coordinates will need to be adjusted to take in more of the view - * - calculate zoom factor, and adjust using center-point - */ - float zoom, center, size; - - /* TODO: is this zoom factor calculation valid? - * It seems to produce same results every time... */ - if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { - size = BLI_rctf_size_x(&cur_new); - zoom = size / BLI_rctf_size_x(&rect); - center = BLI_rctf_cent_x(&cur_new); - - cur_new.xmin = center - (size * zoom); - cur_new.xmax = center + (size * zoom); - } - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { - size = BLI_rctf_size_y(&cur_new); - zoom = size / BLI_rctf_size_y(&rect); - center = BLI_rctf_cent_y(&cur_new); - - cur_new.ymin = center - (size * zoom); - cur_new.ymax = center + (size * zoom); - } - } - - UI_view2d_smooth_view(C, region, &cur_new, smooth_viewtx); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_zoom_border(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Zoom to Border"; - ot->description = "Zoom in the view to the nearest item contained in the border"; - ot->idname = "VIEW2D_OT_zoom_border"; - - /* api callbacks */ - ot->invoke = WM_gesture_box_invoke; - ot->exec = view_borderzoom_exec; - ot->modal = WM_gesture_box_modal; - ot->cancel = WM_gesture_box_cancel; - - ot->poll = view_zoom_poll; - - /* rna */ - WM_operator_properties_gesture_box_zoom(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name NDOF Pan/Zoom Operator - * \{ */ - -#ifdef WITH_INPUT_NDOF -static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - if (event->type != NDOF_MOTION) { - return OPERATOR_CANCELLED; - } - - const wmNDOFMotionData *ndof = event->customdata; - - /* tune these until it feels right */ - const float zoom_sensitivity = 0.5f; - const float speed = 10.0f; /* match view3d ortho */ - const bool has_translate = !is_zero_v2(ndof->tvec) && view_pan_poll(C); - const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C); - - if (has_translate) { - float pan_vec[3]; - - WM_event_ndof_pan_get(ndof, pan_vec, false); - - pan_vec[0] *= speed; - pan_vec[1] *= speed; - - view_pan_init(C, op); - - v2dViewPanData *vpd = op->customdata; - view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]); - - view_pan_exit(op); - } - - if (has_zoom) { - float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2]; - - bool do_zoom_xy[2]; - - if (U.ndof_flag & NDOF_ZOOM_INVERT) { - zoom_factor = -zoom_factor; - } - - view_zoom_axis_lock_defaults(C, do_zoom_xy); - - view_zoomdrag_init(C, op); - - v2dViewZoomData *vzd = op->customdata; - view_zoomstep_apply_ex( - C, vzd, do_zoom_xy[0] ? zoom_factor : 0.0f, do_zoom_xy[1] ? zoom_factor : 0.0f); - - view_zoomstep_exit(op); - } - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_ndof(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "NDOF Pan/Zoom"; - ot->idname = "VIEW2D_OT_ndof"; - ot->description = "Use a 3D mouse device to pan/zoom the view"; - - /* api callbacks */ - ot->invoke = view2d_ndof_invoke; - ot->poll = view2d_poll; - - /* flags */ - ot->flag = OPTYPE_LOCK_BYPASS; -} -#endif /* WITH_INPUT_NDOF */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Smooth View Operator - * \{ */ - -struct SmoothView2DStore { - rctf orig_cur, new_cur; - - double time_allowed; -}; - -/** - * function to get a factor out of a rectangle - * - * NOTE: this doesn't always work as well as it might because the target size - * may not be reached because of clamping the desired rect, we _could_ - * attempt to clamp the rect before working out the zoom factor but its - * not really worthwhile for the few cases this happens. - */ -static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b) -{ - const float size_a[2] = {BLI_rctf_size_x(rect_a), BLI_rctf_size_y(rect_a)}; - const float size_b[2] = {BLI_rctf_size_x(rect_b), BLI_rctf_size_y(rect_b)}; - const float cent_a[2] = {BLI_rctf_cent_x(rect_a), BLI_rctf_cent_y(rect_a)}; - const float cent_b[2] = {BLI_rctf_cent_x(rect_b), BLI_rctf_cent_y(rect_b)}; - - float fac_max = 0.0f; - - for (int i = 0; i < 2; i++) { - /* axis translation normalized to scale */ - float tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]); - fac_max = max_ff(fac_max, tfac); - if (fac_max >= 1.0f) { - break; - } - - /* axis scale difference, x2 so doubling or half gives 1.0f */ - tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f; - fac_max = max_ff(fac_max, tfac); - if (fac_max >= 1.0f) { - break; - } - } - return min_ff(fac_max, 1.0f); -} - -void UI_view2d_smooth_view(bContext *C, ARegion *region, const rctf *cur, const int smooth_viewtx) -{ - wmWindowManager *wm = CTX_wm_manager(C); - wmWindow *win = CTX_wm_window(C); - - View2D *v2d = ®ion->v2d; - struct SmoothView2DStore sms = {{0}}; - bool ok = false; - float fac = 1.0f; - - /* initialize sms */ - sms.new_cur = v2d->cur; - - /* store the options we want to end with */ - if (cur) { - sms.new_cur = *cur; - } - - if (cur) { - fac = smooth_view_rect_to_fac(&v2d->cur, cur); - } - - if (smooth_viewtx && fac > FLT_EPSILON) { - bool changed = false; - - if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false) { - changed = true; - } - - /* The new view is different from the old one - * so animate the view */ - if (changed) { - sms.orig_cur = v2d->cur; - - sms.time_allowed = (double)smooth_viewtx / 1000.0; - - /* scale the time allowed the change in view */ - sms.time_allowed *= (double)fac; - - /* keep track of running timer! */ - if (v2d->sms == NULL) { - v2d->sms = MEM_mallocN(sizeof(struct SmoothView2DStore), "smoothview v2d"); - } - *v2d->sms = sms; - if (v2d->smooth_timer) { - WM_event_remove_timer(wm, win, v2d->smooth_timer); - } - /* TIMER1 is hard-coded in key-map. */ - v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); - - ok = true; - } - } - - /* if we get here nothing happens */ - if (ok == false) { - v2d->cur = sms.new_cur; - - UI_view2d_curRect_changed(C, v2d); - ED_region_tag_redraw_no_rebuild(region); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); - } -} - -/* only meant for timer usage */ -static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) -{ - wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - struct SmoothView2DStore *sms = v2d->sms; - - /* escape if not our timer */ - if (v2d->smooth_timer == NULL || v2d->smooth_timer != event->customdata) { - return OPERATOR_PASS_THROUGH; - } - - float step; - if (sms->time_allowed != 0.0) { - step = (float)((v2d->smooth_timer->duration) / sms->time_allowed); - } - else { - step = 1.0f; - } - - /* end timer */ - if (step >= 1.0f) { - v2d->cur = sms->new_cur; - - MEM_freeN(v2d->sms); - v2d->sms = NULL; - - WM_event_remove_timer(CTX_wm_manager(C), win, v2d->smooth_timer); - v2d->smooth_timer = NULL; - - /* Event handling won't know if a UI item has been moved under the pointer. */ - WM_event_add_mousemove(win); - } - else { - /* ease in/out */ - step = (3.0f * step * step - 2.0f * step * step * step); - - BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step); - } - - UI_view2d_curRect_changed(C, v2d); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); - ED_region_tag_redraw_no_rebuild(region); - - if (v2d->sms == NULL) { - UI_view2d_zoom_cache_reset(); - } - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_smoothview(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Smooth View 2D"; - ot->idname = "VIEW2D_OT_smoothview"; - - /* api callbacks */ - ot->invoke = view2d_smoothview_invoke; - ot->poll = view2d_poll; - - /* flags */ - ot->flag = OPTYPE_INTERNAL; - - /* rna */ - WM_operator_properties_gesture_box(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Scroll Bar Move Operator - * \{ */ - -/** - * Scrollers should behave in the following ways, when clicked on with LMB (and dragged): - * -# 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, - * enlarge 'cur' rect on the relevant side. - * -# 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite). - * - * In order to make sure this works, each operator must define the following RNA-Operator Props: - * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor) - */ - -/* customdata for scroller-invoke data */ -typedef struct v2dScrollerMove { - /** View2D data that this operation affects */ - View2D *v2d; - /** region that the scroller is in */ - ARegion *region; - - /** scroller that mouse is in ('h' or 'v') */ - char scroller; - - /* XXX find some way to provide visual feedback of this (active color?) */ - /** -1 is min zoomer, 0 is bar, 1 is max zoomer */ - short zone; - - /** view adjustment factor, based on size of region */ - float fac; - /** for pixel rounding (avoid visible UI jitter) */ - float fac_round; - /** amount moved by mouse on axis of interest */ - float delta; - - /** width of the scrollbar itself, used for page up/down clicks */ - float scrollbarwidth; - /** initial location of scrollbar x/y, mouse relative */ - int scrollbar_orig; - - /** previous mouse coordinates (in screen coordinates) for determining movement */ - int lastx, lasty; -} v2dScrollerMove; - -/** - * #View2DScrollers is typedef'd in UI_view2d.h - * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, - * as we only need focus bubble info. - * - * \warning The start of this struct must not change, - * so that it stays in sync with the 'real' version. - * For now, we don't need to have a separate (internal) header for structs like this... - */ -struct View2DScrollers { - int vert_min, vert_max; /* vertical scrollbar */ - int hor_min, hor_max; /* horizontal scrollbar */ - - /* These values are written into, even if we don't use them. */ - rcti _hor, _vert; -}; - -/* quick enum for vsm->zone (scroller handles) */ -enum { - SCROLLHANDLE_MIN = -1, - SCROLLHANDLE_BAR, - SCROLLHANDLE_MAX, - SCROLLHANDLE_MIN_OUTSIDE, - SCROLLHANDLE_MAX_OUTSIDE, -} /*eV2DScrollerHandle_Zone*/; - -/** - * Check if mouse is within scroller handle. - * - * \param mouse: relevant mouse coordinate in region space. - * \param sc_min, sc_max: extents of scroller 'groove' (potential available space for scroller). - * \param sh_min, sh_max: positions of scrollbar handles. - */ -static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max) -{ - /* firstly, check if - * - 'bubble' fills entire scroller - * - 'bubble' completely out of view on either side - */ - bool in_view = true; - if (sh_min <= sc_min && sc_max <= sh_max) { - in_view = false; - } - else if (sh_max <= sc_min || sc_max <= sh_min) { - in_view = false; - } - - if (!in_view) { - return SCROLLHANDLE_BAR; - } - - /* check if mouse is in or past either handle */ - /* TODO: check if these extents are still valid or not */ - bool in_max = ((mouse >= (sh_max - V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && - (mouse <= (sh_max + V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); - bool in_min = ((mouse <= (sh_min + V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && - (mouse >= (sh_min - V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); - bool in_bar = ((mouse < (sh_max - V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && - (mouse > (sh_min + V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); - const bool out_min = mouse < (sh_min - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); - const bool out_max = mouse > (sh_max + V2D_SCROLL_HANDLE_SIZE_HOTSPOT); - - if (in_bar) { - return SCROLLHANDLE_BAR; - } - if (in_max) { - return SCROLLHANDLE_MAX; - } - if (in_min) { - return SCROLLHANDLE_MIN; - } - if (out_min) { - return SCROLLHANDLE_MIN_OUTSIDE; - } - if (out_max) { - return SCROLLHANDLE_MAX_OUTSIDE; - } - - /* unlikely to happen, though we just cover it in case */ - return SCROLLHANDLE_BAR; -} - -static bool scroller_activate_poll(bContext *C) -{ - if (!view2d_poll(C)) { - return false; - } - - wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - wmEvent *event = win->eventstate; - - /* check if mouse in scrollbars, if they're enabled */ - return (UI_view2d_mouse_in_scrollers(region, v2d, event->xy) != 0); -} - -/* initialize customdata for scroller manipulation operator */ -static void scroller_activate_init(bContext *C, - wmOperator *op, - const wmEvent *event, - const char in_scroller) -{ - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - - /* set custom-data for operator */ - v2dScrollerMove *vsm = MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove"); - op->customdata = vsm; - - /* set general data */ - vsm->v2d = v2d; - vsm->region = region; - vsm->scroller = in_scroller; - - /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */ - vsm->lastx = event->xy[0]; - vsm->lasty = event->xy[1]; - /* 'zone' depends on where mouse is relative to bubble - * - zooming must be allowed on this axis, otherwise, default to pan - */ - View2DScrollers scrollers; - UI_view2d_scrollers_calc(v2d, NULL, &scrollers); - - /* Use a union of 'cur' & 'tot' in case the current view is far outside 'tot'. In this cases - * moving the scroll bars has far too little effect and the view can get stuck T31476. */ - rctf tot_cur_union = v2d->tot; - BLI_rctf_union(&tot_cur_union, &v2d->cur); - - if (in_scroller == 'h') { - /* horizontal scroller - calculate adjustment factor first */ - const float mask_size = (float)BLI_rcti_size_x(&v2d->hor); - vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size; - - /* pixel rounding */ - vsm->fac_round = (BLI_rctf_size_x(&v2d->cur)) / (float)(BLI_rcti_size_x(®ion->winrct) + 1); - - /* get 'zone' (i.e. which part of scroller is activated) */ - vsm->zone = mouse_in_scroller_handle( - event->mval[0], v2d->hor.xmin, v2d->hor.xmax, scrollers.hor_min, scrollers.hor_max); - - if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { - /* default to scroll, as handles not usable */ - vsm->zone = SCROLLHANDLE_BAR; - } - - vsm->scrollbarwidth = scrollers.hor_max - scrollers.hor_min; - vsm->scrollbar_orig = ((scrollers.hor_max + scrollers.hor_min) / 2) + region->winrct.xmin; - } - else { - /* vertical scroller - calculate adjustment factor first */ - const float mask_size = (float)BLI_rcti_size_y(&v2d->vert); - vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size; - - /* pixel rounding */ - vsm->fac_round = (BLI_rctf_size_y(&v2d->cur)) / (float)(BLI_rcti_size_y(®ion->winrct) + 1); - - /* get 'zone' (i.e. which part of scroller is activated) */ - vsm->zone = mouse_in_scroller_handle( - event->mval[1], v2d->vert.ymin, v2d->vert.ymax, scrollers.vert_min, scrollers.vert_max); - - if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { - /* default to scroll, as handles not usable */ - vsm->zone = SCROLLHANDLE_BAR; - } - - vsm->scrollbarwidth = scrollers.vert_max - scrollers.vert_min; - vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin; - } - - vsm->v2d->flag |= V2D_IS_NAVIGATING; - - ED_region_tag_redraw_no_rebuild(region); -} - -/* Cleanup temp custom-data. */ -static void scroller_activate_exit(bContext *C, wmOperator *op) -{ - if (op->customdata) { - v2dScrollerMove *vsm = op->customdata; - - vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE); - vsm->v2d->flag &= ~V2D_IS_NAVIGATING; - - MEM_freeN(op->customdata); - op->customdata = NULL; - - ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); - } -} - -static void scroller_activate_cancel(bContext *C, wmOperator *op) -{ - scroller_activate_exit(C, op); -} - -/* apply transform to view (i.e. adjust 'cur' rect) */ -static void scroller_activate_apply(bContext *C, wmOperator *op) -{ - v2dScrollerMove *vsm = op->customdata; - View2D *v2d = vsm->v2d; - - /* calculate amount to move view by */ - float temp = vsm->fac * vsm->delta; - - /* round to pixel */ - temp = roundf(temp / vsm->fac_round) * vsm->fac_round; - - /* type of movement */ - switch (vsm->zone) { - case SCROLLHANDLE_MIN: - /* only expand view on axis if zoom is allowed */ - if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) { - v2d->cur.xmin -= temp; - } - if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) { - v2d->cur.ymin -= temp; - } - break; - - case SCROLLHANDLE_MAX: - - /* only expand view on axis if zoom is allowed */ - if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) { - v2d->cur.xmax += temp; - } - if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) { - v2d->cur.ymax += temp; - } - break; - - case SCROLLHANDLE_MIN_OUTSIDE: - case SCROLLHANDLE_MAX_OUTSIDE: - case SCROLLHANDLE_BAR: - default: - /* only move view on an axis if panning is allowed */ - if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) { - v2d->cur.xmin += temp; - v2d->cur.xmax += temp; - } - if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) { - v2d->cur.ymin += temp; - v2d->cur.ymax += temp; - } - break; - } - - /* Inform v2d about changes after this operation. */ - UI_view2d_curRect_changed(C, v2d); - - /* request updates to be done... */ - ED_region_tag_redraw_no_rebuild(vsm->region); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); -} - -/** - * Handle user input for scrollers - calculations of mouse-movement need to be done here, - * not in the apply callback! - */ -static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - v2dScrollerMove *vsm = op->customdata; - - /* execute the events */ - switch (event->type) { - case MOUSEMOVE: { - /* calculate new delta transform, then store mouse-coordinates for next-time */ - if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) { - /* if using bar (i.e. 'panning') or 'max' zoom widget */ - switch (vsm->scroller) { - case 'h': /* horizontal scroller - so only horizontal movement - * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->xy[0] - vsm->lastx); - break; - case 'v': /* vertical scroller - so only vertical movement - * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->xy[1] - vsm->lasty); - break; - } - } - else if (vsm->zone == SCROLLHANDLE_MIN) { - /* using 'min' zoom widget */ - switch (vsm->scroller) { - case 'h': /* horizontal scroller - so only horizontal movement - * ('cur' moves with mouse) */ - vsm->delta = (float)(vsm->lastx - event->xy[0]); - break; - case 'v': /* vertical scroller - so only vertical movement - * ('cur' moves with to mouse) */ - vsm->delta = (float)(vsm->lasty - event->xy[1]); - break; - } - } - - /* store previous coordinates */ - vsm->lastx = event->xy[0]; - vsm->lasty = event->xy[1]; - - scroller_activate_apply(C, op); - break; - } - case LEFTMOUSE: - case MIDDLEMOUSE: - if (event->val == KM_RELEASE) { - /* single-click was in empty space outside bubble, so scroll by 1 'page' */ - if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) { - if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE) { - vsm->delta = -vsm->scrollbarwidth * 0.8f; - } - else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE) { - vsm->delta = vsm->scrollbarwidth * 0.8f; - } - - scroller_activate_apply(C, op); - scroller_activate_exit(C, op); - return OPERATOR_FINISHED; - } - - /* Otherwise, end the drag action. */ - if (vsm->lastx || vsm->lasty) { - scroller_activate_exit(C, op); - return OPERATOR_FINISHED; - } - } - break; - } - - return OPERATOR_RUNNING_MODAL; -} - -/* a click (or click drag in progress) - * should have occurred, so check if it happened in scrollbar */ -static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - - /* check if mouse in scrollbars, if they're enabled */ - const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->xy); - - /* if in a scroller, init customdata then set modal handler which will - * catch mouse-down to start doing useful stuff */ - if (in_scroller) { - /* initialize customdata */ - scroller_activate_init(C, op, event, in_scroller); - v2dScrollerMove *vsm = (v2dScrollerMove *)op->customdata; - - /* Support for quick jump to location - GTK and QT do this on Linux. */ - if (event->type == MIDDLEMOUSE) { - switch (vsm->scroller) { - case 'h': /* horizontal scroller - so only horizontal movement - * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->xy[0] - vsm->scrollbar_orig); - break; - case 'v': /* vertical scroller - so only vertical movement - * ('cur' moves opposite to mouse) */ - vsm->delta = (float)(event->xy[1] - vsm->scrollbar_orig); - break; - } - scroller_activate_apply(C, op); - - vsm->zone = SCROLLHANDLE_BAR; - } - - /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue - * NOTE: see view2d.c for latest conditions, and keep this in sync with that - */ - if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { - if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) == 0) || - ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) == 0)) { - /* switch to bar (i.e. no scaling gets handled) */ - vsm->zone = SCROLLHANDLE_BAR; - } - } - - /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */ - if (vsm->zone == SCROLLHANDLE_BAR) { - if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) || - ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y))) { - /* free customdata initialized */ - scroller_activate_exit(C, op); - - /* can't catch this event for ourselves, so let it go to someone else? */ - return OPERATOR_PASS_THROUGH; - } - } - - /* zone is also inappropriate if scroller is not visible... */ - if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_FULLR)) || - ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_FULLR))) { - /* free customdata initialized */ - scroller_activate_exit(C, op); - - /* can't catch this event for ourselves, so let it go to someone else? */ - /* XXX NOTE: if handlers use mask rect to clip input, input will fail for this case. */ - return OPERATOR_PASS_THROUGH; - } - - /* activate the scroller */ - if (vsm->scroller == 'h') { - v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE; - } - else { - v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE; - } - - /* still ok, so can add */ - WM_event_add_modal_handler(C, op); - return OPERATOR_RUNNING_MODAL; - } - - /* not in scroller, so nothing happened... - * (pass through let's something else catch event) */ - return OPERATOR_PASS_THROUGH; -} - -/* LMB-Drag in Scrollers - not repeatable operator! */ -static void VIEW2D_OT_scroller_activate(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Scroller Activate"; - ot->description = "Scroll view by mouse click and drag"; - ot->idname = "VIEW2D_OT_scroller_activate"; - - /* flags */ - ot->flag = OPTYPE_BLOCKING; - - /* api callbacks */ - ot->invoke = scroller_activate_invoke; - ot->modal = scroller_activate_modal; - ot->cancel = scroller_activate_cancel; - - ot->poll = scroller_activate_poll; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View Reset Operator - * \{ */ - -static int reset_exec(bContext *C, wmOperator *UNUSED(op)) -{ - const uiStyle *style = UI_style_get(); - ARegion *region = CTX_wm_region(C); - View2D *v2d = ®ion->v2d; - const int snap_test = ED_region_snap_size_test(region); - - /* zoom 1.0 */ - const int winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1); - const int winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1); - - v2d->cur.xmax = v2d->cur.xmin + winx; - v2d->cur.ymax = v2d->cur.ymin + winy; - - /* align */ - if (v2d->align) { - /* posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { - v2d->cur.xmax = 0.0f; - v2d->cur.xmin = -winx * style->panelzoom; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { - v2d->cur.xmax = winx * style->panelzoom; - v2d->cur.xmin = 0.0f; - } - - /* - posx and negx flags are mutually exclusive, so watch out */ - if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { - v2d->cur.ymax = 0.0f; - v2d->cur.ymin = -winy * style->panelzoom; - } - else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { - v2d->cur.ymax = winy * style->panelzoom; - v2d->cur.ymin = 0.0f; - } - } - - /* Inform v2d about changes after this operation. */ - UI_view2d_curRect_changed(C, v2d); - - if (ED_region_snap_size_apply(region, snap_test)) { - ScrArea *area = CTX_wm_area(C); - ED_area_tag_redraw(area); - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, NULL); - } - - /* request updates to be done... */ - ED_region_tag_redraw(region); - UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); - - UI_view2d_zoom_cache_reset(); - - return OPERATOR_FINISHED; -} - -static void VIEW2D_OT_reset(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Reset View"; - ot->description = "Reset the view"; - ot->idname = "VIEW2D_OT_reset"; - - /* api callbacks */ - ot->exec = reset_exec; - ot->poll = view2d_poll; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Registration - * \{ */ - -void ED_operatortypes_view2d(void) -{ - WM_operatortype_append(VIEW2D_OT_pan); - WM_operatortype_append(VIEW2D_OT_edge_pan); - - WM_operatortype_append(VIEW2D_OT_scroll_left); - WM_operatortype_append(VIEW2D_OT_scroll_right); - WM_operatortype_append(VIEW2D_OT_scroll_up); - WM_operatortype_append(VIEW2D_OT_scroll_down); - - WM_operatortype_append(VIEW2D_OT_zoom_in); - WM_operatortype_append(VIEW2D_OT_zoom_out); - - WM_operatortype_append(VIEW2D_OT_zoom); - WM_operatortype_append(VIEW2D_OT_zoom_border); - -#ifdef WITH_INPUT_NDOF - WM_operatortype_append(VIEW2D_OT_ndof); -#endif - - WM_operatortype_append(VIEW2D_OT_smoothview); - - WM_operatortype_append(VIEW2D_OT_scroller_activate); - - WM_operatortype_append(VIEW2D_OT_reset); -} - -void ED_keymap_view2d(wmKeyConfig *keyconf) -{ - WM_keymap_ensure(keyconf, "View2D", 0, 0); -} - -/** \} */ diff --git a/source/blender/editors/interface/view2d_ops.cc b/source/blender/editors/interface/view2d_ops.cc new file mode 100644 index 00000000000..81c1cc79a0e --- /dev/null +++ b/source/blender/editors/interface/view2d_ops.cc @@ -0,0 +1,2274 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" +#include "DNA_windowmanager_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math_base.h" +#include "BLI_math_vector.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "PIL_time.h" /* USER_ZOOM_CONTINUE */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Utilities + * \{ */ + +static bool view2d_poll(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + return (region != nullptr) && (region->v2d.flag & V2D_IS_INIT); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Pan Shared Utilities + * \{ */ + +/** + * This group of operators come in several forms: + * -# Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by + * -# Scroll-wheel 'steps' - rolling mouse-wheel by one step moves view by predefined amount + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor) + */ + +/** + * Temporary custom-data for operator. + */ +struct v2dViewPanData { + /** screen where view pan was initiated */ + bScreen *screen; + /** area where view pan was initiated */ + ScrArea *area; + /** region where view pan was initiated */ + ARegion *region; + /** view2d we're operating in */ + View2D *v2d; + + /** amount to move view relative to zoom */ + float facx, facy; + + /* options for version 1 */ + /** mouse x/y values in window when operator was initiated */ + int startx, starty; + /** previous x/y values of mouse in window */ + int lastx, lasty; + /** event starting pan, for modal exit */ + int invoke_event; + + /** for MMB in scrollers (old feature in past, but now not that useful) */ + short in_scroller; + + /* View2D Edge Panning */ + double edge_pan_last_time; + double edge_pan_start_time_x, edge_pan_start_time_y; +}; + +static bool view_pan_poll(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + /* check if there's a region in context to work with */ + if (region == nullptr) { + return false; + } + + View2D *v2d = ®ion->v2d; + + /* check that 2d-view can pan */ + if ((v2d->keepofs & V2D_LOCKOFS_X) && (v2d->keepofs & V2D_LOCKOFS_Y)) { + return false; + } + + /* view can pan */ + return true; +} + +/* initialize panning customdata */ +static void view_pan_init(bContext *C, wmOperator *op) +{ + /* Should've been checked before. */ + BLI_assert(view_pan_poll(C)); + + /* set custom-data for operator */ + v2dViewPanData *vpd = MEM_cnew(__func__); + op->customdata = vpd; + + /* set pointers to owners */ + vpd->screen = CTX_wm_screen(C); + vpd->area = CTX_wm_area(C); + vpd->region = CTX_wm_region(C); + vpd->v2d = &vpd->region->v2d; + + /* calculate translation factor - based on size of view */ + const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1); + const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); + vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx; + vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy; + + vpd->v2d->flag |= V2D_IS_NAVIGATING; +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_pan_apply_ex(bContext *C, v2dViewPanData *vpd, float dx, float dy) +{ + View2D *v2d = vpd->v2d; + + /* calculate amount to move view by */ + dx *= vpd->facx; + dy *= vpd->facy; + + /* only move view on an axis if change is allowed */ + if ((v2d->keepofs & V2D_LOCKOFS_X) == 0) { + v2d->cur.xmin += dx; + v2d->cur.xmax += dx; + } + if ((v2d->keepofs & V2D_LOCKOFS_Y) == 0) { + v2d->cur.ymin += dy; + v2d->cur.ymax += dy; + } + + /* 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); +} + +static void view_pan_apply(bContext *C, wmOperator *op) +{ + v2dViewPanData *vpd = static_cast(op->customdata); + + view_pan_apply_ex(C, vpd, RNA_int_get(op->ptr, "deltax"), RNA_int_get(op->ptr, "deltay")); +} + +/* Cleanup temp custom-data. */ +static void view_pan_exit(wmOperator *op) +{ + v2dViewPanData *vpd = static_cast(op->customdata); + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; + MEM_SAFE_FREE(op->customdata); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Pan Operator (modal drag-pan) + * \{ */ + +/* for 'redo' only, with no user input */ +static int view_pan_exec(bContext *C, wmOperator *op) +{ + view_pan_init(C, op); + view_pan_apply(C, op); + view_pan_exit(op); + return OPERATOR_FINISHED; +} + +/* set up modal operator and relevant settings */ +static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmWindow *window = CTX_wm_window(C); + + /* set up customdata */ + view_pan_init(C, op); + + v2dViewPanData *vpd = static_cast(op->customdata); + View2D *v2d = vpd->v2d; + + /* set initial settings */ + vpd->startx = vpd->lastx = event->xy[0]; + vpd->starty = vpd->lasty = event->xy[1]; + vpd->invoke_event = event->type; + + if (event->type == MOUSEPAN) { + RNA_int_set(op->ptr, "deltax", event->prev_xy[0] - event->xy[0]); + RNA_int_set(op->ptr, "deltay", event->prev_xy[1] - event->xy[1]); + + view_pan_apply(C, op); + view_pan_exit(op); + return OPERATOR_FINISHED; + } + + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", 0); + + if (v2d->keepofs & V2D_LOCKOFS_X) { + WM_cursor_modal_set(window, WM_CURSOR_NS_SCROLL); + } + else if (v2d->keepofs & V2D_LOCKOFS_Y) { + WM_cursor_modal_set(window, WM_CURSOR_EW_SCROLL); + } + else { + WM_cursor_modal_set(window, WM_CURSOR_NSEW_SCROLL); + } + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +/* handle user input - calculations of mouse-movement + * need to be done here, not in the apply callback! */ +static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + v2dViewPanData *vpd = static_cast(op->customdata); + + /* execute the events */ + switch (event->type) { + case MOUSEMOVE: { + /* calculate new delta transform, then store mouse-coordinates for next-time */ + RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->xy[0])); + RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->xy[1])); + + vpd->lastx = event->xy[0]; + vpd->lasty = event->xy[1]; + + view_pan_apply(C, op); + break; + } + /* XXX: Mode switching isn't implemented. See comments in 36818. + * switch to zoom */ +#if 0 + case LEFTMOUSE: + if (event->val == KM_PRESS) { + /* calculate overall delta mouse-movement for redo */ + RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx)); + RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty)); + + view_pan_exit(op); + WM_cursor_modal_restore(CTX_wm_window(C)); + WM_operator_name_call(C, "VIEW2D_OT_zoom", WM_OP_INVOKE_DEFAULT, nullptr, event); + return OPERATOR_FINISHED; + } +#endif + default: + if (ELEM(event->type, vpd->invoke_event, EVT_ESCKEY)) { + if (event->val == KM_RELEASE) { + /* calculate overall delta mouse-movement for redo */ + RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx)); + RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty)); + + view_pan_exit(op); + WM_cursor_modal_restore(CTX_wm_window(C)); + + return OPERATOR_FINISHED; + } + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +static void view_pan_cancel(bContext *UNUSED(C), wmOperator *op) +{ + view_pan_exit(op); +} + +static void VIEW2D_OT_pan(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Pan View"; + ot->description = "Pan the view"; + ot->idname = "VIEW2D_OT_pan"; + + /* api callbacks */ + ot->exec = view_pan_exec; + ot->invoke = view_pan_invoke; + ot->modal = view_pan_modal; + ot->cancel = view_pan_cancel; + ot->poll = view_pan_poll; + + /* operator is modal */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Edge Pan Operator (modal) + * + * Scroll the region if the mouse is dragged to an edge. "Invisible" operator that always + * passes through. + * \{ */ + +/* set up modal operator and relevant settings */ +static int view_edge_pan_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + op->customdata = MEM_callocN(sizeof(View2DEdgePanData), "View2DEdgePanData"); + View2DEdgePanData *vpd = static_cast(op->customdata); + UI_view2d_edge_pan_operator_init(C, vpd, op); + + WM_event_add_modal_handler(C, op); + + return (OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH); +} + +static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + View2DEdgePanData *vpd = static_cast(op->customdata); + + if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) { + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; + MEM_SAFE_FREE(op->customdata); + return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); + } + + UI_view2d_edge_pan_apply_event(C, vpd, event); + + /* This operator is supposed to run together with some drag action. + * On successful handling, always pass events on to other handlers. */ + return OPERATOR_PASS_THROUGH; +} + +static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op) +{ + v2dViewPanData *vpd = static_cast(op->customdata); + vpd->v2d->flag &= ~V2D_IS_NAVIGATING; + MEM_SAFE_FREE(op->customdata); +} + +static void VIEW2D_OT_edge_pan(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "View Edge Pan"; + ot->description = "Pan the view when the mouse is held at an edge"; + ot->idname = "VIEW2D_OT_edge_pan"; + + /* api callbacks */ + ot->invoke = view_edge_pan_invoke; + ot->modal = view_edge_pan_modal; + ot->cancel = view_edge_pan_cancel; + ot->poll = UI_view2d_edge_pan_poll; + + /* operator is modal */ + ot->flag = OPTYPE_INTERNAL; + UI_view2d_edge_pan_operator_properties(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Pan Operator (single step) + * \{ */ + +/* this operator only needs this single callback, where it calls the view_pan_*() methods */ +static int view_scrollright_exec(bContext *C, wmOperator *op) +{ + /* initialize default settings (and validate if ok to run) */ + view_pan_init(C, op); + + /* also, check if can pan in horizontal axis */ + v2dViewPanData *vpd = static_cast(op->customdata); + if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { + view_pan_exit(op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props - only movement in positive x-direction */ + RNA_int_set(op->ptr, "deltax", 40); + RNA_int_set(op->ptr, "deltay", 0); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(op); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_scroll_right(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Scroll Right"; + ot->description = "Scroll the view right"; + ot->idname = "VIEW2D_OT_scroll_right"; + + /* api callbacks */ + ot->exec = view_scrollright_exec; + ot->poll = view_pan_poll; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + +/* this operator only needs this single callback, where it calls the view_pan_*() methods */ +static int view_scrollleft_exec(bContext *C, wmOperator *op) +{ + /* initialize default settings (and validate if ok to run) */ + view_pan_init(C, op); + + /* also, check if can pan in horizontal axis */ + v2dViewPanData *vpd = static_cast(op->customdata); + if (vpd->v2d->keepofs & V2D_LOCKOFS_X) { + view_pan_exit(op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props - only movement in negative x-direction */ + RNA_int_set(op->ptr, "deltax", -40); + RNA_int_set(op->ptr, "deltay", 0); + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(op); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_scroll_left(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Scroll Left"; + ot->description = "Scroll the view left"; + ot->idname = "VIEW2D_OT_scroll_left"; + + /* api callbacks */ + ot->exec = view_scrollleft_exec; + ot->poll = view_pan_poll; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); +} + +/* this operator only needs this single callback, where it calls the view_pan_*() methods */ +static int view_scrolldown_exec(bContext *C, wmOperator *op) +{ + /* initialize default settings (and validate if ok to run) */ + view_pan_init(C, op); + + /* also, check if can pan in vertical axis */ + v2dViewPanData *vpd = static_cast(op->customdata); + if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { + view_pan_exit(op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props */ + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", -40); + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page"); + if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) { + ARegion *region = CTX_wm_region(C); + RNA_int_set(op->ptr, "deltay", region->v2d.mask.ymin - region->v2d.mask.ymax); + } + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(op); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_scroll_down(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Scroll Down"; + ot->description = "Scroll the view down"; + ot->idname = "VIEW2D_OT_scroll_down"; + + /* api callbacks */ + ot->exec = view_scrolldown_exec; + ot->poll = view_pan_poll; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); + RNA_def_boolean(ot->srna, "page", false, "Page", "Scroll down one page"); +} + +/* this operator only needs this single callback, where it calls the view_pan_*() methods */ +static int view_scrollup_exec(bContext *C, wmOperator *op) +{ + /* initialize default settings (and validate if ok to run) */ + view_pan_init(C, op); + + /* also, check if can pan in vertical axis */ + v2dViewPanData *vpd = static_cast(op->customdata); + if (vpd->v2d->keepofs & V2D_LOCKOFS_Y) { + view_pan_exit(op); + return OPERATOR_PASS_THROUGH; + } + + /* set RNA-Props */ + RNA_int_set(op->ptr, "deltax", 0); + RNA_int_set(op->ptr, "deltay", 40); + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "page"); + if (RNA_property_is_set(op->ptr, prop) && RNA_property_boolean_get(op->ptr, prop)) { + ARegion *region = CTX_wm_region(C); + RNA_int_set(op->ptr, "deltay", BLI_rcti_size_y(®ion->v2d.mask)); + } + + /* apply movement, then we're done */ + view_pan_apply(C, op); + view_pan_exit(op); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_scroll_up(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Scroll Up"; + ot->description = "Scroll the view up"; + ot->idname = "VIEW2D_OT_scroll_up"; + + /* api callbacks */ + ot->exec = view_scrollup_exec; + ot->poll = view_pan_poll; + + /* rna - must keep these in sync with the other operators */ + RNA_def_int(ot->srna, "deltax", 0, INT_MIN, INT_MAX, "Delta X", "", INT_MIN, INT_MAX); + RNA_def_int(ot->srna, "deltay", 0, INT_MIN, INT_MAX, "Delta Y", "", INT_MIN, INT_MAX); + RNA_def_boolean(ot->srna, "page", false, "Page", "Scroll up one page"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Zoom Shared Utilities + * \{ */ + +/** + * This group of operators come in several forms: + * -# Scroll-wheel 'steps' - rolling mouse-wheel by one step zooms view by predefined amount. + * -# Scroll-wheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y). + * XXX this could be implemented... + * -# Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount. + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * + * - zoomfacx, zoomfacy - These two zoom factors allow for non-uniform scaling. + * It is safe to scale by 0, as these factors are used to determine. + * amount to enlarge 'cur' by. + */ + +/** + * Temporary custom-data for operator. + */ +struct v2dViewZoomData { + View2D *v2d; /* view2d we're operating in */ + ARegion *region; + + /* needed for continuous zoom */ + wmTimer *timer; + double timer_lastdraw; + + int lastx, lasty; /* previous x/y values of mouse in window */ + int invoke_event; /* event type that invoked, for modal exits */ + float dx, dy; /* running tally of previous delta values (for obtaining final zoom) */ + float mx_2d, my_2d; /* initial mouse location in v2d coords */ + bool zoom_to_mouse_pos; +}; + +/** + * Clamp by convention rather than locking flags, + * for ndof and +/- keys + */ +static void view_zoom_axis_lock_defaults(bContext *C, bool r_do_zoom_xy[2]) +{ + ScrArea *area = CTX_wm_area(C); + + r_do_zoom_xy[0] = true; + r_do_zoom_xy[1] = true; + + /* default not to zoom the sequencer vertically */ + if (area && area->spacetype == SPACE_SEQ) { + ARegion *region = CTX_wm_region(C); + + if (region && region->regiontype == RGN_TYPE_WINDOW) { + r_do_zoom_xy[1] = false; + } + } +} + +/* check if step-zoom can be applied */ +static bool view_zoom_poll(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + /* check if there's a region in context to work with */ + if (region == nullptr) { + return false; + } + + /* Do not show that in 3DView context. */ + if (CTX_wm_region_view3d(C)) { + return false; + } + + View2D *v2d = ®ion->v2d; + + /* check that 2d-view is zoomable */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y)) { + return false; + } + + /* view is zoomable */ + return true; +} + +/* initialize panning customdata */ +static void view_zoomdrag_init(bContext *C, wmOperator *op) +{ + /* Should've been checked before. */ + BLI_assert(view_zoom_poll(C)); + + /* set custom-data for operator */ + v2dViewZoomData *vzd = MEM_cnew(__func__); + op->customdata = vzd; + + /* set pointers to owners */ + vzd->region = CTX_wm_region(C); + vzd->v2d = &vzd->region->v2d; + /* False by default. Interactive callbacks (ie invoke()) can set it to true. */ + vzd->zoom_to_mouse_pos = false; + + vzd->v2d->flag |= V2D_IS_NAVIGATING; +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_zoomstep_apply_ex(bContext *C, + v2dViewZoomData *vzd, + const float facx, + const float facy) +{ + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + const rctf cur_old = v2d->cur; + const int snap_test = ED_region_snap_size_test(region); + + /* calculate amount to move view by, ensuring symmetry so the + * old zoom level is restored after zooming back the same amount + */ + float dx, dy; + if (facx >= 0.0f) { + dx = BLI_rctf_size_x(&v2d->cur) * facx; + dy = BLI_rctf_size_y(&v2d->cur) * facy; + } + else { + dx = (BLI_rctf_size_x(&v2d->cur) / (1.0f + 2.0f * facx)) * facx; + dy = (BLI_rctf_size_y(&v2d->cur) / (1.0f + 2.0f * facy)) * facy; + } + + /* only resize view on an axis if change is allowed */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + v2d->cur.xmax -= 2 * dx; + } + else if (v2d->keepofs & V2D_KEEPOFS_X) { + if (v2d->align & V2D_ALIGN_NO_POS_X) { + v2d->cur.xmin += 2 * dx; + } + else { + v2d->cur.xmax -= 2 * dx; + } + } + else { + + v2d->cur.xmin += dx; + v2d->cur.xmax -= dx; + + if (vzd->zoom_to_mouse_pos) { + /* get zoom fac the same way as in + * ui_view2d_curRect_validate_resize - better keep in sync! */ + const float zoomx = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur); + + /* only move view to mouse if zoom fac is inside minzoom/maxzoom */ + if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) || + IN_RANGE_INCL(zoomx, v2d->minzoom, v2d->maxzoom)) { + const float mval_fac = (vzd->mx_2d - cur_old.xmin) / BLI_rctf_size_x(&cur_old); + const float mval_faci = 1.0f - mval_fac; + const float ofs = (mval_fac * dx) - (mval_faci * dx); + + v2d->cur.xmin += ofs; + v2d->cur.xmax += ofs; + } + } + } + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + v2d->cur.ymax -= 2 * dy; + } + else if (v2d->keepofs & V2D_KEEPOFS_Y) { + if (v2d->align & V2D_ALIGN_NO_POS_Y) { + v2d->cur.ymin += 2 * dy; + } + else { + v2d->cur.ymax -= 2 * dy; + } + } + else { + + v2d->cur.ymin += dy; + v2d->cur.ymax -= dy; + + if (vzd->zoom_to_mouse_pos) { + /* get zoom fac the same way as in + * ui_view2d_curRect_validate_resize - better keep in sync! */ + const float zoomy = (float)(BLI_rcti_size_y(&v2d->mask) + 1) / BLI_rctf_size_y(&v2d->cur); + + /* only move view to mouse if zoom fac is inside minzoom/maxzoom */ + if (((v2d->keepzoom & V2D_LIMITZOOM) == 0) || + IN_RANGE_INCL(zoomy, v2d->minzoom, v2d->maxzoom)) { + const float mval_fac = (vzd->my_2d - cur_old.ymin) / BLI_rctf_size_y(&cur_old); + const float mval_faci = 1.0f - mval_fac; + const float ofs = (mval_fac * dy) - (mval_faci * dy); + + v2d->cur.ymin += ofs; + v2d->cur.ymax += ofs; + } + } + } + } + + /* Inform v2d about changes after this operation. */ + UI_view2d_curRect_changed(C, v2d); + + if (ED_region_snap_size_apply(region, snap_test)) { + ScrArea *area = CTX_wm_area(C); + ED_area_tag_redraw(area); + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); + } + + /* request updates to be done... */ + ED_region_tag_redraw_no_rebuild(vzd->region); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +static void view_zoomstep_apply(bContext *C, wmOperator *op) +{ + v2dViewZoomData *vzd = static_cast(op->customdata); + view_zoomstep_apply_ex( + C, vzd, RNA_float_get(op->ptr, "zoomfacx"), RNA_float_get(op->ptr, "zoomfacy")); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Zoom Operator (single step) + * \{ */ + +/* Cleanup temp custom-data. */ +static void view_zoomstep_exit(wmOperator *op) +{ + UI_view2d_zoom_cache_reset(); + v2dViewZoomData *vzd = static_cast(op->customdata); + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; + MEM_SAFE_FREE(op->customdata); +} + +/* this operator only needs this single callback, where it calls the view_zoom_*() methods */ +static int view_zoomin_exec(bContext *C, wmOperator *op) +{ + if (op->customdata == nullptr) { /* Might have been setup in _invoke() already. */ + view_zoomdrag_init(C, op); + } + + bool do_zoom_xy[2]; + view_zoom_axis_lock_defaults(C, do_zoom_xy); + + /* set RNA-Props - zooming in by uniform factor */ + RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? 0.0375f : 0.0f); + RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? 0.0375f : 0.0f); + + /* apply movement, then we're done */ + view_zoomstep_apply(C, op); + + view_zoomstep_exit(op); + + return OPERATOR_FINISHED; +} + +static int view_zoomin_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + view_zoomdrag_init(C, op); + + v2dViewZoomData *vzd = static_cast(op->customdata); + + if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { + ARegion *region = CTX_wm_region(C); + + /* store initial mouse position (in view space) */ + UI_view2d_region_to_view( + ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); + vzd->zoom_to_mouse_pos = true; + } + + return view_zoomin_exec(C, op); +} + +static void VIEW2D_OT_zoom_in(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Zoom In"; + ot->description = "Zoom in the view"; + ot->idname = "VIEW2D_OT_zoom_in"; + + /* api callbacks */ + ot->invoke = view_zoomin_invoke; + ot->exec = view_zoomin_exec; + ot->poll = view_zoom_poll; + + /* rna - must keep these in sync with the other operators */ + prop = RNA_def_float( + ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_float( + ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); +} + +/* this operator only needs this single callback, where it calls the view_zoom_*() methods */ +static int view_zoomout_exec(bContext *C, wmOperator *op) +{ + bool do_zoom_xy[2]; + + if (op->customdata == nullptr) { /* Might have been setup in _invoke() already. */ + view_zoomdrag_init(C, op); + } + + view_zoom_axis_lock_defaults(C, do_zoom_xy); + + /* set RNA-Props - zooming in by uniform factor */ + RNA_float_set(op->ptr, "zoomfacx", do_zoom_xy[0] ? -0.0375f : 0.0f); + RNA_float_set(op->ptr, "zoomfacy", do_zoom_xy[1] ? -0.0375f : 0.0f); + + /* apply movement, then we're done */ + view_zoomstep_apply(C, op); + + view_zoomstep_exit(op); + + return OPERATOR_FINISHED; +} + +static int view_zoomout_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + view_zoomdrag_init(C, op); + + v2dViewZoomData *vzd = static_cast(op->customdata); + + if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { + ARegion *region = CTX_wm_region(C); + + /* store initial mouse position (in view space) */ + UI_view2d_region_to_view( + ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); + vzd->zoom_to_mouse_pos = true; + } + + return view_zoomout_exec(C, op); +} + +static void VIEW2D_OT_zoom_out(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Zoom Out"; + ot->description = "Zoom out the view"; + ot->idname = "VIEW2D_OT_zoom_out"; + + /* api callbacks */ + ot->invoke = view_zoomout_invoke; + ot->exec = view_zoomout_exec; + + ot->poll = view_zoom_poll; + + /* rna - must keep these in sync with the other operators */ + prop = RNA_def_float( + ot->srna, "zoomfacx", 0, -FLT_MAX, FLT_MAX, "Zoom Factor X", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_float( + ot->srna, "zoomfacy", 0, -FLT_MAX, FLT_MAX, "Zoom Factor Y", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Zoom Operator (modal drag-zoom) + * \{ */ + +/** + * MMB Drag - allows non-uniform scaling by dragging mouse + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * - `deltax, deltay` - amounts to add to each side of the 'cur' rect + */ + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void view_zoomdrag_apply(bContext *C, wmOperator *op) +{ + v2dViewZoomData *vzd = static_cast(op->customdata); + View2D *v2d = vzd->v2d; + const int snap_test = ED_region_snap_size_test(vzd->region); + + const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init"); + const bool zoom_to_pos = use_cursor_init && vzd->zoom_to_mouse_pos; + + /* get amount to move view by */ + float dx = RNA_float_get(op->ptr, "deltax") / U.dpi_fac; + float dy = RNA_float_get(op->ptr, "deltay") / U.dpi_fac; + + /* Check if the 'timer' is initialized, as zooming with the trackpad + * never uses the "Continuous" zoom method, and the 'timer' is not initialized. */ + if ((U.viewzoom == USER_ZOOM_CONTINUE) && vzd->timer) { /* XXX store this setting as RNA prop? */ + const double time = PIL_check_seconds_timer(); + const float time_step = (float)(time - vzd->timer_lastdraw); + + dx *= time_step * 0.5f; + dy *= time_step * 0.5f; + + vzd->timer_lastdraw = time; + } + + /* only move view on an axis if change is allowed */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + if (v2d->keepofs & V2D_LOCKOFS_X) { + v2d->cur.xmax -= 2 * dx; + } + else { + if (zoom_to_pos) { + const float mval_fac = (vzd->mx_2d - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur); + const float mval_faci = 1.0f - mval_fac; + const float ofs = (mval_fac * dx) - (mval_faci * dx); + + v2d->cur.xmin += ofs + dx; + v2d->cur.xmax += ofs - dx; + } + else { + v2d->cur.xmin += dx; + v2d->cur.xmax -= dx; + } + } + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + if (v2d->keepofs & V2D_LOCKOFS_Y) { + v2d->cur.ymax -= 2 * dy; + } + else { + if (zoom_to_pos) { + const float mval_fac = (vzd->my_2d - v2d->cur.ymin) / BLI_rctf_size_y(&v2d->cur); + const float mval_faci = 1.0f - mval_fac; + const float ofs = (mval_fac * dy) - (mval_faci * dy); + + v2d->cur.ymin += ofs + dy; + v2d->cur.ymax += ofs - dy; + } + else { + v2d->cur.ymin += dy; + v2d->cur.ymax -= dy; + } + } + } + + /* Inform v2d about changes after this operation. */ + UI_view2d_curRect_changed(C, v2d); + + if (ED_region_snap_size_apply(vzd->region, snap_test)) { + ScrArea *area = CTX_wm_area(C); + ED_area_tag_redraw(area); + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); + } + + /* request updates to be done... */ + ED_region_tag_redraw_no_rebuild(vzd->region); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +/* Cleanup temp custom-data. */ +static void view_zoomdrag_exit(bContext *C, wmOperator *op) +{ + UI_view2d_zoom_cache_reset(); + + if (op->customdata) { + v2dViewZoomData *vzd = static_cast(op->customdata); + vzd->v2d->flag &= ~V2D_IS_NAVIGATING; + + if (vzd->timer) { + WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer); + } + + MEM_freeN(op->customdata); + op->customdata = nullptr; + } +} + +static void view_zoomdrag_cancel(bContext *C, wmOperator *op) +{ + view_zoomdrag_exit(C, op); +} + +/* for 'redo' only, with no user input */ +static int view_zoomdrag_exec(bContext *C, wmOperator *op) +{ + view_zoomdrag_init(C, op); + view_zoomdrag_apply(C, op); + view_zoomdrag_exit(C, op); + return OPERATOR_FINISHED; +} + +/* set up modal operator and relevant settings */ +static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + wmWindow *window = CTX_wm_window(C); + + /* set up customdata */ + view_zoomdrag_init(C, op); + + v2dViewZoomData *vzd = static_cast(op->customdata); + View2D *v2d = vzd->v2d; + + if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) { + ARegion *region = CTX_wm_region(C); + + /* Store initial mouse position (in view space). */ + UI_view2d_region_to_view( + ®ion->v2d, event->mval[0], event->mval[1], &vzd->mx_2d, &vzd->my_2d); + vzd->zoom_to_mouse_pos = true; + } + + if (ELEM(event->type, MOUSEZOOM, MOUSEPAN)) { + vzd->lastx = event->prev_xy[0]; + vzd->lasty = event->prev_xy[1]; + + float facx, facy; + float zoomfac = 0.01f; + + /* Some view2d's (graph) don't have min/max zoom, or extreme ones. */ + if (v2d->maxzoom > 0.0f) { + zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f); + } + + if (event->type == MOUSEPAN) { + facx = zoomfac * WM_event_absolute_delta_x(event); + facy = zoomfac * WM_event_absolute_delta_y(event); + + if (U.uiflag & USER_ZOOM_INVERT) { + facx *= -1.0f; + facy *= -1.0f; + } + } + else { /* MOUSEZOOM */ + facx = facy = zoomfac * WM_event_absolute_delta_x(event); + } + + /* Only respect user setting zoom axis if the view does not have any zoom restrictions + * any will be scaled uniformly. */ + if (((v2d->keepzoom & (V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y)) == 0) && + (v2d->keepzoom & V2D_KEEPASPECT)) { + if (U.uiflag & USER_ZOOM_HORIZ) { + facy = 0.0f; + } + else { + facx = 0.0f; + } + } + + /* support trackpad zoom to always zoom entirely - the v2d code uses portrait or + * landscape exceptions */ + if (v2d->keepzoom & V2D_KEEPASPECT) { + if (fabsf(facx) > fabsf(facy)) { + facy = facx; + } + else { + facx = facy; + } + } + + const float dx = facx * BLI_rctf_size_x(&v2d->cur); + const float dy = facy * BLI_rctf_size_y(&v2d->cur); + + RNA_float_set(op->ptr, "deltax", dx); + RNA_float_set(op->ptr, "deltay", dy); + + view_zoomdrag_apply(C, op); + view_zoomdrag_exit(C, op); + return OPERATOR_FINISHED; + } + + /* set initial settings */ + vzd->lastx = event->xy[0]; + vzd->lasty = event->xy[1]; + RNA_float_set(op->ptr, "deltax", 0); + RNA_float_set(op->ptr, "deltay", 0); + + /* for modal exit test */ + vzd->invoke_event = event->type; + + if (v2d->keepofs & V2D_LOCKOFS_X) { + WM_cursor_modal_set(window, WM_CURSOR_NS_SCROLL); + } + else if (v2d->keepofs & V2D_LOCKOFS_Y) { + WM_cursor_modal_set(window, WM_CURSOR_EW_SCROLL); + } + else { + WM_cursor_modal_set(window, WM_CURSOR_NSEW_SCROLL); + } + + /* add temp handler */ + WM_event_add_modal_handler(C, op); + + if (U.viewzoom == USER_ZOOM_CONTINUE) { + /* needs a timer to continue redrawing */ + vzd->timer = WM_event_add_timer(CTX_wm_manager(C), window, TIMER, 0.01f); + vzd->timer_lastdraw = PIL_check_seconds_timer(); + } + + return OPERATOR_RUNNING_MODAL; +} + +/* handle user input - calculations of mouse-movement need to be done here, + * not in the apply callback! */ +static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + v2dViewZoomData *vzd = static_cast(op->customdata); + View2D *v2d = vzd->v2d; + + /* execute the events */ + if (event->type == TIMER && event->customdata == vzd->timer) { + view_zoomdrag_apply(C, op); + } + else if (event->type == MOUSEMOVE) { + float dx, dy; + float zoomfac = 0.01f; + + /* some view2d's (graph) don't have min/max zoom, or extreme ones */ + if (v2d->maxzoom > 0.0f) { + zoomfac = clamp_f(0.001f * v2d->maxzoom, 0.001f, 0.01f); + } + + /* calculate new delta transform, based on zooming mode */ + if (U.viewzoom == USER_ZOOM_SCALE) { + /* 'scale' zooming */ + float dist; + float len_old[2]; + float len_new[2]; + + /* x-axis transform */ + dist = BLI_rcti_size_x(&v2d->mask) / 2.0f; + len_old[0] = zoomfac * fabsf(vzd->lastx - vzd->region->winrct.xmin - dist); + len_new[0] = zoomfac * fabsf(event->xy[0] - vzd->region->winrct.xmin - dist); + + /* y-axis transform */ + dist = BLI_rcti_size_y(&v2d->mask) / 2.0f; + len_old[1] = zoomfac * fabsf(vzd->lasty - vzd->region->winrct.ymin - dist); + len_new[1] = zoomfac * fabsf(event->xy[1] - vzd->region->winrct.ymin - dist); + + /* Calculate distance */ + if (v2d->keepzoom & V2D_KEEPASPECT) { + dist = len_v2(len_new) - len_v2(len_old); + dx = dy = dist; + } + else { + dx = len_new[0] - len_old[0]; + dy = len_new[1] - len_old[1]; + } + + dx *= BLI_rctf_size_x(&v2d->cur); + dy *= BLI_rctf_size_y(&v2d->cur); + } + else { /* USER_ZOOM_CONTINUE or USER_ZOOM_DOLLY */ + float facx = zoomfac * (event->xy[0] - vzd->lastx); + float facy = zoomfac * (event->xy[1] - vzd->lasty); + + /* Only respect user setting zoom axis if the view does not have any zoom restrictions + * any will be scaled uniformly */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0 && (v2d->keepzoom & V2D_LOCKZOOM_Y) == 0 && + (v2d->keepzoom & V2D_KEEPASPECT)) { + if (U.uiflag & USER_ZOOM_HORIZ) { + facy = 0.0f; + } + else { + facx = 0.0f; + } + } + + /* support zoom to always zoom entirely - the v2d code uses portrait or + * landscape exceptions */ + if (v2d->keepzoom & V2D_KEEPASPECT) { + if (fabsf(facx) > fabsf(facy)) { + facy = facx; + } + else { + facx = facy; + } + } + + dx = facx * BLI_rctf_size_x(&v2d->cur); + dy = facy * BLI_rctf_size_y(&v2d->cur); + } + + if (U.uiflag & USER_ZOOM_INVERT) { + dx *= -1.0f; + dy *= -1.0f; + } + + /* set transform amount, and add current deltas to stored total delta (for redo) */ + RNA_float_set(op->ptr, "deltax", dx); + RNA_float_set(op->ptr, "deltay", dy); + + vzd->dx += dx; + vzd->dy += dy; + + /* Store mouse coordinates for next time, if not doing continuous zoom: + * - Continuous zoom only depends on distance of mouse + * to starting point to determine rate of change. + */ + if (U.viewzoom != USER_ZOOM_CONTINUE) { /* XXX store this setting as RNA prop? */ + vzd->lastx = event->xy[0]; + vzd->lasty = event->xy[1]; + } + + /* apply zooming */ + view_zoomdrag_apply(C, op); + } + else if (ELEM(event->type, vzd->invoke_event, EVT_ESCKEY)) { + if (event->val == KM_RELEASE) { + + /* for redo, store the overall deltas - need to respect zoom-locks here... */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + RNA_float_set(op->ptr, "deltax", vzd->dx); + } + else { + RNA_float_set(op->ptr, "deltax", 0); + } + + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + RNA_float_set(op->ptr, "deltay", vzd->dy); + } + else { + RNA_float_set(op->ptr, "deltay", 0); + } + + /* free customdata */ + view_zoomdrag_exit(C, op); + WM_cursor_modal_restore(CTX_wm_window(C)); + + return OPERATOR_FINISHED; + } + } + + return OPERATOR_RUNNING_MODAL; +} + +static void VIEW2D_OT_zoom(wmOperatorType *ot) +{ + PropertyRNA *prop; + /* identifiers */ + ot->name = "Zoom 2D View"; + ot->description = "Zoom in/out the view"; + ot->idname = "VIEW2D_OT_zoom"; + + /* api callbacks */ + ot->exec = view_zoomdrag_exec; + ot->invoke = view_zoomdrag_invoke; + ot->modal = view_zoomdrag_modal; + ot->cancel = view_zoomdrag_cancel; + + ot->poll = view_zoom_poll; + + /* operator is repeatable */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY; + + /* rna - must keep these in sync with the other operators */ + prop = RNA_def_float(ot->srna, "deltax", 0, -FLT_MAX, FLT_MAX, "Delta X", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); + prop = RNA_def_float(ot->srna, "deltay", 0, -FLT_MAX, FLT_MAX, "Delta Y", "", -FLT_MAX, FLT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN); + + WM_operator_properties_use_cursor_init(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Border Zoom Operator + * \{ */ + +/** + * The user defines a rect using standard box select tools, and we use this rect to + * define the new zoom-level of the view in the following ways: + * + * -# LEFTMOUSE - zoom in to view + * -# RIGHTMOUSE - zoom out of view + * + * Currently, these key mappings are hardcoded, but it shouldn't be too important to + * have custom keymappings for this... + */ + +static int view_borderzoom_exec(bContext *C, wmOperator *op) +{ + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + rctf cur_new = v2d->cur; + const int smooth_viewtx = WM_operator_smooth_viewtx_get(op); + + /* convert coordinates of rect to 'tot' rect coordinates */ + rctf rect; + WM_operator_properties_border_to_rctf(op, &rect); + UI_view2d_region_to_view_rctf(v2d, &rect, &rect); + + /* check if zooming in/out view */ + const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out"); + + if (zoom_in) { + /* zoom in: + * - 'cur' rect will be defined by the coordinates of the border region + * - just set the 'cur' rect to have the same coordinates as the border region + * if zoom is allowed to be changed + */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + cur_new.xmin = rect.xmin; + cur_new.xmax = rect.xmax; + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + cur_new.ymin = rect.ymin; + cur_new.ymax = rect.ymax; + } + } + else { + /* zoom out: + * - the current 'cur' rect coordinates are going to end up where the 'rect' ones are, + * but the 'cur' rect coordinates will need to be adjusted to take in more of the view + * - calculate zoom factor, and adjust using center-point + */ + float zoom, center, size; + + /* TODO: is this zoom factor calculation valid? + * It seems to produce same results every time... */ + if ((v2d->keepzoom & V2D_LOCKZOOM_X) == 0) { + size = BLI_rctf_size_x(&cur_new); + zoom = size / BLI_rctf_size_x(&rect); + center = BLI_rctf_cent_x(&cur_new); + + cur_new.xmin = center - (size * zoom); + cur_new.xmax = center + (size * zoom); + } + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) == 0) { + size = BLI_rctf_size_y(&cur_new); + zoom = size / BLI_rctf_size_y(&rect); + center = BLI_rctf_cent_y(&cur_new); + + cur_new.ymin = center - (size * zoom); + cur_new.ymax = center + (size * zoom); + } + } + + UI_view2d_smooth_view(C, region, &cur_new, smooth_viewtx); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_zoom_border(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Zoom to Border"; + ot->description = "Zoom in the view to the nearest item contained in the border"; + ot->idname = "VIEW2D_OT_zoom_border"; + + /* api callbacks */ + ot->invoke = WM_gesture_box_invoke; + ot->exec = view_borderzoom_exec; + ot->modal = WM_gesture_box_modal; + ot->cancel = WM_gesture_box_cancel; + + ot->poll = view_zoom_poll; + + /* rna */ + WM_operator_properties_gesture_box_zoom(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name NDOF Pan/Zoom Operator + * \{ */ + +#ifdef WITH_INPUT_NDOF +static int view2d_ndof_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (event->type != NDOF_MOTION) { + return OPERATOR_CANCELLED; + } + + const wmNDOFMotionData *ndof = static_cast(event->customdata); + + /* tune these until it feels right */ + const float zoom_sensitivity = 0.5f; + const float speed = 10.0f; /* match view3d ortho */ + const bool has_translate = !is_zero_v2(ndof->tvec) && view_pan_poll(C); + const bool has_zoom = (ndof->tvec[2] != 0.0f) && view_zoom_poll(C); + + if (has_translate) { + float pan_vec[3]; + + WM_event_ndof_pan_get(ndof, pan_vec, false); + + pan_vec[0] *= speed; + pan_vec[1] *= speed; + + view_pan_init(C, op); + + v2dViewPanData *vpd = static_cast(op->customdata); + view_pan_apply_ex(C, vpd, pan_vec[0], pan_vec[1]); + + view_pan_exit(op); + } + + if (has_zoom) { + float zoom_factor = zoom_sensitivity * ndof->dt * -ndof->tvec[2]; + + bool do_zoom_xy[2]; + + if (U.ndof_flag & NDOF_ZOOM_INVERT) { + zoom_factor = -zoom_factor; + } + + view_zoom_axis_lock_defaults(C, do_zoom_xy); + + view_zoomdrag_init(C, op); + + v2dViewZoomData *vzd = static_cast(op->customdata); + view_zoomstep_apply_ex( + C, vzd, do_zoom_xy[0] ? zoom_factor : 0.0f, do_zoom_xy[1] ? zoom_factor : 0.0f); + + view_zoomstep_exit(op); + } + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_ndof(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "NDOF Pan/Zoom"; + ot->idname = "VIEW2D_OT_ndof"; + ot->description = "Use a 3D mouse device to pan/zoom the view"; + + /* api callbacks */ + ot->invoke = view2d_ndof_invoke; + ot->poll = view2d_poll; + + /* flags */ + ot->flag = OPTYPE_LOCK_BYPASS; +} +#endif /* WITH_INPUT_NDOF */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Smooth View Operator + * \{ */ + +struct SmoothView2DStore { + rctf orig_cur, new_cur; + + double time_allowed; +}; + +/** + * function to get a factor out of a rectangle + * + * NOTE: this doesn't always work as well as it might because the target size + * may not be reached because of clamping the desired rect, we _could_ + * attempt to clamp the rect before working out the zoom factor but its + * not really worthwhile for the few cases this happens. + */ +static float smooth_view_rect_to_fac(const rctf *rect_a, const rctf *rect_b) +{ + const float size_a[2] = {BLI_rctf_size_x(rect_a), BLI_rctf_size_y(rect_a)}; + const float size_b[2] = {BLI_rctf_size_x(rect_b), BLI_rctf_size_y(rect_b)}; + const float cent_a[2] = {BLI_rctf_cent_x(rect_a), BLI_rctf_cent_y(rect_a)}; + const float cent_b[2] = {BLI_rctf_cent_x(rect_b), BLI_rctf_cent_y(rect_b)}; + + float fac_max = 0.0f; + + for (int i = 0; i < 2; i++) { + /* axis translation normalized to scale */ + float tfac = fabsf(cent_a[i] - cent_b[i]) / min_ff(size_a[i], size_b[i]); + fac_max = max_ff(fac_max, tfac); + if (fac_max >= 1.0f) { + break; + } + + /* axis scale difference, x2 so doubling or half gives 1.0f */ + tfac = (1.0f - (min_ff(size_a[i], size_b[i]) / max_ff(size_a[i], size_b[i]))) * 2.0f; + fac_max = max_ff(fac_max, tfac); + if (fac_max >= 1.0f) { + break; + } + } + return min_ff(fac_max, 1.0f); +} + +void UI_view2d_smooth_view(bContext *C, ARegion *region, const rctf *cur, const int smooth_viewtx) +{ + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = CTX_wm_window(C); + + View2D *v2d = ®ion->v2d; + SmoothView2DStore sms = {{0}}; + bool ok = false; + float fac = 1.0f; + + /* initialize sms */ + sms.new_cur = v2d->cur; + + /* store the options we want to end with */ + if (cur) { + sms.new_cur = *cur; + } + + if (cur) { + fac = smooth_view_rect_to_fac(&v2d->cur, cur); + } + + if (smooth_viewtx && fac > FLT_EPSILON) { + bool changed = false; + + if (BLI_rctf_compare(&sms.new_cur, &v2d->cur, FLT_EPSILON) == false) { + changed = true; + } + + /* The new view is different from the old one + * so animate the view */ + if (changed) { + sms.orig_cur = v2d->cur; + + sms.time_allowed = (double)smooth_viewtx / 1000.0; + + /* scale the time allowed the change in view */ + sms.time_allowed *= (double)fac; + + /* keep track of running timer! */ + if (v2d->sms == nullptr) { + v2d->sms = MEM_new(__func__); + } + *v2d->sms = sms; + if (v2d->smooth_timer) { + WM_event_remove_timer(wm, win, v2d->smooth_timer); + } + /* TIMER1 is hard-coded in key-map. */ + v2d->smooth_timer = WM_event_add_timer(wm, win, TIMER1, 1.0 / 100.0); + + ok = true; + } + } + + /* if we get here nothing happens */ + if (ok == false) { + v2d->cur = sms.new_cur; + + UI_view2d_curRect_changed(C, v2d); + ED_region_tag_redraw_no_rebuild(region); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + } +} + +/* only meant for timer usage */ +static int view2d_smoothview_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + wmWindow *win = CTX_wm_window(C); + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + SmoothView2DStore *sms = v2d->sms; + + /* escape if not our timer */ + if (v2d->smooth_timer == nullptr || v2d->smooth_timer != event->customdata) { + return OPERATOR_PASS_THROUGH; + } + + float step; + if (sms->time_allowed != 0.0) { + step = (float)((v2d->smooth_timer->duration) / sms->time_allowed); + } + else { + step = 1.0f; + } + + /* end timer */ + if (step >= 1.0f) { + v2d->cur = sms->new_cur; + + MEM_freeN(v2d->sms); + v2d->sms = nullptr; + + WM_event_remove_timer(CTX_wm_manager(C), win, v2d->smooth_timer); + v2d->smooth_timer = nullptr; + + /* Event handling won't know if a UI item has been moved under the pointer. */ + WM_event_add_mousemove(win); + } + else { + /* ease in/out */ + step = (3.0f * step * step - 2.0f * step * step * step); + + BLI_rctf_interp(&v2d->cur, &sms->orig_cur, &sms->new_cur, step); + } + + UI_view2d_curRect_changed(C, v2d); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + ED_region_tag_redraw_no_rebuild(region); + + if (v2d->sms == nullptr) { + UI_view2d_zoom_cache_reset(); + } + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_smoothview(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Smooth View 2D"; + ot->idname = "VIEW2D_OT_smoothview"; + + /* api callbacks */ + ot->invoke = view2d_smoothview_invoke; + ot->poll = view2d_poll; + + /* flags */ + ot->flag = OPTYPE_INTERNAL; + + /* rna */ + WM_operator_properties_gesture_box(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Scroll Bar Move Operator + * \{ */ + +/** + * Scrollers should behave in the following ways, when clicked on with LMB (and dragged): + * -# 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, + * enlarge 'cur' rect on the relevant side. + * -# 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite). + * + * In order to make sure this works, each operator must define the following RNA-Operator Props: + * - `deltax, deltay` - define how much to move view by (relative to zoom-correction factor) + */ + +/* customdata for scroller-invoke data */ +struct v2dScrollerMove { + /** View2D data that this operation affects */ + View2D *v2d; + /** region that the scroller is in */ + ARegion *region; + + /** scroller that mouse is in ('h' or 'v') */ + char scroller; + + /* XXX find some way to provide visual feedback of this (active color?) */ + /** -1 is min zoomer, 0 is bar, 1 is max zoomer */ + short zone; + + /** view adjustment factor, based on size of region */ + float fac; + /** for pixel rounding (avoid visible UI jitter) */ + float fac_round; + /** amount moved by mouse on axis of interest */ + float delta; + + /** width of the scrollbar itself, used for page up/down clicks */ + float scrollbarwidth; + /** initial location of scrollbar x/y, mouse relative */ + int scrollbar_orig; + + /** previous mouse coordinates (in screen coordinates) for determining movement */ + int lastx, lasty; +}; + +/** + * #View2DScrollers is typedef'd in UI_view2d.h + * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, + * as we only need focus bubble info. + * + * \warning The start of this struct must not change, + * so that it stays in sync with the 'real' version. + * For now, we don't need to have a separate (internal) header for structs like this... + */ +struct View2DScrollers { + int vert_min, vert_max; /* vertical scrollbar */ + int hor_min, hor_max; /* horizontal scrollbar */ + + /* These values are written into, even if we don't use them. */ + rcti _hor, _vert; +}; + +/* quick enum for vsm->zone (scroller handles) */ +enum { + SCROLLHANDLE_MIN = -1, + SCROLLHANDLE_BAR, + SCROLLHANDLE_MAX, + SCROLLHANDLE_MIN_OUTSIDE, + SCROLLHANDLE_MAX_OUTSIDE, +} /*eV2DScrollerHandle_Zone*/; + +/** + * Check if mouse is within scroller handle. + * + * \param mouse: relevant mouse coordinate in region space. + * \param sc_min, sc_max: extents of scroller 'groove' (potential available space for scroller). + * \param sh_min, sh_max: positions of scrollbar handles. + */ +static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max) +{ + /* firstly, check if + * - 'bubble' fills entire scroller + * - 'bubble' completely out of view on either side + */ + bool in_view = true; + if (sh_min <= sc_min && sc_max <= sh_max) { + in_view = false; + } + else if (sh_max <= sc_min || sc_max <= sh_min) { + in_view = false; + } + + if (!in_view) { + return SCROLLHANDLE_BAR; + } + + /* check if mouse is in or past either handle */ + /* TODO: check if these extents are still valid or not */ + bool in_max = ((mouse >= (sh_max - V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && + (mouse <= (sh_max + V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); + bool in_min = ((mouse <= (sh_min + V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && + (mouse >= (sh_min - V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); + bool in_bar = ((mouse < (sh_max - V2D_SCROLL_HANDLE_SIZE_HOTSPOT)) && + (mouse > (sh_min + V2D_SCROLL_HANDLE_SIZE_HOTSPOT))); + const bool out_min = mouse < (sh_min - V2D_SCROLL_HANDLE_SIZE_HOTSPOT); + const bool out_max = mouse > (sh_max + V2D_SCROLL_HANDLE_SIZE_HOTSPOT); + + if (in_bar) { + return SCROLLHANDLE_BAR; + } + if (in_max) { + return SCROLLHANDLE_MAX; + } + if (in_min) { + return SCROLLHANDLE_MIN; + } + if (out_min) { + return SCROLLHANDLE_MIN_OUTSIDE; + } + if (out_max) { + return SCROLLHANDLE_MAX_OUTSIDE; + } + + /* unlikely to happen, though we just cover it in case */ + return SCROLLHANDLE_BAR; +} + +static bool scroller_activate_poll(bContext *C) +{ + if (!view2d_poll(C)) { + return false; + } + + wmWindow *win = CTX_wm_window(C); + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + wmEvent *event = win->eventstate; + + /* check if mouse in scrollbars, if they're enabled */ + return (UI_view2d_mouse_in_scrollers(region, v2d, event->xy) != 0); +} + +/* initialize customdata for scroller manipulation operator */ +static void scroller_activate_init(bContext *C, + wmOperator *op, + const wmEvent *event, + const char in_scroller) +{ + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + + /* set custom-data for operator */ + v2dScrollerMove *vsm = MEM_cnew(__func__); + op->customdata = vsm; + + /* set general data */ + vsm->v2d = v2d; + vsm->region = region; + vsm->scroller = in_scroller; + + /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */ + vsm->lastx = event->xy[0]; + vsm->lasty = event->xy[1]; + /* 'zone' depends on where mouse is relative to bubble + * - zooming must be allowed on this axis, otherwise, default to pan + */ + View2DScrollers scrollers; + UI_view2d_scrollers_calc(v2d, nullptr, &scrollers); + + /* Use a union of 'cur' & 'tot' in case the current view is far outside 'tot'. In this cases + * moving the scroll bars has far too little effect and the view can get stuck T31476. */ + rctf tot_cur_union = v2d->tot; + BLI_rctf_union(&tot_cur_union, &v2d->cur); + + if (in_scroller == 'h') { + /* horizontal scroller - calculate adjustment factor first */ + const float mask_size = (float)BLI_rcti_size_x(&v2d->hor); + vsm->fac = BLI_rctf_size_x(&tot_cur_union) / mask_size; + + /* pixel rounding */ + vsm->fac_round = (BLI_rctf_size_x(&v2d->cur)) / (float)(BLI_rcti_size_x(®ion->winrct) + 1); + + /* get 'zone' (i.e. which part of scroller is activated) */ + vsm->zone = mouse_in_scroller_handle( + event->mval[0], v2d->hor.xmin, v2d->hor.xmax, scrollers.hor_min, scrollers.hor_max); + + if ((v2d->keepzoom & V2D_LOCKZOOM_X) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { + /* default to scroll, as handles not usable */ + vsm->zone = SCROLLHANDLE_BAR; + } + + vsm->scrollbarwidth = scrollers.hor_max - scrollers.hor_min; + vsm->scrollbar_orig = ((scrollers.hor_max + scrollers.hor_min) / 2) + region->winrct.xmin; + } + else { + /* vertical scroller - calculate adjustment factor first */ + const float mask_size = (float)BLI_rcti_size_y(&v2d->vert); + vsm->fac = BLI_rctf_size_y(&tot_cur_union) / mask_size; + + /* pixel rounding */ + vsm->fac_round = (BLI_rctf_size_y(&v2d->cur)) / (float)(BLI_rcti_size_y(®ion->winrct) + 1); + + /* get 'zone' (i.e. which part of scroller is activated) */ + vsm->zone = mouse_in_scroller_handle( + event->mval[1], v2d->vert.ymin, v2d->vert.ymax, scrollers.vert_min, scrollers.vert_max); + + if ((v2d->keepzoom & V2D_LOCKZOOM_Y) && ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { + /* default to scroll, as handles not usable */ + vsm->zone = SCROLLHANDLE_BAR; + } + + vsm->scrollbarwidth = scrollers.vert_max - scrollers.vert_min; + vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin; + } + + vsm->v2d->flag |= V2D_IS_NAVIGATING; + + ED_region_tag_redraw_no_rebuild(region); +} + +/* Cleanup temp custom-data. */ +static void scroller_activate_exit(bContext *C, wmOperator *op) +{ + if (op->customdata) { + v2dScrollerMove *vsm = static_cast(op->customdata); + + vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE); + vsm->v2d->flag &= ~V2D_IS_NAVIGATING; + + MEM_freeN(op->customdata); + op->customdata = nullptr; + + ED_region_tag_redraw_no_rebuild(CTX_wm_region(C)); + } +} + +static void scroller_activate_cancel(bContext *C, wmOperator *op) +{ + scroller_activate_exit(C, op); +} + +/* apply transform to view (i.e. adjust 'cur' rect) */ +static void scroller_activate_apply(bContext *C, wmOperator *op) +{ + v2dScrollerMove *vsm = static_cast(op->customdata); + View2D *v2d = vsm->v2d; + + /* calculate amount to move view by */ + float temp = vsm->fac * vsm->delta; + + /* round to pixel */ + temp = roundf(temp / vsm->fac_round) * vsm->fac_round; + + /* type of movement */ + switch (vsm->zone) { + case SCROLLHANDLE_MIN: + /* only expand view on axis if zoom is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) { + v2d->cur.xmin -= temp; + } + if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) { + v2d->cur.ymin -= temp; + } + break; + + case SCROLLHANDLE_MAX: + + /* only expand view on axis if zoom is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X)) { + v2d->cur.xmax += temp; + } + if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y)) { + v2d->cur.ymax += temp; + } + break; + + case SCROLLHANDLE_MIN_OUTSIDE: + case SCROLLHANDLE_MAX_OUTSIDE: + case SCROLLHANDLE_BAR: + default: + /* only move view on an axis if panning is allowed */ + if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) { + v2d->cur.xmin += temp; + v2d->cur.xmax += temp; + } + if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) { + v2d->cur.ymin += temp; + v2d->cur.ymax += temp; + } + break; + } + + /* Inform v2d about changes after this operation. */ + UI_view2d_curRect_changed(C, v2d); + + /* request updates to be done... */ + ED_region_tag_redraw_no_rebuild(vsm->region); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); +} + +/** + * Handle user input for scrollers - calculations of mouse-movement need to be done here, + * not in the apply callback! + */ +static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + v2dScrollerMove *vsm = static_cast(op->customdata); + + /* execute the events */ + switch (event->type) { + case MOUSEMOVE: { + /* calculate new delta transform, then store mouse-coordinates for next-time */ + if (ELEM(vsm->zone, SCROLLHANDLE_BAR, SCROLLHANDLE_MAX)) { + /* if using bar (i.e. 'panning') or 'max' zoom widget */ + switch (vsm->scroller) { + case 'h': /* horizontal scroller - so only horizontal movement + * ('cur' moves opposite to mouse) */ + vsm->delta = (float)(event->xy[0] - vsm->lastx); + break; + case 'v': /* vertical scroller - so only vertical movement + * ('cur' moves opposite to mouse) */ + vsm->delta = (float)(event->xy[1] - vsm->lasty); + break; + } + } + else if (vsm->zone == SCROLLHANDLE_MIN) { + /* using 'min' zoom widget */ + switch (vsm->scroller) { + case 'h': /* horizontal scroller - so only horizontal movement + * ('cur' moves with mouse) */ + vsm->delta = (float)(vsm->lastx - event->xy[0]); + break; + case 'v': /* vertical scroller - so only vertical movement + * ('cur' moves with to mouse) */ + vsm->delta = (float)(vsm->lasty - event->xy[1]); + break; + } + } + + /* store previous coordinates */ + vsm->lastx = event->xy[0]; + vsm->lasty = event->xy[1]; + + scroller_activate_apply(C, op); + break; + } + case LEFTMOUSE: + case MIDDLEMOUSE: + if (event->val == KM_RELEASE) { + /* single-click was in empty space outside bubble, so scroll by 1 'page' */ + if (ELEM(vsm->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) { + if (vsm->zone == SCROLLHANDLE_MIN_OUTSIDE) { + vsm->delta = -vsm->scrollbarwidth * 0.8f; + } + else if (vsm->zone == SCROLLHANDLE_MAX_OUTSIDE) { + vsm->delta = vsm->scrollbarwidth * 0.8f; + } + + scroller_activate_apply(C, op); + scroller_activate_exit(C, op); + return OPERATOR_FINISHED; + } + + /* Otherwise, end the drag action. */ + if (vsm->lastx || vsm->lasty) { + scroller_activate_exit(C, op); + return OPERATOR_FINISHED; + } + } + break; + } + + return OPERATOR_RUNNING_MODAL; +} + +/* a click (or click drag in progress) + * should have occurred, so check if it happened in scrollbar */ +static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + + /* check if mouse in scrollbars, if they're enabled */ + const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->xy); + + /* if in a scroller, init customdata then set modal handler which will + * catch mouse-down to start doing useful stuff */ + if (in_scroller) { + /* initialize customdata */ + scroller_activate_init(C, op, event, in_scroller); + v2dScrollerMove *vsm = (v2dScrollerMove *)op->customdata; + + /* Support for quick jump to location - GTK and QT do this on Linux. */ + if (event->type == MIDDLEMOUSE) { + switch (vsm->scroller) { + case 'h': /* horizontal scroller - so only horizontal movement + * ('cur' moves opposite to mouse) */ + vsm->delta = (float)(event->xy[0] - vsm->scrollbar_orig); + break; + case 'v': /* vertical scroller - so only vertical movement + * ('cur' moves opposite to mouse) */ + vsm->delta = (float)(event->xy[1] - vsm->scrollbar_orig); + break; + } + scroller_activate_apply(C, op); + + vsm->zone = SCROLLHANDLE_BAR; + } + + /* check if zoom zones are inappropriate (i.e. zoom widgets not shown), so cannot continue + * NOTE: see view2d.c for latest conditions, and keep this in sync with that + */ + if (ELEM(vsm->zone, SCROLLHANDLE_MIN, SCROLLHANDLE_MAX)) { + if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) == 0) || + ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) == 0)) { + /* switch to bar (i.e. no scaling gets handled) */ + vsm->zone = SCROLLHANDLE_BAR; + } + } + + /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */ + if (vsm->zone == SCROLLHANDLE_BAR) { + if (((vsm->scroller == 'h') && (v2d->keepofs & V2D_LOCKOFS_X)) || + ((vsm->scroller == 'v') && (v2d->keepofs & V2D_LOCKOFS_Y))) { + /* free customdata initialized */ + scroller_activate_exit(C, op); + + /* can't catch this event for ourselves, so let it go to someone else? */ + return OPERATOR_PASS_THROUGH; + } + } + + /* zone is also inappropriate if scroller is not visible... */ + if (((vsm->scroller == 'h') && (v2d->scroll & V2D_SCROLL_HORIZONTAL_FULLR)) || + ((vsm->scroller == 'v') && (v2d->scroll & V2D_SCROLL_VERTICAL_FULLR))) { + /* free customdata initialized */ + scroller_activate_exit(C, op); + + /* can't catch this event for ourselves, so let it go to someone else? */ + /* XXX NOTE: if handlers use mask rect to clip input, input will fail for this case. */ + return OPERATOR_PASS_THROUGH; + } + + /* activate the scroller */ + if (vsm->scroller == 'h') { + v2d->scroll_ui |= V2D_SCROLL_H_ACTIVE; + } + else { + v2d->scroll_ui |= V2D_SCROLL_V_ACTIVE; + } + + /* still ok, so can add */ + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; + } + + /* not in scroller, so nothing happened... + * (pass through let's something else catch event) */ + return OPERATOR_PASS_THROUGH; +} + +/* LMB-Drag in Scrollers - not repeatable operator! */ +static void VIEW2D_OT_scroller_activate(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Scroller Activate"; + ot->description = "Scroll view by mouse click and drag"; + ot->idname = "VIEW2D_OT_scroller_activate"; + + /* flags */ + ot->flag = OPTYPE_BLOCKING; + + /* api callbacks */ + ot->invoke = scroller_activate_invoke; + ot->modal = scroller_activate_modal; + ot->cancel = scroller_activate_cancel; + + ot->poll = scroller_activate_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Reset Operator + * \{ */ + +static int reset_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const uiStyle *style = UI_style_get(); + ARegion *region = CTX_wm_region(C); + View2D *v2d = ®ion->v2d; + const int snap_test = ED_region_snap_size_test(region); + + /* zoom 1.0 */ + const int winx = (float)(BLI_rcti_size_x(&v2d->mask) + 1); + const int winy = (float)(BLI_rcti_size_y(&v2d->mask) + 1); + + v2d->cur.xmax = v2d->cur.xmin + winx; + v2d->cur.ymax = v2d->cur.ymin + winy; + + /* align */ + if (v2d->align) { + /* posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_X) && !(v2d->align & V2D_ALIGN_NO_NEG_X)) { + v2d->cur.xmax = 0.0f; + v2d->cur.xmin = -winx * style->panelzoom; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_X) && !(v2d->align & V2D_ALIGN_NO_POS_X)) { + v2d->cur.xmax = winx * style->panelzoom; + v2d->cur.xmin = 0.0f; + } + + /* - posx and negx flags are mutually exclusive, so watch out */ + if ((v2d->align & V2D_ALIGN_NO_POS_Y) && !(v2d->align & V2D_ALIGN_NO_NEG_Y)) { + v2d->cur.ymax = 0.0f; + v2d->cur.ymin = -winy * style->panelzoom; + } + else if ((v2d->align & V2D_ALIGN_NO_NEG_Y) && !(v2d->align & V2D_ALIGN_NO_POS_Y)) { + v2d->cur.ymax = winy * style->panelzoom; + v2d->cur.ymin = 0.0f; + } + } + + /* Inform v2d about changes after this operation. */ + UI_view2d_curRect_changed(C, v2d); + + if (ED_region_snap_size_apply(region, snap_test)) { + ScrArea *area = CTX_wm_area(C); + ED_area_tag_redraw(area); + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); + } + + /* request updates to be done... */ + ED_region_tag_redraw(region); + UI_view2d_sync(CTX_wm_screen(C), CTX_wm_area(C), v2d, V2D_LOCK_COPY); + + UI_view2d_zoom_cache_reset(); + + return OPERATOR_FINISHED; +} + +static void VIEW2D_OT_reset(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset View"; + ot->description = "Reset the view"; + ot->idname = "VIEW2D_OT_reset"; + + /* api callbacks */ + ot->exec = reset_exec; + ot->poll = view2d_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Registration + * \{ */ + +void ED_operatortypes_view2d(void) +{ + WM_operatortype_append(VIEW2D_OT_pan); + WM_operatortype_append(VIEW2D_OT_edge_pan); + + WM_operatortype_append(VIEW2D_OT_scroll_left); + WM_operatortype_append(VIEW2D_OT_scroll_right); + WM_operatortype_append(VIEW2D_OT_scroll_up); + WM_operatortype_append(VIEW2D_OT_scroll_down); + + WM_operatortype_append(VIEW2D_OT_zoom_in); + WM_operatortype_append(VIEW2D_OT_zoom_out); + + WM_operatortype_append(VIEW2D_OT_zoom); + WM_operatortype_append(VIEW2D_OT_zoom_border); + +#ifdef WITH_INPUT_NDOF + WM_operatortype_append(VIEW2D_OT_ndof); +#endif + + WM_operatortype_append(VIEW2D_OT_smoothview); + + WM_operatortype_append(VIEW2D_OT_scroller_activate); + + WM_operatortype_append(VIEW2D_OT_reset); +} + +void ED_keymap_view2d(wmKeyConfig *keyconf) +{ + WM_keymap_ensure(keyconf, "View2D", 0, 0); +} + +/** \} */ diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h index c69658c516c..e30ea618fa4 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h @@ -84,6 +84,8 @@ typedef enum eWM_GizmoFlag { WM_GIZMO_NO_TOOLTIP = (1 << 12), } eWM_GizmoFlag; +ENUM_OPERATORS(eWM_GizmoFlag, WM_GIZMO_NO_TOOLTIP); + /** * #wmGizmoGroupType.flag * Flags that influence the behavior of all gizmos in the group. -- cgit v1.2.3