Synchronize custom-scrollbar cursor geometry after relayout

This commit is contained in:
fufesou
2026-09-13 14:53:47 +08:00
parent b630f14483
commit 16088cfc80
3 changed files with 18 additions and 14 deletions

View File

@@ -1173,7 +1173,7 @@ class _ImagePaintState extends State<ImagePaint> {
child: child); child: child);
}); });
if (c.imageOverflow.isTrue && c.scrollStyle != ScrollStyle.scrollauto) { if (c.imageOverflow.isTrue && c.scrollStyle != ScrollStyle.scrollauto) {
_syncEdgeScrollAfterLayout(c); _syncScrollAfterLayout(c);
final paintWidth = c.getDisplayWidth() * s; final paintWidth = c.getDisplayWidth() * s;
final paintHeight = c.getDisplayHeight() * s; final paintHeight = c.getDisplayHeight() * s;
final paintSize = Size(paintWidth, paintHeight); final paintSize = Size(paintWidth, paintHeight);
@@ -1218,13 +1218,15 @@ class _ImagePaintState extends State<ImagePaint> {
} }
} }
void _syncEdgeScrollAfterLayout(CanvasModel canvas) { void _syncScrollAfterLayout(CanvasModel canvas) {
if (canvas.scrollStyle != ScrollStyle.scrolledge) return; // Custom scrollbars also paint from scroll fractions; preserve Original's path.
if (canvas.scrollStyle != ScrollStyle.scrolledge &&
canvas.viewStyle.style != kRemoteViewStyleCustom) return;
final renderedScroll = (canvas.scrollX, canvas.scrollY); final renderedScroll = (canvas.scrollX, canvas.scrollY);
// Relayout can clamp or detach scroll positions without a scroll event. // Relayout can clamp or detach scroll positions without a scroll event.
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
canvas.updateEdgeScrollAfterLayout(renderedScroll); canvas.updateScrollAfterLayout(renderedScroll);
}); });
} }

View File

@@ -2702,7 +2702,7 @@ class CanvasModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateEdgeScrollAfterLayout((double, double) renderedScroll) { void updateScrollAfterLayout((double, double) renderedScroll) {
updateScrollPercent(); updateScrollPercent();
// A delayed refresh may already have changed the model without repainting. // A delayed refresh may already have changed the model without repainting.
if (renderedScroll != (_scrollX, _scrollY)) { if (renderedScroll != (_scrollX, _scrollY)) {

View File

@@ -47,7 +47,7 @@ class _Texture extends Fake implements TextureModel {
} }
class _ScrollCanvas extends CanvasModel { class _ScrollCanvas extends CanvasModel {
_ScrollCanvas(FFI ffi, String style) _ScrollCanvas(FFI ffi, String style, this.scrollStyle)
: viewStyle = ViewStyle( : viewStyle = ViewStyle(
style: style, style: style,
width: _viewport.width, width: _viewport.width,
@@ -69,7 +69,7 @@ class _ScrollCanvas extends CanvasModel {
@override @override
double get y => (size.height - getDisplayHeight() * scale) / 2; double get y => (size.height - getDisplayHeight() * scale) / 2;
@override @override
ScrollStyle get scrollStyle => ScrollStyle.scrolledge; final ScrollStyle scrollStyle;
@override @override
final ViewStyle viewStyle; final ViewStyle viewStyle;
@@ -100,10 +100,10 @@ class _Cursor extends CursorModel {
class _FFI extends Fake implements FFI { class _FFI extends Fake implements FFI {
_FFI(ui.Image frame, ui.Image cursor, _FFI(ui.Image frame, ui.Image cursor,
{required bool texture, required String style, required Offset position}) {required bool texture, required (String, ScrollStyle) style, required Offset position})
: ffiModel = _Peer(frame) { : ffiModel = _Peer(frame) {
imageModel = _Image(this, frame, texture); imageModel = _Image(this, frame, texture);
canvasModel = _ScrollCanvas(this, style); canvasModel = _ScrollCanvas(this, style.$1, style.$2);
cursorModel = _Cursor(this, cursor, position); cursorModel = _Cursor(this, cursor, position);
} }
@override @override
@@ -131,7 +131,9 @@ class _Draw extends Fake implements Canvas {
} }
void main() { void main() {
for (final style in [kRemoteViewStyleOriginal, kRemoteViewStyleCustom]) { for (final style in [(kRemoteViewStyleOriginal, ScrollStyle.scrolledge),
(kRemoteViewStyleCustom, ScrollStyle.scrolledge),
(kRemoteViewStyleCustom, ScrollStyle.scrollbar)]) {
for (final texture in [false, true]) { for (final texture in [false, true]) {
for (final (frame, dpr) in [ for (final (frame, dpr) in [
(const Size(199, 320), 1.0), (const Size(199, 320), 1.0),
@@ -140,13 +142,13 @@ void main() {
(const Size(400, 158), 1.0), (const Size(400, 158), 1.0),
]) { ]) {
testWidgets( testWidgets(
'ScrollEdge $style texture=$texture frame=$frame DPR=$dpr', 'Scroll $style texture=$texture frame=$frame DPR=$dpr',
(tester) => tester (tester) => tester
.runAsync(() => _check(tester, (style, texture, frame, dpr)))); .runAsync(() => _check(tester, (style, texture, frame, dpr))));
} }
for (final refreshBeforeLayout in [true, false]) { for (final refreshBeforeLayout in [true, false]) {
testWidgets( testWidgets(
'ScrollEdge relayout $style texture=$texture early=$refreshBeforeLayout', 'Scroll relayout $style texture=$texture early=$refreshBeforeLayout',
(tester) => tester.runAsync(() => (tester) => tester.runAsync(() =>
_checkRelayout(tester, (style, texture, refreshBeforeLayout)))); _checkRelayout(tester, (style, texture, refreshBeforeLayout))));
} }
@@ -155,7 +157,7 @@ void main() {
} }
Future<void> _check( Future<void> _check(
WidgetTester tester, (String, bool, Size, double) testCase) async { WidgetTester tester, ((String, ScrollStyle), bool, Size, double) testCase) async {
final (style, texture, frame, dpr) = testCase; final (style, texture, frame, dpr) = testCase;
tester.view.devicePixelRatio = dpr; tester.view.devicePixelRatio = dpr;
tester.view.physicalSize = _viewport * dpr; tester.view.physicalSize = _viewport * dpr;
@@ -195,7 +197,7 @@ Future<void> _check(
} }
Future<void> _checkRelayout( Future<void> _checkRelayout(
WidgetTester tester, (String, bool, bool) testCase) async { WidgetTester tester, ((String, ScrollStyle), bool, bool) testCase) async {
final (style, texture, refreshBeforeLayout) = testCase; final (style, texture, refreshBeforeLayout) = testCase;
tester.view.devicePixelRatio = 1; tester.view.devicePixelRatio = 1;
tester.view.physicalSize = _viewport; tester.view.physicalSize = _viewport;