diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c index e4dad0f1a53..c7bb75b54da 100644 --- a/source/blender/editors/interface/view2d.c +++ b/source/blender/editors/interface/view2d.c @@ -30,247 +30,277 @@ #include "DNA_scene_types.h" #include "DNA_userdef_types.h" #include "BLI_array.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; } +/* Return scrollbar sizes from scroll flags. */ +static void scroller_size_get(const int scroll, float *r_x, float *r_y) +{ + 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; + } + } +} + +/* Get current scrollbar sizes of the current 2D view. They will be zero if hidden. */ +void UI_view2d_scroller_size_get(const View2D *v2d, float *r_x, float *r_y) +{ + scroller_size_get(view2d_scroll_mapped(v2d->scroll), r_x, r_y); +} + /** * 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); + /* Do not use mapped scroll here because we want to update scroller rects + * even if they are not displayed. For init purposes. See T75003.*/ + scroll = v2d->scroll; /* scrollers are based off regionsize * - 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); + scroller_size_get(scroll, &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) * \{ */ /** * Initialize all relevant View2D data (including view rects if first time) * and/or refresh mask sizes after view resize. * * - For some of these presets, it is expected that the region will have defined some * additional settings necessary for the customization of the 2D viewport to its requirements * - This function should only be called from region init() callbacks, where it is expected that * this is called before #UI_view2d_size_update(), * as this one checks that the rects are properly initialized. */ 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! */ @@ -1765,225 +1795,200 @@ void UI_view2d_view_to_region_fl( 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 data by default resides in region, so get from region stored in context */ 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); } /* same as above, but it returns regionwindow. Utility for pulldowns or buttons */ 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); } -/* Get scrollbar sizes of the current 2D view. The size will be zero if the view has its scrollbars - * disabled. */ -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; - } - } -} - /** * Calculate the scale per-axis of the drawing-area * * Is used to inverse correct drawing of icons, etc. that need to follow view * but not be affected by scale * * \param r_x, r_y: scale on each axis */ 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); } /** * Same as ``UI_view2d_scale_get() - 1.0f / x, y`` */ 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); } } /** * Simple functions for consistent center offset access. * Used by node editor to shift view center for each individual node tree. */ 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); } /** * Simple pan function * (0.0, 0.0) bottom left * (0.5, 0.5) center * (1.0, 1.0) top right. */ 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); } /** * Check if mouse is within scrollers * * \param x, y: Mouse coordinates in screen (not region) space. * \param r_scroll: Mapped view2d scroll flag. * * \return appropriate code for match. * - 'h' = in horizontal scroller. * - 'v' = in vertical scroller. * - 0 = not in scroller. */