Skip to content

Commit 25b5d80

Browse files
committed
Attempting to fix more editor scroll resetting jank
1 parent 78c21d9 commit 25b5d80

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/Editor.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.fxmisc.flowless.VirtualFlow;
1919
import org.fxmisc.richtext.CodeArea;
2020
import org.fxmisc.richtext.GenericStyledArea;
21+
import org.fxmisc.richtext.StyleActions;
2122
import org.fxmisc.richtext.model.PlainTextChange;
2223
import org.fxmisc.richtext.model.ReadOnlyStyledDocument;
2324
import org.fxmisc.richtext.model.StyleSpans;
@@ -177,7 +178,7 @@ else if (e.getCode() == KeyCode.ENTER)
177178
int start = range.start();
178179
int end = range.end();
179180
return new StyleResult(syntaxHighlighter.createStyleSpans(text, start, end), start);
180-
}, result -> codeArea.setStyleSpans(result.position(), result.spans()));
181+
}, result -> setStyleSpans(result.position(), result.spans()));
181182
}
182183
}
183184

@@ -248,7 +249,29 @@ public void showParagraphAtCenter(int paragraph) {
248249
// - Assuming all cells are the same height, we can compute the offset needed to center the paragraph.
249250
int count = 1 + Math.max(virtualFlow.getLastVisibleIndex() - virtualFlow.getFirstVisibleIndex(), 0);
250251
double paragraphHeight = getHeight() / count;
251-
virtualFlow.showAtOffset(paragraph, count/2.0 * paragraphHeight/2.0);
252+
virtualFlow.showAtOffset(paragraph, count / 2.0 * paragraphHeight / 2.0);
253+
}
254+
255+
/**
256+
* Delegates to {@link StyleActions#setStyleSpans(int, StyleSpans)} but with some scroll-position preservation logic.
257+
*
258+
* @param from
259+
* Text position to start applying styles at.
260+
* @param spans
261+
* Style spans to apply.
262+
*/
263+
private void setStyleSpans(int from, @Nonnull StyleSpans<Collection<String>> spans) {
264+
// Updating the styles can cause the 'Navigator' to set its target position back to zero for... some reason.
265+
// See Navigator:
266+
// - setTargetPosition
267+
// - scrollCurrentPositionBy
268+
// To prevent jank, we record the first visible index before the update, and restore it after.
269+
//
270+
// We use the CodeArea's "showParagraphAtTop" instead of our direct one on the VirtualFlow, because the CodeArea's
271+
// suspension handling is necessary to keep event ordering correct (where the restoration happens after the janky reset).
272+
int virtualFlowFirst = virtualFlow.getFirstVisibleIndex();
273+
codeArea.setStyleSpans(from, spans);
274+
codeArea.showParagraphAtTop(virtualFlowFirst);
252275
}
253276

254277
/**
@@ -272,7 +295,7 @@ public CompletableFuture<Void> restyleAtPosition(int position, int length) {
272295
int start = range.start();
273296
int end = range.end();
274297
return new StyleResult(syntaxHighlighter.createStyleSpans(getText(), start, end), start);
275-
}, result -> codeArea.setStyleSpans(result.position(), result.spans()));
298+
}, result -> setStyleSpans(result.position(), result.spans()));
276299
}
277300
return CompletableFuture.completedFuture(null);
278301
}
@@ -457,7 +480,7 @@ public void setSyntaxHighlighter(@Nullable SyntaxHighlighter syntaxHighlighter)
457480
syntaxHighlighter.install(this);
458481
String text = getText();
459482
if (!text.isBlank())
460-
codeArea.setStyleSpans(0, syntaxHighlighter.createStyleSpans(text, 0, getTextLength()));
483+
setStyleSpans(0, syntaxHighlighter.createStyleSpans(text, 0, getTextLength()));
461484
}
462485
}
463486

recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/inheritance/InheritanceGutterGraphicFactory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ public void apply(@Nonnull LineContainer container, int paragraph) {
9696

9797
// Add inheritance icons.
9898
if (!parents.isEmpty()) {
99-
Node node = SVG.ofIconFile(SVG.METHOD_IMPLEMENTING);
99+
boolean isInterface = Objects.requireNonNull(parents.getFirst().path().getParent()).getValue().hasInterfaceModifier();
100+
Node node = SVG.ofIconFile(isInterface ? SVG.METHOD_IMPLEMENTING : SVG.METHOD_OVERRIDING);
100101
node.setCursor(Cursor.HAND);
101102
box.getChildren().add(node);
102103
box.setOnMousePressed(e -> {
@@ -116,7 +117,8 @@ public void apply(@Nonnull LineContainer container, int paragraph) {
116117
});
117118
}
118119
if (!children.isEmpty()) {
119-
Node node = SVG.ofIconFile(SVG.METHOD_OVERRIDDEN);
120+
boolean isInterface = Objects.requireNonNull(children.getFirst().path().getParent()).getValue().hasInterfaceModifier();
121+
Node node = SVG.ofIconFile(isInterface ? SVG.METHOD_IMPLEMENTED : SVG.METHOD_OVERRIDDEN);
120122
node.setCursor(Cursor.HAND);
121123
box.getChildren().add(node);
122124
box.setOnMousePressed(e -> {

0 commit comments

Comments
 (0)