UI: Hide & shrink scroll-bars based on cursor position
authorJulian Eisel <eiseljulian@gmail.com>
Sun, 6 May 2018 23:31:18 +0000 (01:31 +0200)
committerJulian Eisel <eiseljulian@gmail.com>
Mon, 7 May 2018 09:42:12 +0000 (11:42 +0200)
Scroll-bars are now hidden unless the cursor approaches them, in which case they
smoothly grow and become more & more visible. Note that since 0d309144020168e55,
scroll-bars are drawn on top of editor contents. There's no more jumping of
buttons when scroll-bars appear.

Technical notes:
* AZones are used to adjust scrollbars based on mouse movements

  We may want to support screen level AZones if we want scrollbars to also
  smoothly appear when approaching them from a different area.
  I also plan to make further changes to AZones to clean up stuff a bit.
* Had to move AZone handling to a post ARegion init stage, since we need the
  updated View2D data from there.
* View2D masks and scroller rectangles are now updated on every redraw. It's
  cheap to do that though.

source/blender/blenloader/intern/readfile.c
source/blender/editors/include/ED_screen_types.h
source/blender/editors/include/UI_view2d.h
source/blender/editors/interface/view2d.c
source/blender/editors/screen/area.c
source/blender/editors/screen/screen_ops.c
source/blender/makesdna/DNA_view2d_types.h

index a042e42775b053ad76f45c72fee36a374672ed9a..96e3f4704d98981dc46f6b40829891db2628b970 100644 (file)
@@ -6382,6 +6382,8 @@ static void direct_link_region(FileData *fd, ARegion *ar, int spacetype)
        ar->v2d.tab_num = 0;
        ar->v2d.tab_cur = 0;
        ar->v2d.sms = NULL;
+       ar->v2d.alpha_hor = ar->v2d.alpha_vert = 0;
+       ar->v2d.size_hor = ar->v2d.size_vert = 0;
        BLI_listbase_clear(&ar->panels_category);
        BLI_listbase_clear(&ar->handlers);
        BLI_listbase_clear(&ar->uiblocks);
index 1c41b14a8744818dcd9e9e72d911bd2a8ffe9a84..0fed5eb03fda8c0ab380c8eff4644e6f16269280 100644 (file)
@@ -83,13 +83,22 @@ typedef enum {
        AE_BOTTOM_TO_TOPLEFT    /* Region located at the top, _bottom_ edge is action zone. Region minimized to the top left */
 } AZEdge;
 
+typedef enum {
+       AZ_SCROLL_VERT,
+       AZ_SCROLL_HOR,
+} AZScrollDirection;
+
 /* for editing areas/regions */
 typedef struct AZone {
        struct AZone *next, *prev;
        ARegion *ar;
        int type;
-       /* region-azone, which of the edges (only for AZONE_REGION) */
-       AZEdge edge;
+
+       union {
+               /* region-azone, which of the edges (only for AZONE_REGION) */
+               AZEdge edge;
+               AZScrollDirection direction;
+       };
        /* for draw */
        short x1, y1, x2, y2;
        /* for clip */
@@ -99,8 +108,15 @@ typedef struct AZone {
 } AZone;
 
 /* actionzone type */
-#define AZONE_AREA      1  /* corner widgets for splitting areas */
-#define AZONE_REGION    2  /* when a region is collapsed, draw a handle to expose */
-#define AZONE_FULLSCREEN 3 /* when in editor fullscreen draw a corner to go to normal mode */
+enum {
+       /* corner widgets for splitting areas */
+       AZONE_AREA = 1,
+       /* when a region is collapsed, draw a handle to expose */
+       AZONE_REGION,
+       /* when in editor fullscreen draw a corner to go to normal mode */
+       AZONE_FULLSCREEN,
+       /* Hotspot azone around scrollbars to show/hide them. */
+       AZONE_REGION_SCROLL,
+};
 
 #endif /* __ED_SCREEN_TYPES_H__ */
index a19b2f05e2e2f03f65d3217def52a14bc25ac163..1f106e3f08d95aa757254c66377347f68d0e4ad6 100644 (file)
@@ -103,6 +103,8 @@ enum eView2D_Gridlines {
 /* ------ Defines for Scrollers ----- */
 
 /* scroller area */
+#define V2D_SCROLL_HEIGHT_MIN  (0.25f * U.widget_unit)
+#define V2D_SCROLL_WIDTH_MIN   (0.25f * U.widget_unit)
 #define V2D_SCROLL_HEIGHT      (0.45f * U.widget_unit)
 #define V2D_SCROLL_WIDTH       (0.45f * U.widget_unit)
 /* For scrollers with scale markings (text written onto them) */
index 8342387f956743f129e2b031d095e7b01f5b9e37..2a687118eaa3dce50f6726c99735e9eaa56e4e0d 100644 (file)
@@ -152,10 +152,11 @@ static void view2d_masks(View2D *v2d, bool check_scrollers)
         *      - if they overlap, they must not occupy the corners (which are reserved for other widgets)
         */
        if (scroll) {
-               const int scroll_width = (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) ?
-                                            V2D_SCROLL_WIDTH_TEXT : V2D_SCROLL_WIDTH;
-               const int scroll_height = (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) ?
-                                             V2D_SCROLL_HEIGHT_TEXT : V2D_SCROLL_HEIGHT;
+               int scroll_width  = (v2d->scroll & V2D_SCROLL_SCALE_VERTICAL) ?   V2D_SCROLL_WIDTH_TEXT  : v2d->size_vert;
+               int scroll_height = (v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) ? V2D_SCROLL_HEIGHT_TEXT : v2d->size_hor;
+
+               CLAMP_MIN(scroll_width, V2D_SCROLL_WIDTH_MIN);
+               CLAMP_MIN(scroll_height, V2D_SCROLL_HEIGHT_MIN);
 
                /* vertical scroller */
                if (scroll & V2D_SCROLL_LEFT) {
@@ -357,6 +358,11 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
        /* set masks (always do), but leave scroller scheck to totrect_set */
        view2d_masks(v2d, 0);
        
+       if (do_init) {
+               /* Visible by default. */
+               v2d->alpha_hor = v2d->alpha_vert = 255;
+       }
+       
        /* set 'tot' rect before setting cur? */
        /* XXX confusing stuff here still - I made this function not check scroller hide - that happens in totrect_set */
        if (tot_changed)
@@ -1641,6 +1647,9 @@ View2DScrollers *UI_view2d_scrollers_calc(
        /* scrollers is allocated here... */
        scrollers = MEM_callocN(sizeof(View2DScrollers), "View2DScrollers");
        
+       /* Always update before drawing (for dynamically sized scrollers). */
+       view2d_masks(v2d, false);
+       
        vert = v2d->vert;
        hor = v2d->hor;
        
@@ -1804,9 +1813,11 @@ static void scroll_printstr(Scene *scene, float x, float y, float val, int power
 /* Draw scrollbars in the given 2d-region */
 void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *vs)
 {
+       bTheme *btheme = UI_GetTheme();
        Scene *scene = CTX_data_scene(C);
        rcti vert, hor;
-       int scroll = view2d_scroll_mapped(v2d->scroll);
+       const int scroll = view2d_scroll_mapped(v2d->scroll);
+       const char emboss_alpha = btheme->tui.widget_emboss[3];
        unsigned char scrollers_back_color[4];
 
        /* Color for scrollbar backs */
@@ -1818,8 +1829,8 @@ void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *v
        
        /* horizontal scrollbar */
        if (scroll & V2D_SCROLL_HORIZONTAL) {
-               bTheme *btheme = UI_GetTheme();
                uiWidgetColors wcol = btheme->tui.wcol_scroll;
+               const float alpha_fac = v2d->alpha_hor / 255.0f;
                rcti slider;
                int state;
                
@@ -1830,6 +1841,11 @@ void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *v
                
                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)
@@ -1916,9 +1932,9 @@ void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *v
        
        /* vertical scrollbar */
        if (scroll & V2D_SCROLL_VERTICAL) {
-               bTheme *btheme = UI_GetTheme();
                uiWidgetColors wcol = btheme->tui.wcol_scroll;
                rcti slider;
+               const float alpha_fac = v2d->alpha_vert / 255.0f;
                int state;
                
                slider.xmin = vert.xmin;
@@ -1928,6 +1944,11 @@ void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *v
                
                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)
@@ -1990,6 +2011,8 @@ void UI_view2d_scrollers_draw(const bContext *C, View2D *v2d, View2DScrollers *v
                }
        }
        
+       /* Was changed above, so reset. */
+       btheme->tui.widget_emboss[3] = emboss_alpha;
 }
 
 /* free temporary memory used for drawing scrollers */
index 58dbebd2b7507c44a6137d2fb4f4a5604dbb11a6..e86655ab2cfa60f01f10c4399d4f1125529f29e0 100644 (file)
@@ -203,6 +203,24 @@ void ED_area_azones_update(ScrArea *sa, const int mouse_xy[2])
                                break;
                        }
                }
+               else if (az->type == AZONE_REGION_SCROLL) {
+                       /* only if mouse is not hovering the azone */
+                       if (BLI_rcti_isect_pt_v(&az->rect, mouse_xy) == false) {
+                               View2D *v2d = &az->ar->v2d;
+
+                               if (az->direction == AZ_SCROLL_VERT) {
+                                       az->alpha = v2d->alpha_vert = 0;
+                                       changed = true;
+                               }
+                               else if (az->direction == AZ_SCROLL_HOR) {
+                                       az->alpha = v2d->alpha_hor = 0;
+                                       changed = true;
+                               }
+                               else {
+                                       BLI_assert(0);
+                               }
+                       }
+               }
        }
 
        if (changed) {
@@ -464,6 +482,12 @@ static void region_draw_azones(ScrArea *sa, ARegion *ar)
                                        area_azone_tag_update(sa);
                                }
                        }
+                       else if (az->type == AZONE_REGION_SCROLL) {
+                               if (az->alpha != 0.0f) {
+                                       area_azone_tag_update(sa);
+                               }
+                               /* Don't draw this azone. */
+                       }
                }
        }
 
@@ -1043,7 +1067,7 @@ static void region_azone_tria(ScrArea *sa, AZone *az, ARegion *ar)
 }      
 
 
-static void region_azone_initialize(ScrArea *sa, ARegion *ar, AZEdge edge, const bool is_fullscreen)
+static void region_azone_edge_initialize(ScrArea *sa, ARegion *ar, AZEdge edge, const bool is_fullscreen)
 {
        AZone *az = NULL;
        const bool is_hidden = (ar->flag & (RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) == 0;
@@ -1074,21 +1098,76 @@ static void region_azone_initialize(ScrArea *sa, ARegion *ar, AZEdge edge, const
        
 }
 
+static void region_azone_scrollbar_initialize(ScrArea *sa, ARegion *ar, AZScrollDirection direction)
+{
+       rcti scroller_vert = (direction == AZ_SCROLL_VERT) ? ar->v2d.vert : ar->v2d.hor;
+       AZone *az = MEM_callocN(sizeof(*az), __func__);
+
+       BLI_addtail(&sa->actionzones, az);
+       az->type = AZONE_REGION_SCROLL;
+       az->ar = ar;
+       az->direction = direction;
+
+       if (direction == AZ_SCROLL_VERT) {
+               az->ar->v2d.alpha_vert = 0;
+       }
+       else if (direction == AZ_SCROLL_HOR) {
+               az->ar->v2d.alpha_hor = 0;
+       }
+
+       BLI_rcti_translate(&scroller_vert, ar->winrct.xmin, ar->winrct.ymin);
+       az->x1 = scroller_vert.xmin - AZONEFADEIN;
+       az->y1 = scroller_vert.ymin - AZONEFADEIN;
+       az->x2 = scroller_vert.xmax + AZONEFADEIN;
+       az->y2 = scroller_vert.ymax + AZONEFADEIN;
+
+       BLI_rcti_init(&az->rect, az->x1, az->x2, az->y1, az->y2);
+}
+
+static void region_azones_scrollbars_initialize(ScrArea *sa, ARegion *ar)
+{
+       const View2D *v2d = &ar->v2d;
+
+       if ((v2d->scroll & V2D_SCROLL_VERTICAL)   && ((v2d->scroll & V2D_SCROLL_SCALE_VERTICAL)   == 0)) {
+               region_azone_scrollbar_initialize(sa, ar, AZ_SCROLL_VERT);
+       }
+       if ((v2d->scroll & V2D_SCROLL_HORIZONTAL) && ((v2d->scroll & V2D_SCROLL_SCALE_HORIZONTAL) == 0)) {
+               region_azone_scrollbar_initialize(sa, ar, AZ_SCROLL_HOR);
+       }
+}
+
 
 /* *************************************************************** */
 
-static void region_azone_add(ScrArea *sa, ARegion *ar, const int alignment, const bool is_fullscreen)
+static void region_azones_add(const bScreen *screen, ScrArea *sa, ARegion *ar, const int alignment)
 {
+       const bool is_fullscreen = screen->state == SCREENFULL;
+
        /* edge code (t b l r) is along which area edge azone will be drawn */
-       
-       if (alignment == RGN_ALIGN_TOP)
-               region_azone_initialize(sa, ar, AE_BOTTOM_TO_TOPLEFT, is_fullscreen);
+
+       if (ar->regiontype == RGN_TYPE_HEADER && ar->winy + 6 > sa->winy) {
+               /* The logic for this is: when the header takes up the full area,
+                * disallow hiding it to view the main window.
+                *
+                * Without this, you can drag down the file selectors header and hide it
+                * by accident very easily (highly annoying!), the value 6 is arbitrary
+                * but accounts for small common rounding problems when scaling the UI,
+                * must be minimum '4' */
+       }
+       else if (alignment == RGN_ALIGN_TOP)
+               region_azone_edge_initialize(sa, ar, AE_BOTTOM_TO_TOPLEFT, is_fullscreen);
        else if (alignment == RGN_ALIGN_BOTTOM)
-               region_azone_initialize(sa, ar, AE_TOP_TO_BOTTOMRIGHT, is_fullscreen);
+               region_azone_edge_initialize(sa, ar, AE_TOP_TO_BOTTOMRIGHT, is_fullscreen);
        else if (alignment == RGN_ALIGN_RIGHT)
-               region_azone_initialize(sa, ar, AE_LEFT_TO_TOPRIGHT, is_fullscreen);
+               region_azone_edge_initialize(sa, ar, AE_LEFT_TO_TOPRIGHT, is_fullscreen);
        else if (alignment == RGN_ALIGN_LEFT)
-               region_azone_initialize(sa, ar, AE_RIGHT_TO_TOPLEFT, is_fullscreen);
+               region_azone_edge_initialize(sa, ar, AE_RIGHT_TO_TOPLEFT, is_fullscreen);
+
+       if (is_fullscreen) {
+               fullscreen_azone_initialize(sa, ar);
+       }
+
+       region_azones_scrollbars_initialize(sa, ar);
 }
 
 /* dir is direction to check, not the splitting edge direction! */
@@ -1193,7 +1272,7 @@ static bool region_is_overlap(ScrArea *sa, ARegion *ar)
        return 0;
 }
 
-static void region_rect_recursive(wmWindow *win, ScrArea *sa, ARegion *ar, rcti *remainder, rcti *overlap_remainder, int quad, bool add_azones)
+static void region_rect_recursive(wmWindow *win, ScrArea *sa, ARegion *ar, rcti *remainder, rcti *overlap_remainder, int quad)
 {
        rcti *remainder_prev = remainder;
        int prefsizex, prefsizey;
@@ -1421,30 +1500,8 @@ static void region_rect_recursive(wmWindow *win, ScrArea *sa, ARegion *ar, rcti
        if (!ar->overlap) {
                *overlap_remainder = *remainder;
        }
-       
-       /* in end, add azones, where appropriate */
-       if (ar->regiontype == RGN_TYPE_HEADER && ar->winy + 6 > sa->winy) {
-               /* The logic for this is: when the header takes up the full area,
-                * disallow hiding it to view the main window.
-                *
-                * Without this, you can drag down the file selectors header and hide it
-                * by accident very easily (highly annoying!), the value 6 is arbitrary
-                * but accounts for small common rounding problems when scaling the UI,
-                * must be minimum '4' */
-       }
-       else if (add_azones) {
-               const bScreen *screen = WM_window_get_active_screen(win);
 
-               if (ELEM(screen->state, SCREENNORMAL, SCREENMAXIMIZED)) {
-                       region_azone_add(sa, ar, alignment, false);
-               }
-               else {
-                       region_azone_add(sa, ar, alignment, true);
-                       fullscreen_azone_initialize(sa, ar);
-               }
-       }
-
-       region_rect_recursive(win, sa, ar->next, remainder, overlap_remainder, quad, add_azones);
+       region_rect_recursive(win, sa, ar->next, remainder, overlap_remainder, quad);
 }
 
 static void area_calc_totrct(ScrArea *sa, int window_size_x, int window_size_y)
@@ -1574,7 +1631,7 @@ void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *ar
        /* region rect sizes */
        rect = area->totrct;
        overlap_rect = rect;
-       region_rect_recursive(win, area, area->regionbase.first, &rect, &overlap_rect, 0, false);
+       region_rect_recursive(win, area, area->regionbase.first, &rect, &overlap_rect, 0);
 
        for (ARegion *ar = area->regionbase.first; ar; ar = ar->next) {
                region_subwindow(ar);
@@ -1615,13 +1672,10 @@ void ED_area_initialize(wmWindowManager *wm, wmWindow *win, ScrArea *sa)
        /* area sizes */
        area_calc_totrct(sa, window_size_x, window_size_y);
 
-       /* clear all azones, add the area triange widgets */
-       area_azone_initialize(win, screen, sa);
-
        /* region rect sizes */
        rect = sa->totrct;
        overlap_rect = rect;
-       region_rect_recursive(win, sa, sa->regionbase.first, &rect, &overlap_rect, 0, true);
+       region_rect_recursive(win, sa, sa->regionbase.first, &rect, &overlap_rect, 0);
        sa->flag &= ~AREA_FLAG_REGION_SIZE_UPDATE;
        
        /* default area handlers */
@@ -1629,7 +1683,10 @@ void ED_area_initialize(wmWindowManager *wm, wmWindow *win, ScrArea *sa)
        /* checks spacedata, adds own handlers */
        if (sa->type->init)
                sa->type->init(wm, sa);
-       
+
+       /* clear all azones, add the area triange widgets */
+       area_azone_initialize(win, screen, sa);
+
        /* region windows, default and own handlers */
        for (ar = sa->regionbase.first; ar; ar = ar->next) {
                region_subwindow(ar);
@@ -1646,6 +1703,9 @@ void ED_area_initialize(wmWindowManager *wm, wmWindow *win, ScrArea *sa)
                        /* prevent uiblocks to run */
                        UI_blocklist_free(NULL, &ar->uiblocks);
                }
+
+               /* Some AZones use View2D data which is only updated in region init, so call that first! */
+               region_azones_add(screen, sa, ar, ar->alignment & ~RGN_SPLIT_PREV);
        }
 }
 
index 3d18e9cbaeef27d5b7169863424bee246375195f..df0466075efa69e76926913da69955ad7873726f 100644 (file)
@@ -710,6 +710,59 @@ AZone *is_in_area_actionzone(ScrArea *sa, const int xy[2])
                                ED_area_tag_redraw(sa);
                                break;
                        }
+                       else if (az->type == AZONE_REGION_SCROLL) {
+                               ARegion *ar = az->ar;
+                               View2D *v2d = &ar->v2d;
+                               const short isect_value = UI_view2d_mouse_in_scrollers(ar, v2d, xy[0], xy[1]);
+                               bool redraw = false;
+
+                               if (isect_value == 'h') {
+                                       if (az->direction == AZ_SCROLL_HOR) {
+                                               az->alpha = 1.0f;
+                                               v2d->alpha_hor = 255;
+                                               v2d->size_hor = V2D_SCROLL_HEIGHT;
+                                               redraw = true;
+                                       }
+                               }
+                               else if (isect_value == 'v') {
+                                       if (az->direction == AZ_SCROLL_VERT) {
+                                               az->alpha = 1.0f;
+                                               v2d->alpha_vert = 255;
+                                               v2d->size_vert = V2D_SCROLL_WIDTH;
+                                               redraw = true;
+                                       }
+                               }
+                               else {
+                                       const int local_xy[2] = {xy[0] - ar->winrct.xmin, xy[1] - ar->winrct.ymin};
+                                       float dist_fac = 0.0f, alpha = 0.0f;
+
+                                       if (az->direction == AZ_SCROLL_HOR) {
+                                               dist_fac = BLI_rcti_length_y(&v2d->hor, local_xy[1]) / AZONEFADEIN;
+                                               CLAMP(dist_fac, 0.0f, 1.0f);
+                                               alpha = 1.0f - dist_fac;
+
+                                               v2d->alpha_hor = alpha * 255;
+                                               v2d->size_hor = round_fl_to_int(V2D_SCROLL_HEIGHT -
+                                                                               ((V2D_SCROLL_HEIGHT - V2D_SCROLL_HEIGHT_MIN) * dist_fac));
+                                       }
+                                       else if (az->direction == AZ_SCROLL_VERT) {
+                                               dist_fac = BLI_rcti_length_x(&v2d->vert, local_xy[0]) / AZONEFADEIN;
+                                               CLAMP(dist_fac, 0.0f, 1.0f);
+                                               alpha = 1.0f - dist_fac;
+
+                                               v2d->alpha_vert = alpha * 255;
+                                               v2d->size_vert = round_fl_to_int(V2D_SCROLL_WIDTH -
+                                                                                ((V2D_SCROLL_WIDTH - V2D_SCROLL_WIDTH_MIN) * dist_fac));
+                                       }
+                                       az->alpha = alpha;
+                                       redraw = true;
+                               }
+
+                               if (redraw) {
+                                       ED_area_tag_redraw(sa);
+                               }
+                               /* Don't return! */
+                       }
                }
        }
        
@@ -772,6 +825,9 @@ static int actionzone_invoke(bContext *C, wmOperator *op, const wmEvent *event)
                actionzone_exit(op);
                return OPERATOR_FINISHED;
        }
+       else if (ELEM(sad->az->type, AZONE_REGION_SCROLL)) {
+               return OPERATOR_PASS_THROUGH;
+       }
        else {
                /* add modal handler */
                WM_event_add_modal_handler(C, op);
index 3319fed8cdd8587bf8a4254289916d0229ce9f25..a0480aa361bc266a620700e16f85b4dc78fb709b 100644 (file)
@@ -64,6 +64,11 @@ typedef struct View2D {
        int tab_num;                                    /* number of tabs stored */
        int tab_cur;                                    /* current tab */
 
+       /* Usually set externally (as in, not in view2d files). */
+       char alpha_vert, alpha_hor;             /* alpha of vertical and horizontal scrollbars (range is [0, 255]) */
+       short size_vert, size_hor;              /* Dynamic size for scrollers without scale markers (no V2D_SCROLL_SCALE_FOO) */
+       short pad;
+
        /* animated smooth view */
        struct SmoothView2DStore *sms;
        struct wmTimer *smooth_timer;