fix: the slash menu position sometimes is wrong (#7492)

This commit is contained in:
Morn 2025-03-13 13:58:06 +08:00 committed by GitHub
parent c7d3d612ae
commit 9bd13ac29e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -36,12 +36,16 @@ class MobileSelectionMenu extends SelectionMenuService {
Alignment _alignment = Alignment.topLeft; Alignment _alignment = Alignment.topLeft;
final int itemCountFilter; final int itemCountFilter;
final int startOffset; final int startOffset;
ValueNotifier<_Position> _positionNotifier = ValueNotifier(_Position.zero);
@override @override
void dismiss() { void dismiss() {
if (_selectionMenuEntry != null) { if (_selectionMenuEntry != null) {
editorState.service.keyboardService?.enable(); editorState.service.keyboardService?.enable();
editorState.service.scrollService?.enable(); editorState.service.scrollService?.enable();
editorState
.removeScrollViewScrolledListener(_checkPositionAfterScrolling);
_positionNotifier.dispose();
} }
_selectionMenuEntry?.remove(); _selectionMenuEntry?.remove();
@ -53,23 +57,20 @@ class MobileSelectionMenu extends SelectionMenuService {
final completer = Completer<void>(); final completer = Completer<void>();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_show(); _show();
editorState.addScrollViewScrolledListener(_checkPositionAfterScrolling);
completer.complete(); completer.complete();
}); });
return completer.future; return completer.future;
} }
void _show() { void _show() {
final selectionRects = editorState.selectionRects(); final position = _getCurrentPosition();
if (selectionRects.isEmpty) { if (position == null) return;
return;
}
calculateSelectionMenuOffset(selectionRects.first);
final (left, top, right, bottom) = getPosition();
final editorHeight = editorState.renderBox!.size.height; final editorHeight = editorState.renderBox!.size.height;
final editorWidth = editorState.renderBox!.size.width; final editorWidth = editorState.renderBox!.size.width;
_positionNotifier = ValueNotifier(position);
_selectionMenuEntry = OverlayEntry( _selectionMenuEntry = OverlayEntry(
builder: (context) { builder: (context) {
return SizedBox( return SizedBox(
@ -80,11 +81,14 @@ class MobileSelectionMenu extends SelectionMenuService {
onTap: dismiss, onTap: dismiss,
child: Stack( child: Stack(
children: [ children: [
Positioned( ValueListenableBuilder(
top: top, valueListenable: _positionNotifier,
bottom: bottom, builder: (context, value, _) {
left: left, return Positioned(
right: right, top: value.top,
bottom: value.bottom,
left: value.left,
right: value.right,
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: MobileSelectionMenuWidget( child: MobileSelectionMenuWidget(
@ -94,7 +98,8 @@ class MobileSelectionMenu extends SelectionMenuService {
..forEach((element) { ..forEach((element) {
if (element is MobileSelectionMenuItem) { if (element is MobileSelectionMenuItem) {
element.deleteSlash = false; element.deleteSlash = false;
element.deleteKeywords = deleteKeywordsByDefault; element.deleteKeywords =
deleteKeywordsByDefault;
for (final e in element.children) { for (final e in element.children) {
e.deleteSlash = deleteSlashByDefault; e.deleteSlash = deleteSlashByDefault;
e.deleteKeywords = deleteKeywordsByDefault; e.deleteKeywords = deleteKeywordsByDefault;
@ -104,7 +109,8 @@ class MobileSelectionMenu extends SelectionMenuService {
} }
} else { } else {
element.deleteSlash = deleteSlashByDefault; element.deleteSlash = deleteSlashByDefault;
element.deleteKeywords = deleteKeywordsByDefault; element.deleteKeywords =
deleteKeywordsByDefault;
element.onSelected = () { element.onSelected = () {
dismiss(); dismiss();
}; };
@ -121,6 +127,8 @@ class MobileSelectionMenu extends SelectionMenuService {
deleteSlashByDefault: deleteSlashByDefault, deleteSlashByDefault: deleteSlashByDefault,
), ),
), ),
);
},
), ),
], ],
), ),
@ -135,6 +143,34 @@ class MobileSelectionMenu extends SelectionMenuService {
editorState.service.scrollService?.disable(); editorState.service.scrollService?.disable();
} }
/// the workaround for: editor auto scrolling that will cause wrong position
/// of slash menu
void _checkPositionAfterScrolling() {
final position = _getCurrentPosition();
if (position == null) return;
if (position == _positionNotifier.value) {
Future.delayed(const Duration(milliseconds: 100)).then((_) {
final position = _getCurrentPosition();
if (position == null) return;
if (position != _positionNotifier.value) {
_positionNotifier.value = position;
}
});
} else {
_positionNotifier.value = position;
}
}
_Position? _getCurrentPosition() {
final selectionRects = editorState.selectionRects();
if (selectionRects.isEmpty) {
return null;
}
calculateSelectionMenuOffset(selectionRects.first);
final (left, top, right, bottom) = getPosition();
return _Position(left, top, right, bottom);
}
@override @override
Alignment get alignment { Alignment get alignment {
return _alignment; return _alignment;
@ -166,7 +202,6 @@ class MobileSelectionMenu extends SelectionMenuService {
bottom = offset.dy; bottom = offset.dy;
break; break;
} }
return (left, top, right, bottom); return (left, top, right, bottom);
} }
@ -217,3 +252,28 @@ class MobileSelectionMenu extends SelectionMenuService {
} }
} }
} }
class _Position {
const _Position(this.left, this.top, this.right, this.bottom);
final double? left;
final double? top;
final double? right;
final double? bottom;
static const _Position zero = _Position(0, 0, 0, 0);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _Position &&
runtimeType == other.runtimeType &&
left == other.left &&
top == other.top &&
right == other.right &&
bottom == other.bottom;
@override
int get hashCode =>
left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode;
}