Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 510b39cbf7778e30d3ac0cb942c3db3c55517e49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*******************************************************************************
 * Copyright (c) 2011 THALES GLOBAL SERVICES.
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.sirius.diagram.ui.tools.internal.graphical.edit.part;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.editparts.ViewportAutoexposeHelper;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;

/**
 * A {@link SiriusScroller} that make the scroll bigger if the cursor is
 * outside editor (the farthest the cursor is, the highest the multiplicator
 * will be).
 * 
 * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a>
 * 
 */
public class SiriusScroller extends ViewportAutoexposeHelper {

    /**
     * The insets on which the scroll will be installed (represents the zone
     * within the editor in which scroll is active).
     */
    public static final Insets VIEWPOINT_SCROLLER_INSETS = new Insets(50);

    /** Defines the range where autoscroll is active inside a viewer. */
    private static final Insets DEFAULT_EXPOSE_THRESHOLD = new Insets(200);

    /**
     * The maximum value for the scroll multiplicator.
     */
    private static final int MAXIMUM_MULTIPLICATOR = 8;

    /**
     * The distance (in px) from the cursor to the editor bounds that causes
     * increase of the multiplicator.
     */
    private static final int DISTANCE_THAT_INCREASE_MULTIPLICATOR = 20;

    /** The last time an auto expose was performed. */
    private long lastStepTime;

    /** The insets for this helper. */
    private Insets threshold;

    /**
     * Constructs a new helper on the given GraphicalEditPart. The editpart must
     * have a <code>Viewport</code> somewhere between its <i>contentsPane</i>
     * and its <i>figure</i> inclusively.
     * 
     * @param owner
     *            the GraphicalEditPart that owns the Viewport
     */
    public SiriusScroller(GraphicalEditPart owner) {
        super(owner);
        threshold = DEFAULT_EXPOSE_THRESHOLD;
    }

    /**
     * Constructs a new helper on the given GraphicalEditPart. The editpart must
     * have a <code>Viewport</code> somewhere between its <i>contentsPane</i>
     * and its <i>figure</i> inclusively.
     * 
     * @param owner
     *            the GraphicalEditPart that owns the Viewport
     * @param threshold
     *            the Expose Threshold to use when determing whether or not a
     *            scroll should occur.
     */
    public SiriusScroller(GraphicalEditPart owner, Insets threshold) {
        super(owner);
        this.threshold = threshold;
    }

    /**
     * Returns <code>true</code> if the given point is inside the viewport, but
     * near its edge.
     * 
     * @param where
     *            the point to test
     * @return <code>true</code> if the given point is inside the viewport, but
     *         near its edge
     * @see org.eclipse.gef.AutoexposeHelper#detect(org.eclipse.draw2d.geometry.Point)
     */
    public boolean detect(Point where) {
        lastStepTime = 0;
        Viewport port = findViewport(owner);
        Rectangle rect = Rectangle.SINGLETON;
        port.getClientArea(rect);
        port.translateToParent(rect);
        port.translateToAbsolute(rect);
        return rect.contains(where) && !rect.shrink(threshold).contains(where);
    }

    /**
     * Returns <code>true</code> if the given point is outside the viewport or
     * near its edge. Scrolls the viewport by a calculated (time based) amount
     * in the current direction.
     * 
     * @param where
     *            the cursor location
     * @return <code>true</code> if the given point is outside the viewport or
     *         near its edge, false otherwise
     * 
     * @see org.eclipse.gef.AutoexposeHelper#step(org.eclipse.draw2d.geometry.Point)
     */
    public boolean step(Point where) {
        Viewport port = findViewport(owner);

        Option<Point> loc = calculateScroll(port, where);
        if (loc.some()) {
            port.setViewLocation(loc.get());
        }
        return true;
    }

    /**
     * Calculates the scroll according to the cursor location and the editor
     * bounds. If the cursor is outside the editor, then a multiplicator
     * coefficient will be applied on the scroll, according to the distance
     * between editor and cursor.
     * 
     * @param port
     *            the editor bounds
     * @param where
     *            the cursor location
     * @return the scroll according to the cursor location and the editor bounds
     */
    public Option<Point> calculateScroll(Viewport port, Point where) {

        Option<Point> result = Options.newNone();
        int multiplicator = 1;

        // Step 1 : checking that the point is valid for scroll
        Rectangle rect = Rectangle.SINGLETON;
        port.getClientArea(rect);
        port.translateToParent(rect);
        port.translateToAbsolute(rect);
        if (!rect.contains(where) || rect.shrink(threshold).contains(where)) {
            // If the zone is out of editor bounds
            // we calculate a new multiplicator according to the distance
            // between the cursor location on the bounds of the editors
            multiplicator = calculateMultiplicator(port, where);
        }

        // Step 2 : calculate scroll offset (timed base)
        int scrollOffset = 0;
        if (lastStepTime == 0)
            lastStepTime = System.currentTimeMillis();

        long difference = System.currentTimeMillis() - lastStepTime;

        if (difference > 0) {
            scrollOffset = (int) difference / 3;
            lastStepTime = System.currentTimeMillis();
        }

        // Step 3 : calculate scroll location
        if (scrollOffset != 0) {
            scrollOffset = multiplicator * scrollOffset;
            rect.shrink(threshold);

            int region = rect.getPosition(where);
            Point loc = port.getViewLocation();

            loc = updateLocationAccordingToPosition(scrollOffset, region, loc);
            result = Options.newSome(loc);
        }
        return result;
    }

    /**
     * Calculates the mutliplicator to apply on the offset, according to the
     * cursor distance from editor : the farthest the cursor is, the highest the
     * multiplicator will be.
     * 
     * @param port
     *            the editor bounds
     * @param where
     *            the cursor location
     * @return the mutliplicator to apply on the offset
     */
    private int calculateMultiplicator(Viewport port, Point where) {
        int mutliplicator = 1;
        double maximalDistance = 0;

        // Getting the distance between the cursor and the editor bounds
        // left distance
        if (where.x < port.getBounds().x) {
            maximalDistance = Math.abs(port.getBounds().x - where.x);
        }

        // right distance
        if (where.x > (port.getBounds().x + port.getBounds().width)) {
            maximalDistance = where.x - (port.getBounds().x + port.getBounds().width);
        }
        // top distance
        if (where.y < port.getBounds().y) {
            double topDistance = Math.abs(port.getBounds().y - where.y);
            if (topDistance > maximalDistance) {
                maximalDistance = topDistance;
            }
        }
        // bottom distance
        if (where.y > (port.getBounds().y + port.getBounds().height)) {
            double bottomDistance = where.y - (port.getBounds().y + port.getBounds().height);
            if (bottomDistance > maximalDistance) {
                maximalDistance = bottomDistance;
            }
        }

        // Calculating the multiplicator accord to maximal distance
        int candidateMultiplicator = (int) maximalDistance / DISTANCE_THAT_INCREASE_MULTIPLICATOR + 1;
        if (candidateMultiplicator > mutliplicator) {
            mutliplicator = Math.min(MAXIMUM_MULTIPLICATOR, candidateMultiplicator);
        }
        return mutliplicator;
    }

    private Point updateLocationAccordingToPosition(int scrollOffset, int region, Point loc) {
        Point result = loc;
        if ((region & PositionConstants.SOUTH) != 0) {
            result.y += scrollOffset;
        } else {
            if ((region & PositionConstants.NORTH) != 0) {
                result.y -= scrollOffset;
            }
        }

        if ((region & PositionConstants.EAST) != 0) {
            result.x += scrollOffset;
        } else {
            if ((region & PositionConstants.WEST) != 0) {
                result.x -= scrollOffset;
            }
        }
        return result;
    }

    /**
     * 
     * {@inheritDoc}
     * 
     * @see org.eclipse.gef.editparts.ViewportAutoexposeHelper#toString()
     */
    public String toString() {
        return "ViewportAutoexposeHelper for: " + owner; //$NON-NLS-1$
    }

}

Back to the top