1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup edanimation
22  */
23 
24 #include "BLI_sys_types.h"
25 
26 #include "DNA_anim_types.h"
27 #include "DNA_gpencil_types.h"
28 #include "DNA_mask_types.h"
29 #include "DNA_object_types.h"
30 #include "DNA_scene_types.h"
31 #include "DNA_screen_types.h"
32 #include "DNA_space_types.h"
33 #include "DNA_userdef_types.h"
34 
35 #include "BLI_dlrbTree.h"
36 #include "BLI_math.h"
37 #include "BLI_rect.h"
38 #include "BLI_timecode.h"
39 #include "BLI_utildefines.h"
40 
41 #include "BKE_context.h"
42 #include "BKE_curve.h"
43 #include "BKE_fcurve.h"
44 #include "BKE_global.h"
45 #include "BKE_mask.h"
46 #include "BKE_nla.h"
47 
48 #include "ED_anim_api.h"
49 #include "ED_keyframes_draw.h"
50 #include "ED_keyframes_edit.h"
51 
52 #include "RNA_access.h"
53 
54 #include "UI_interface.h"
55 #include "UI_resources.h"
56 #include "UI_view2d.h"
57 
58 #include "GPU_immediate.h"
59 #include "GPU_matrix.h"
60 #include "GPU_state.h"
61 
62 /* *************************************************** */
63 /* CURRENT FRAME DRAWING */
64 
65 /* General call for drawing current frame indicator in animation editor */
ANIM_draw_cfra(const bContext * C,View2D * v2d,short flag)66 void ANIM_draw_cfra(const bContext *C, View2D *v2d, short flag)
67 {
68   Scene *scene = CTX_data_scene(C);
69 
70   const float time = scene->r.cfra + scene->r.subframe;
71   const float x = (float)(time * scene->r.framelen);
72 
73   GPU_line_width((flag & DRAWCFRA_WIDE) ? 3.0 : 2.0);
74 
75   GPUVertFormat *format = immVertexFormat();
76   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
77 
78   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
79 
80   /* Draw a light green line to indicate current frame */
81   immUniformThemeColor(TH_CFRAME);
82 
83   immBegin(GPU_PRIM_LINES, 2);
84   immVertex2f(pos, x, v2d->cur.ymin - 500.0f); /* XXX arbitrary... want it go to bottom */
85   immVertex2f(pos, x, v2d->cur.ymax);
86   immEnd();
87   immUnbindProgram();
88 }
89 
90 /* *************************************************** */
91 /* PREVIEW RANGE 'CURTAINS' */
92 /* Note: 'Preview Range' tools are defined in anim_ops.c */
93 
94 /* Draw preview range 'curtains' for highlighting where the animation data is */
ANIM_draw_previewrange(const bContext * C,View2D * v2d,int end_frame_width)95 void ANIM_draw_previewrange(const bContext *C, View2D *v2d, int end_frame_width)
96 {
97   Scene *scene = CTX_data_scene(C);
98 
99   /* only draw this if preview range is set */
100   if (PRVRANGEON) {
101     GPU_blend(GPU_BLEND_ALPHA);
102 
103     GPUVertFormat *format = immVertexFormat();
104     uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
105 
106     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
107     immUniformThemeColorShadeAlpha(TH_ANIM_PREVIEW_RANGE, -25, -30);
108     /* XXX: Fix this hardcoded color (anim_active) */
109     // immUniformColor4f(0.8f, 0.44f, 0.1f, 0.2f);
110 
111     /* only draw two separate 'curtains' if there's no overlap between them */
112     if (PSFRA < PEFRA + end_frame_width) {
113       immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)PSFRA, v2d->cur.ymax);
114       immRectf(pos, (float)(PEFRA + end_frame_width), v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
115     }
116     else {
117       immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
118     }
119 
120     immUnbindProgram();
121 
122     GPU_blend(GPU_BLEND_NONE);
123   }
124 }
125 
126 /* *************************************************** */
127 /* SCENE FRAME RANGE */
128 
129 /**
130  * Draw frame range guides (for scene frame range) in background.
131  *
132  * TODO: Should we still show these when preview range is enabled?
133  */
ANIM_draw_framerange(Scene * scene,View2D * v2d)134 void ANIM_draw_framerange(Scene *scene, View2D *v2d)
135 {
136   /* draw darkened area outside of active timeline frame range */
137   GPU_blend(GPU_BLEND_ALPHA);
138 
139   GPUVertFormat *format = immVertexFormat();
140   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
141 
142   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
143   immUniformThemeColorShadeAlpha(TH_BACK, -25, -100);
144 
145   if (SFRA < EFRA) {
146     immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, (float)SFRA, v2d->cur.ymax);
147     immRectf(pos, (float)EFRA, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
148   }
149   else {
150     immRectf(pos, v2d->cur.xmin, v2d->cur.ymin, v2d->cur.xmax, v2d->cur.ymax);
151   }
152 
153   GPU_blend(GPU_BLEND_NONE);
154 
155   /* thin lines where the actual frames are */
156   immUniformThemeColorShade(TH_BACK, -60);
157 
158   immBegin(GPU_PRIM_LINES, 4);
159 
160   immVertex2f(pos, (float)SFRA, v2d->cur.ymin);
161   immVertex2f(pos, (float)SFRA, v2d->cur.ymax);
162 
163   immVertex2f(pos, (float)EFRA, v2d->cur.ymin);
164   immVertex2f(pos, (float)EFRA, v2d->cur.ymax);
165 
166   immEnd();
167   immUnbindProgram();
168 }
169 
170 /* *************************************************** */
171 /* NLA-MAPPING UTILITIES (required for drawing and also editing keyframes)  */
172 
173 /**
174  * Obtain the AnimData block providing NLA-mapping for the given channel (if applicable).
175  *
176  * TODO: do not supply return this if the animdata tells us that there is no mapping to perform.
177  */
ANIM_nla_mapping_get(bAnimContext * ac,bAnimListElem * ale)178 AnimData *ANIM_nla_mapping_get(bAnimContext *ac, bAnimListElem *ale)
179 {
180   /* sanity checks */
181   if (ac == NULL) {
182     return NULL;
183   }
184 
185   /* abort if rendering - we may get some race condition issues... */
186   if (G.is_rendering) {
187     return NULL;
188   }
189 
190   /* apart from strictly keyframe-related contexts, this shouldn't even happen */
191   /* XXX: nla and channel here may not be necessary... */
192   if (ELEM(ac->datatype,
193            ANIMCONT_ACTION,
194            ANIMCONT_SHAPEKEY,
195            ANIMCONT_DOPESHEET,
196            ANIMCONT_FCURVES,
197            ANIMCONT_NLA,
198            ANIMCONT_CHANNEL)) {
199     /* handling depends on the type of animation-context we've got */
200     if (ale) {
201       /* NLA Control Curves occur on NLA strips,
202        * and shouldn't be subjected to this kind of mapping. */
203       if (ale->type != ANIMTYPE_NLACURVE) {
204         return ale->adt;
205       }
206     }
207   }
208 
209   /* cannot handle... */
210   return NULL;
211 }
212 
213 /* ------------------- */
214 
215 /* Helper function for ANIM_nla_mapping_apply_fcurve() -> "restore",
216  * i.e. mapping points back to action-time. */
bezt_nlamapping_restore(KeyframeEditData * ked,BezTriple * bezt)217 static short bezt_nlamapping_restore(KeyframeEditData *ked, BezTriple *bezt)
218 {
219   /* AnimData block providing scaling is stored in 'data', only_keys option is stored in i1 */
220   AnimData *adt = (AnimData *)ked->data;
221   short only_keys = (short)ked->i1;
222 
223   /* adjust BezTriple handles only if allowed to */
224   if (only_keys == 0) {
225     bezt->vec[0][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[0][0], NLATIME_CONVERT_UNMAP);
226     bezt->vec[2][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[2][0], NLATIME_CONVERT_UNMAP);
227   }
228 
229   bezt->vec[1][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[1][0], NLATIME_CONVERT_UNMAP);
230 
231   return 0;
232 }
233 
234 /* helper function for ANIM_nla_mapping_apply_fcurve() -> "apply",
235  * i.e. mapping points to NLA-mapped global time */
bezt_nlamapping_apply(KeyframeEditData * ked,BezTriple * bezt)236 static short bezt_nlamapping_apply(KeyframeEditData *ked, BezTriple *bezt)
237 {
238   /* AnimData block providing scaling is stored in 'data', only_keys option is stored in i1 */
239   AnimData *adt = (AnimData *)ked->data;
240   short only_keys = (short)ked->i1;
241 
242   /* adjust BezTriple handles only if allowed to */
243   if (only_keys == 0) {
244     bezt->vec[0][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[0][0], NLATIME_CONVERT_MAP);
245     bezt->vec[2][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[2][0], NLATIME_CONVERT_MAP);
246   }
247 
248   bezt->vec[1][0] = BKE_nla_tweakedit_remap(adt, bezt->vec[1][0], NLATIME_CONVERT_MAP);
249 
250   return 0;
251 }
252 
253 /* Apply/Unapply NLA mapping to all keyframes in the nominated F-Curve
254  * - restore = whether to map points back to non-mapped time
255  * - only_keys = whether to only adjust the location of the center point of beztriples
256  */
ANIM_nla_mapping_apply_fcurve(AnimData * adt,FCurve * fcu,bool restore,bool only_keys)257 void ANIM_nla_mapping_apply_fcurve(AnimData *adt, FCurve *fcu, bool restore, bool only_keys)
258 {
259   KeyframeEditData ked = {{NULL}};
260   KeyframeEditFunc map_cb;
261 
262   /* init edit data
263    * - AnimData is stored in 'data'
264    * - only_keys is stored in 'i1'
265    */
266   ked.data = (void *)adt;
267   ked.i1 = (int)only_keys;
268 
269   /* get editing callback */
270   if (restore) {
271     map_cb = bezt_nlamapping_restore;
272   }
273   else {
274     map_cb = bezt_nlamapping_apply;
275   }
276 
277   /* apply to F-Curve */
278   ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, map_cb, NULL);
279 }
280 
281 /* *************************************************** */
282 /* UNITS CONVERSION MAPPING (required for drawing and editing keyframes) */
283 
284 /* Get flags used for normalization in ANIM_unit_mapping_get_factor. */
ANIM_get_normalization_flags(bAnimContext * ac)285 short ANIM_get_normalization_flags(bAnimContext *ac)
286 {
287   if (ac->sl->spacetype == SPACE_GRAPH) {
288     SpaceGraph *sipo = (SpaceGraph *)ac->sl;
289     bool use_normalization = (sipo->flag & SIPO_NORMALIZE) != 0;
290     bool freeze_normalization = (sipo->flag & SIPO_NORMALIZE_FREEZE) != 0;
291     return use_normalization ? (ANIM_UNITCONV_NORMALIZE |
292                                 (freeze_normalization ? ANIM_UNITCONV_NORMALIZE_FREEZE : 0)) :
293                                0;
294   }
295 
296   return 0;
297 }
298 
normalization_factor_get(Scene * scene,FCurve * fcu,short flag,float * r_offset)299 static float normalization_factor_get(Scene *scene, FCurve *fcu, short flag, float *r_offset)
300 {
301   float factor = 1.0f, offset = 0.0f;
302 
303   if (flag & ANIM_UNITCONV_RESTORE) {
304     if (r_offset) {
305       *r_offset = fcu->prev_offset;
306     }
307 
308     return 1.0f / fcu->prev_norm_factor;
309   }
310 
311   if (flag & ANIM_UNITCONV_NORMALIZE_FREEZE) {
312     if (r_offset) {
313       *r_offset = fcu->prev_offset;
314     }
315     if (fcu->prev_norm_factor == 0.0f) {
316       /* Happens when Auto Normalize was disabled before
317        * any curves were displayed.
318        */
319       return 1.0f;
320     }
321     return fcu->prev_norm_factor;
322   }
323 
324   if (G.moving & G_TRANSFORM_FCURVES) {
325     if (r_offset) {
326       *r_offset = fcu->prev_offset;
327     }
328     if (fcu->prev_norm_factor == 0.0f) {
329       /* Same as above. */
330       return 1.0f;
331     }
332     return fcu->prev_norm_factor;
333   }
334 
335   fcu->prev_norm_factor = 1.0f;
336   if (fcu->bezt) {
337     const bool use_preview_only = PRVRANGEON;
338     const BezTriple *bezt;
339     int i;
340     float max_coord = -FLT_MAX;
341     float min_coord = FLT_MAX;
342     float range;
343 
344     if (fcu->totvert < 1) {
345       return 1.0f;
346     }
347 
348     for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
349       if (use_preview_only && !IN_RANGE_INCL(bezt->vec[1][0], scene->r.psfra, scene->r.pefra)) {
350         continue;
351       }
352 
353       if (i == 0) {
354         /* We ignore extrapolation flags and handle here, and use the
355          * control point position only. so we normalize "interesting"
356          * part of the curve.
357          *
358          * Here we handle left extrapolation.
359          */
360         max_coord = max_ff(max_coord, bezt->vec[1][1]);
361 
362         min_coord = min_ff(min_coord, bezt->vec[1][1]);
363       }
364       else {
365         const BezTriple *prev_bezt = bezt - 1;
366         if (!ELEM(prev_bezt->ipo, BEZT_IPO_BEZ, BEZT_IPO_BACK, BEZT_IPO_ELASTIC)) {
367           /* The points on the curve will lie inside the start and end points.
368            * Calculate min/max using both previous and current CV.
369            */
370           max_coord = max_ff(max_coord, bezt->vec[1][1]);
371           min_coord = min_ff(min_coord, bezt->vec[1][1]);
372           max_coord = max_ff(max_coord, prev_bezt->vec[1][1]);
373           min_coord = min_ff(min_coord, prev_bezt->vec[1][1]);
374         }
375         else {
376           const int resol = fcu->driver ?
377                                 32 :
378                                 min_ii((int)(5.0f * len_v2v2(bezt->vec[1], prev_bezt->vec[1])),
379                                        32);
380           if (resol < 2) {
381             max_coord = max_ff(max_coord, prev_bezt->vec[1][1]);
382             min_coord = min_ff(min_coord, prev_bezt->vec[1][1]);
383           }
384           else {
385             if (!ELEM(prev_bezt->ipo, BEZT_IPO_BACK, BEZT_IPO_ELASTIC)) {
386               /* Calculate min/max using bezier forward differencing. */
387               float data[120];
388               float v1[2], v2[2], v3[2], v4[2];
389 
390               v1[0] = prev_bezt->vec[1][0];
391               v1[1] = prev_bezt->vec[1][1];
392               v2[0] = prev_bezt->vec[2][0];
393               v2[1] = prev_bezt->vec[2][1];
394 
395               v3[0] = bezt->vec[0][0];
396               v3[1] = bezt->vec[0][1];
397               v4[0] = bezt->vec[1][0];
398               v4[1] = bezt->vec[1][1];
399 
400               BKE_fcurve_correct_bezpart(v1, v2, v3, v4);
401 
402               BKE_curve_forward_diff_bezier(
403                   v1[0], v2[0], v3[0], v4[0], data, resol, sizeof(float[3]));
404               BKE_curve_forward_diff_bezier(
405                   v1[1], v2[1], v3[1], v4[1], data + 1, resol, sizeof(float[3]));
406 
407               for (int j = 0; j <= resol; ++j) {
408                 const float *fp = &data[j * 3];
409                 max_coord = max_ff(max_coord, fp[1]);
410                 min_coord = min_ff(min_coord, fp[1]);
411               }
412             }
413             else {
414               /* Calculate min/max using full fcurve evaluation.
415                * [slower than bezier forward differencing but evaluates Back/Elastic interpolation
416                * as well].*/
417               float step_size = (bezt->vec[1][0] - prev_bezt->vec[1][0]) / resol;
418               for (int j = 0; j <= resol; j++) {
419                 float eval_time = prev_bezt->vec[1][0] + step_size * j;
420                 float eval_value = evaluate_fcurve_only_curve(fcu, eval_time);
421                 max_coord = max_ff(max_coord, eval_value);
422                 min_coord = min_ff(min_coord, eval_value);
423               }
424             }
425           }
426         }
427       }
428     }
429 
430     if (max_coord > min_coord) {
431       range = max_coord - min_coord;
432       if (range > FLT_EPSILON) {
433         factor = 2.0f / range;
434       }
435       offset = -min_coord - range / 2.0f;
436     }
437     else if (max_coord == min_coord) {
438       factor = 1.0f;
439       offset = -min_coord;
440     }
441   }
442   BLI_assert(factor != 0.0f);
443   if (r_offset) {
444     *r_offset = offset;
445   }
446 
447   fcu->prev_norm_factor = factor;
448   fcu->prev_offset = offset;
449   return factor;
450 }
451 
452 /* Get unit conversion factor for given ID + F-Curve */
ANIM_unit_mapping_get_factor(Scene * scene,ID * id,FCurve * fcu,short flag,float * r_offset)453 float ANIM_unit_mapping_get_factor(Scene *scene, ID *id, FCurve *fcu, short flag, float *r_offset)
454 {
455   if (flag & ANIM_UNITCONV_NORMALIZE) {
456     return normalization_factor_get(scene, fcu, flag, r_offset);
457   }
458 
459   if (r_offset) {
460     *r_offset = 0.0f;
461   }
462 
463   /* sanity checks */
464   if (id && fcu && fcu->rna_path) {
465     PointerRNA ptr, id_ptr;
466     PropertyRNA *prop;
467 
468     /* get RNA property that F-Curve affects */
469     RNA_id_pointer_create(id, &id_ptr);
470     if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &ptr, &prop)) {
471       /* rotations: radians <-> degrees? */
472       if (RNA_SUBTYPE_UNIT(RNA_property_subtype(prop)) == PROP_UNIT_ROTATION) {
473         /* if the radians flag is not set, default to using degrees which need conversions */
474         if ((scene) && (scene->unit.system_rotation == USER_UNIT_ROT_RADIANS) == 0) {
475           if (flag & ANIM_UNITCONV_RESTORE) {
476             return DEG2RADF(1.0f); /* degrees to radians */
477           }
478           return RAD2DEGF(1.0f); /* radians to degrees */
479         }
480       }
481 
482       /* TODO: other rotation types here as necessary */
483     }
484   }
485 
486   /* no mapping needs to occur... */
487   return 1.0f;
488 }
489 
find_prev_next_keyframes(struct bContext * C,int * r_nextfra,int * r_prevfra)490 static bool find_prev_next_keyframes(struct bContext *C, int *r_nextfra, int *r_prevfra)
491 {
492   Scene *scene = CTX_data_scene(C);
493   Object *ob = CTX_data_active_object(C);
494   Mask *mask = CTX_data_edit_mask(C);
495   bDopeSheet ads = {NULL};
496   DLRBT_Tree keys;
497   ActKeyColumn *aknext, *akprev;
498   float cfranext, cfraprev;
499   bool donenext = false, doneprev = false;
500   int nextcount = 0, prevcount = 0;
501 
502   cfranext = cfraprev = (float)(CFRA);
503 
504   /* init binarytree-list for getting keyframes */
505   BLI_dlrbTree_init(&keys);
506 
507   /* seed up dummy dopesheet context with flags to perform necessary filtering */
508   if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) {
509     /* only selected channels are included */
510     ads.filterflag |= ADS_FILTER_ONLYSEL;
511   }
512 
513   /* populate tree with keyframe nodes */
514   scene_to_keylist(&ads, scene, &keys, 0);
515   gpencil_to_keylist(&ads, scene->gpd, &keys, false);
516 
517   if (ob) {
518     ob_to_keylist(&ads, ob, &keys, 0);
519     gpencil_to_keylist(&ads, ob->data, &keys, false);
520   }
521 
522   if (mask) {
523     MaskLayer *masklay = BKE_mask_layer_active(mask);
524     mask_to_keylist(&ads, masklay, &keys);
525   }
526 
527   /* find matching keyframe in the right direction */
528   do {
529     aknext = (ActKeyColumn *)BLI_dlrbTree_search_next(&keys, compare_ak_cfraPtr, &cfranext);
530 
531     if (aknext) {
532       if (CFRA == (int)aknext->cfra) {
533         /* make this the new starting point for the search and ignore */
534         cfranext = aknext->cfra;
535       }
536       else {
537         /* this changes the frame, so set the frame and we're done */
538         if (++nextcount == U.view_frame_keyframes) {
539           donenext = true;
540         }
541       }
542       cfranext = aknext->cfra;
543     }
544   } while ((aknext != NULL) && (donenext == false));
545 
546   do {
547     akprev = (ActKeyColumn *)BLI_dlrbTree_search_prev(&keys, compare_ak_cfraPtr, &cfraprev);
548 
549     if (akprev) {
550       if (CFRA == (int)akprev->cfra) {
551         /* make this the new starting point for the search */
552       }
553       else {
554         /* this changes the frame, so set the frame and we're done */
555         if (++prevcount == U.view_frame_keyframes) {
556           doneprev = true;
557         }
558       }
559       cfraprev = akprev->cfra;
560     }
561   } while ((akprev != NULL) && (doneprev == false));
562 
563   /* free temp stuff */
564   BLI_dlrbTree_free(&keys);
565 
566   /* any success? */
567   if (doneprev || donenext) {
568     if (doneprev) {
569       *r_prevfra = cfraprev;
570     }
571     else {
572       *r_prevfra = CFRA - (cfranext - CFRA);
573     }
574 
575     if (donenext) {
576       *r_nextfra = cfranext;
577     }
578     else {
579       *r_nextfra = CFRA + (CFRA - cfraprev);
580     }
581 
582     return true;
583   }
584 
585   return false;
586 }
587 
ANIM_center_frame(struct bContext * C,int smooth_viewtx)588 void ANIM_center_frame(struct bContext *C, int smooth_viewtx)
589 {
590   ARegion *region = CTX_wm_region(C);
591   Scene *scene = CTX_data_scene(C);
592   float w = BLI_rctf_size_x(&region->v2d.cur);
593   rctf newrct;
594   int nextfra, prevfra;
595 
596   switch (U.view_frame_type) {
597     case ZOOM_FRAME_MODE_SECONDS: {
598       const float fps = FPS;
599       newrct.xmax = scene->r.cfra + U.view_frame_seconds * fps + 1;
600       newrct.xmin = scene->r.cfra - U.view_frame_seconds * fps - 1;
601       newrct.ymax = region->v2d.cur.ymax;
602       newrct.ymin = region->v2d.cur.ymin;
603       break;
604     }
605 
606     /* hardest case of all, look for all keyframes around frame and display those */
607     case ZOOM_FRAME_MODE_KEYFRAMES:
608       if (find_prev_next_keyframes(C, &nextfra, &prevfra)) {
609         newrct.xmax = nextfra;
610         newrct.xmin = prevfra;
611         newrct.ymax = region->v2d.cur.ymax;
612         newrct.ymin = region->v2d.cur.ymin;
613         break;
614       }
615       /* else drop through, keep range instead */
616       ATTR_FALLTHROUGH;
617 
618     case ZOOM_FRAME_MODE_KEEP_RANGE:
619     default:
620       newrct.xmax = scene->r.cfra + (w / 2);
621       newrct.xmin = scene->r.cfra - (w / 2);
622       newrct.ymax = region->v2d.cur.ymax;
623       newrct.ymin = region->v2d.cur.ymin;
624       break;
625   }
626 
627   UI_view2d_smooth_view(C, region, &newrct, smooth_viewtx);
628 }
629 /* *************************************************** */
630