diff options
-rw-r--r-- | source/blender/editors/interface/interface_panel.c | 34 | ||||
-rw-r--r-- | source/blender/editors/interface/view2d_ops.c | 185 |
2 files changed, 209 insertions, 10 deletions
diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index 54f60a05cfd..33f600f30a1 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -107,6 +107,7 @@ typedef struct uiHandlePanelData { int startx, starty; int startofsx, startofsy; int startsizex, startsizey; + float start_cur_xmin, start_cur_ymin; } uiHandlePanelData; typedef struct PanelSort { @@ -1731,21 +1732,22 @@ static void check_panel_overlap(ARegion *region, Panel *panel) /************************ panel dragging ****************************/ +#define DRAG_REGION_PAD (PNL_HEADER * 0.5) static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) { uiHandlePanelData *data = panel->activedata; ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); - short align = panel_aligned(area, region), dx = 0, dy = 0; + short align = panel_aligned(area, region); - /* first clip for window, no dragging outside */ - if (!BLI_rcti_isect_pt_v(®ion->winrct, &event->x)) { - return; - } + /* Keep the drag position in the region with a small pad to keep the panel visible. */ + int x = clamp_i(event->x, region->winrct.xmin, region->winrct.xmax + DRAG_REGION_PAD); + int y = clamp_i(event->y, region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); - dx = (event->x - data->startx); - dy = (event->y - data->starty); + float dx = (float)(x - data->startx); + float dy = (float)(y - data->starty); + /* Adjust for region zoom. */ dx *= (float)BLI_rctf_size_x(®ion->v2d.cur) / (float)BLI_rcti_size_x(®ion->winrct); dy *= (float)BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); @@ -1763,17 +1765,21 @@ static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) /* reset the panel snapping, to allow dragging away from snapped edges */ panel->snap = PNL_SNAP_NONE; - panel->ofsx = data->startofsx + dx; - panel->ofsy = data->startofsy + dy; + /* Add the movement of the view due to edge scrolling while dragging. */ + dx += ((float)region->v2d.cur.xmin - data->start_cur_xmin); + dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); + panel->ofsx = data->startofsx + round_fl_to_int(dx); + panel->ofsy = data->startofsy + round_fl_to_int(dy); check_panel_overlap(region, panel); if (align) { - uiAlignPanelStep(area, region, 0.2, true); + uiAlignPanelStep(area, region, 0.2f, true); } } ED_region_tag_redraw(region); } +#undef DRAG_REGION_PAD /******************* region level panel interaction *****************/ @@ -2967,6 +2973,12 @@ static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelS data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); } + /* Initiate edge panning during drags so we can move beyond the initial region view. */ + if (state == PANEL_STATE_DRAG) { + wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); + ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true); + } + data->state = state; data->startx = win->eventstate->x; data->starty = win->eventstate->y; @@ -2974,6 +2986,8 @@ static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelS data->startofsy = panel->ofsy; data->startsizex = panel->sizex; data->startsizey = panel->sizey; + data->start_cur_xmin = region->v2d.cur.xmin; + data->start_cur_ymin = region->v2d.cur.ymin; data->starttime = PIL_check_seconds_timer(); /* Remember drag drop state even when animating to the aligned position after dragging. */ diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c index 5b1e5f746ef..ff35b25e488 100644 --- a/source/blender/editors/interface/view2d_ops.c +++ b/source/blender/editors/interface/view2d_ops.c @@ -100,6 +100,10 @@ typedef struct v2dViewPanData { /** 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; /* initialize panning customdata */ @@ -357,6 +361,186 @@ static void VIEW2D_OT_pan(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name View Edge Pan Operator (modal) + * + * Scroll the region if the mouse is dragged to an edge. "Invisible" operator that always + * passes through. + * \{ */ + +/** Distance from the edge of the region within which to start panning. */ +#define EDGE_PAN_REGION_PAD (U.widget_unit) +/** Speed factor in pixels per second per pixel of distance from edge pan zone beginning. */ +#define EDGE_PAN_SPEED_PER_PIXEL (25.0f * (float)U.dpi_fac) +/** Delay before drag panning in seconds. */ +#define EDGE_PAN_DELAY 1.0f + +/* set up modal operator and relevant settings */ +static int view_edge_pan_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + /* Set up customdata. */ + if (!view_pan_init(C, op)) { + return OPERATOR_PASS_THROUGH; + } + + v2dViewPanData *vpd = op->customdata; + + 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(); + + WM_event_add_modal_handler(C, op); + + return (OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH); +} + +/** + * 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(v2dViewPanData *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(v2dViewPanData *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. */ + int min = x_dir ? region->winrct.xmin : region->winrct.ymin + EDGE_PAN_REGION_PAD; + int max = x_dir ? region->winrct.xmax : region->winrct.ymax - EDGE_PAN_REGION_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(!"Calculating speed outside of pan zones"); + return 0.0f; + } + + /* Apply a fade in to the speed based on a start time delay. */ + double start_time = x_dir ? vpd->edge_pan_start_time_x : vpd->edge_pan_start_time_y; + float delay_factor = smootherstep(EDGE_PAN_DELAY, (float)(current_time - start_time)); + + return distance * EDGE_PAN_SPEED_PER_PIXEL * delay_factor; +} + +static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + v2dViewPanData *vpd = op->customdata; + ARegion *region = vpd->region; + + if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) { + view_pan_exit(op); + return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH); + } + /* Only mousemove events matter here, ignore others. */ + if (event->type != MOUSEMOVE) { + return OPERATOR_PASS_THROUGH; + } + + /* This operator is supposed to run together with some drag action. + * On successful handling, always pass events on to other handlers. */ + const int success_retval = OPERATOR_PASS_THROUGH; + + /* Find whether the mouse is beyond X and Y edges. */ + int pan_dir_x = 0; + int pan_dir_y = 0; + if (event->x > region->winrct.xmax - EDGE_PAN_REGION_PAD) { + pan_dir_x = 1; + } + else if (event->x < region->winrct.xmin + EDGE_PAN_REGION_PAD) { + pan_dir_x = -1; + } + if (event->y > region->winrct.ymax - EDGE_PAN_REGION_PAD) { + pan_dir_y = 1; + } + else if (event->y < region->winrct.ymin + EDGE_PAN_REGION_PAD) { + 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. */ + float dtime = (float)(current_time - vpd->edge_pan_last_time); + float dx = 0.0f, dy = 0.0f; + if (pan_dir_x != 0) { + float speed = edge_pan_speed(vpd, event->x, true, current_time); + dx = dtime * speed * (float)pan_dir_x; + } + if (pan_dir_y != 0) { + float speed = edge_pan_speed(vpd, event->y, false, current_time); + dy = dtime * speed * (float)pan_dir_y; + } + vpd->edge_pan_last_time = current_time; + + /* Pan, clamping inside the regions's total bounds. */ + view_pan_apply_ex(C, vpd, dx, dy); + + return success_retval; +} + +static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op) +{ + view_pan_exit(op); +} + +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; + + /* operator is modal */ + ot->flag = OPTYPE_INTERNAL; +} + +#undef EDGE_PAN_REGION_PAD +#undef EDGE_PAN_SPEED_PER_PIXEL +#undef EDGE_PAN_DELAY + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name View Pan Operator (single step) * \{ */ @@ -2246,6 +2430,7 @@ static void VIEW2D_OT_reset(wmOperatorType *ot) 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); |