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,47 +81,54 @@ 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,
child: SingleChildScrollView( bottom: value.bottom,
scrollDirection: Axis.horizontal, left: value.left,
child: MobileSelectionMenuWidget( right: value.right,
selectionMenuStyle: style, child: SingleChildScrollView(
singleColumn: singleColumn, scrollDirection: Axis.horizontal,
items: selectionMenuItems child: MobileSelectionMenuWidget(
..forEach((element) { selectionMenuStyle: style,
if (element is MobileSelectionMenuItem) { singleColumn: singleColumn,
element.deleteSlash = false; items: selectionMenuItems
element.deleteKeywords = deleteKeywordsByDefault; ..forEach((element) {
for (final e in element.children) { if (element is MobileSelectionMenuItem) {
e.deleteSlash = deleteSlashByDefault; element.deleteSlash = false;
e.deleteKeywords = deleteKeywordsByDefault; element.deleteKeywords =
e.onSelected = () { deleteKeywordsByDefault;
dismiss(); for (final e in element.children) {
}; e.deleteSlash = deleteSlashByDefault;
} e.deleteKeywords = deleteKeywordsByDefault;
} else { e.onSelected = () {
element.deleteSlash = deleteSlashByDefault; dismiss();
element.deleteKeywords = deleteKeywordsByDefault; };
element.onSelected = () { }
dismiss(); } else {
}; element.deleteSlash = deleteSlashByDefault;
} element.deleteKeywords =
}), deleteKeywordsByDefault;
maxItemInRow: 5, element.onSelected = () {
editorState: editorState, dismiss();
itemCountFilter: itemCountFilter, };
startOffset: startOffset, }
menuService: this, }),
onExit: () { maxItemInRow: 5,
dismiss(); editorState: editorState,
}, itemCountFilter: itemCountFilter,
deleteSlashByDefault: deleteSlashByDefault, startOffset: startOffset,
), menuService: this,
), onExit: () {
dismiss();
},
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;
}