SideBySide2: Reduce scrolling jank
Update the other CodeMirror3 top position immediately, rather than waiting with a delay. This improves UI feel by reducing the visible jank during scrolling. In the common case of scrolling a large number of lines that are unmodified and visible, the line heights are identical and adjusting the Y position immediately comes up with correct results. This gives scrolling the UI a smoother feeling, like the old SideBySide view. While scrolling run a timer every 20 milliseconds to update the other CodeMirror3 with more accurate position information. This is necessary to fix up incorrect widget height estimates. The height of offscreen line widgets used for padding and comment boxes aren't computed by CM3 until they first become visible in the document, which sometimes causes the view to be unaligned. The timer uses a second 20 millisecond delay (for a total of 40 ms) to detect when scrolling ends. The full 40ms delay must elapse before the user can switch to the other CM3 instance and begin scrolling again. Scrolling on the same document is always available and resets the timer for another 40ms delay. Change-Id: I40d6271cd5103249f43e9ecaf6eb1e75181a7dbb
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.Viewport;
|
||||
import net.codemirror.lib.ScrollInfo;
|
||||
|
||||
class ScrollSynchronizer {
|
||||
private DiffTable diffTable;
|
||||
private LineMapper mapper;
|
||||
private ScrollCallback active;
|
||||
|
||||
void init(DiffTable diffTable,
|
||||
CodeMirror cmA, CodeMirror cmB,
|
||||
LineMapper mapper) {
|
||||
this.diffTable = diffTable;
|
||||
this.mapper = mapper;
|
||||
|
||||
cmA.on("scroll", new ScrollCallback(cmA, cmB, DisplaySide.A));
|
||||
cmB.on("scroll", new ScrollCallback(cmB, cmA, DisplaySide.B));
|
||||
}
|
||||
|
||||
private void updateScreenHeader(CodeMirror cm) {
|
||||
ScrollInfo si = cm.getScrollInfo();
|
||||
if (si.getTop() == 0 && !Gerrit.isHeaderVisible()) {
|
||||
Gerrit.setHeaderVisible(true);
|
||||
diffTable.updateFileCommentVisibility(false);
|
||||
} else if (si.getTop() > 0.5 * si.getClientHeight()
|
||||
&& Gerrit.isHeaderVisible()) {
|
||||
Gerrit.setHeaderVisible(false);
|
||||
diffTable.updateFileCommentVisibility(true);
|
||||
}
|
||||
}
|
||||
|
||||
class ScrollCallback implements Runnable {
|
||||
private final CodeMirror src;
|
||||
private final CodeMirror dst;
|
||||
private final DisplaySide srcSide;
|
||||
private final Timer fixup;
|
||||
private int state;
|
||||
|
||||
ScrollCallback(CodeMirror src, CodeMirror dst, DisplaySide srcSide) {
|
||||
this.src = src;
|
||||
this.dst = dst;
|
||||
this.srcSide = srcSide;
|
||||
this.fixup = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (active == ScrollCallback.this) {
|
||||
fixup();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (active == null) {
|
||||
active = this;
|
||||
fixup.scheduleRepeating(20);
|
||||
}
|
||||
if (active == this) {
|
||||
updateScreenHeader(src);
|
||||
dst.scrollToY(src.getScrollInfo().getTop());
|
||||
state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void fixup() {
|
||||
switch (state) {
|
||||
case 0:
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
state = 2;
|
||||
return;
|
||||
case 2:
|
||||
active = null;
|
||||
fixup.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Since CM doesn't always take the height of line widgets into
|
||||
// account when calculating scrollInfo when scrolling too fast (e.g.
|
||||
// throw scrolling), simply setting scrollTop to be the same doesn't
|
||||
// guarantee alignment.
|
||||
//
|
||||
// Iterate over the viewport to find the first line that isn't part of
|
||||
// an insertion or deletion gap, for which isAligned() will be true.
|
||||
// We then manually examine if the lines that should be aligned are at
|
||||
// the same height. If not, perform additional scrolling.
|
||||
Viewport fromTo = src.getViewport();
|
||||
for (int line = fromTo.getFrom(); line <= fromTo.getTo(); line++) {
|
||||
LineOnOtherInfo info = mapper.lineOnOther(srcSide, line);
|
||||
if (info.isAligned()) {
|
||||
double sy = src.heightAtLine(line);
|
||||
double dy = dst.heightAtLine(info.getLine());
|
||||
if (Math.abs(dy - sy) >= 1) {
|
||||
dst.scrollToY(dst.getScrollInfo().getTop() + (dy - sy));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user