Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: select multiple lines with block selection style #936

Merged
merged 3 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example/lib/pages/desktop_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ class _DesktopEditorState extends State<DesktopEditor> {
map.forEach((key, value) {
value.configuration = value.configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 8.0),
blockSelectionAreaMargin: (_) => const EdgeInsets.symmetric(
vertical: 1.0,
),
);

if (key != PageBlockKeys.type) {
Expand Down
77 changes: 72 additions & 5 deletions example/lib/pages/drag_to_reorder_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,33 @@ class DragToReorderAction extends StatefulWidget {
State<DragToReorderAction> createState() => _DragToReorderActionState();
}

const _interceptorKey = 'drag_to_reorder_interceptor';

class _DragToReorderActionState extends State<DragToReorderAction> {
late final Node node;
late final BlockComponentContext blockComponentContext;
late final EditorState editorState = context.read<EditorState>();

Offset? globalPosition;

late final gestureInterceptor = SelectionGestureInterceptor(
key: _interceptorKey,
canTap: (details) => !_isTapInBounds(details.globalPosition),
);

// the selection will be cleared when tap the option button
// so we need to restore the selection after tap the option button
Selection? beforeSelection;
RenderBox? get renderBox => context.findRenderObject() as RenderBox?;

@override
void initState() {
super.initState();

editorState.service.selectionService.registerGestureInterceptor(
gestureInterceptor,
);

// copy the node to avoid the node in document being updated
node = widget.blockComponentContext.node.copyWith();
blockComponentContext = BlockComponentContext(
Expand All @@ -146,6 +162,15 @@ class _DragToReorderActionState extends State<DragToReorderAction> {
);
}

@override
void dispose() {
editorState.service.selectionService.unregisterGestureInterceptor(
_interceptorKey,
);

super.dispose();
}

@override
Widget build(BuildContext context) {
return Padding(
Expand Down Expand Up @@ -192,17 +217,59 @@ class _DragToReorderActionState extends State<DragToReorderAction> {
globalPosition!,
);
},
child: const MouseRegion(
cursor: SystemMouseCursors.grab,
child: Icon(
Icons.drag_indicator_rounded,
size: 18,
child: GestureDetector(
onTap: _onTap,
behavior: HitTestBehavior.translucent,
child: const MouseRegion(
cursor: SystemMouseCursors.grab,
child: Icon(
Icons.drag_indicator_rounded,
size: 18,
),
),
),
),
);
}

void _onTap() {
final path = widget.blockComponentContext.node.path;

debugPrint('onTap, path($path), beforeSelection($beforeSelection)');

if (beforeSelection != null && path.inSelection(beforeSelection)) {
debugPrint('onTap(1), set selection to block');
editorState.updateSelectionWithReason(
beforeSelection,
customSelectionType: SelectionType.block,
);
} else {
debugPrint('onTap(2), set selection to block');
final selection = Selection.collapsed(
Position(path: path),
);
editorState.updateSelectionWithReason(
selection,
customSelectionType: SelectionType.block,
);
}
}

bool _isTapInBounds(Offset offset) {
if (renderBox == null) {
return false;
}

final localPosition = renderBox!.globalToLocal(offset);
final result = renderBox!.paintBounds.contains(localPosition);
if (result) {
beforeSelection = editorState.selection;
} else {
beforeSelection = null;
}
return result;
}

Future<void> _moveNodeToNewPosition(
Node node,
Path? acceptedPath,
Expand Down
13 changes: 11 additions & 2 deletions lib/src/core/document/path.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,18 @@ extension PathExtensions on Path {
return true;
}

bool inSelection(Selection? selection) {
return selection != null &&
// if isSameDepth is true, the path must be the same depth as the selection
bool inSelection(
Selection? selection, {
bool isSameDepth = false,
}) {
selection = selection?.normalized;
bool result = selection != null &&
selection.start.path <= this &&
this <= selection.end.path;
if (isSameDepth) {
return result && selection.start.path.length == length;
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class BlockComponentConfiguration {
this.placeholderText = _placeholderText,
this.textStyle = _textStyle,
this.placeholderTextStyle = _placeholderTextStyle,
this.blockSelectionAreaMargin = _blockSelectionAreaPadding,
});

/// The padding of a block component.
Expand All @@ -35,17 +36,23 @@ class BlockComponentConfiguration {
/// It inherits the style from [textStyle].
final TextStyle Function(Node node) placeholderTextStyle;

/// The padding of a block selection area.
final EdgeInsets Function(Node node) blockSelectionAreaMargin;

BlockComponentConfiguration copyWith({
EdgeInsets Function(Node node)? padding,
TextStyle Function(Node node)? textStyle,
String Function(Node node)? placeholderText,
TextStyle Function(Node node)? placeholderTextStyle,
EdgeInsets Function(Node node)? blockSelectionAreaMargin,
}) {
return BlockComponentConfiguration(
padding: padding ?? this.padding,
textStyle: textStyle ?? this.textStyle,
placeholderText: placeholderText ?? this.placeholderText,
placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle,
blockSelectionAreaMargin:
blockSelectionAreaMargin ?? this.blockSelectionAreaMargin,
);
}
}
Expand Down Expand Up @@ -90,3 +97,7 @@ TextStyle _placeholderTextStyle(Node node) {
color: Colors.grey,
);
}

EdgeInsets _blockSelectionAreaPadding(Node node) {
return const EdgeInsets.symmetric(vertical: 0.0);
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,22 @@ class _BlockSelectionAreaState extends State<BlockSelectionArea> {
return sizedBox;
}

if (context.read<EditorState>().selectionType == SelectionType.block) {
final editorState = context.read<EditorState>();
if (editorState.selectionType == SelectionType.block) {
if (!widget.supportTypes.contains(BlockSelectionType.block) ||
!path.equals(selection.start.path) ||
!path.inSelection(selection, isSameDepth: true) ||
prevBlockRect == null) {
return sizedBox;
}
final builder = editorState.service.rendererService
.blockComponentBuilder(widget.node.type);
final padding = builder?.configuration.blockSelectionAreaMargin(
widget.node,
);
return Positioned.fromRect(
rect: prevBlockRect!,
child: Container(
margin: padding,
decoration: BoxDecoration(
color: widget.blockColor,
borderRadius: BorderRadius.circular(4),
Expand Down Expand Up @@ -170,7 +177,7 @@ class _BlockSelectionAreaState extends State<BlockSelectionArea> {
if (selection != null && path.inSelection(selection)) {
if (widget.supportTypes.contains(BlockSelectionType.block) &&
context.read<EditorState>().selectionType == SelectionType.block) {
if (!path.equals(selection.start.path)) {
if (!path.inSelection(selection, isSameDepth: true)) {
if (prevBlockRect != null) {
setState(() {
prevBlockRect = null;
Expand Down
5 changes: 4 additions & 1 deletion lib/src/editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,17 @@
Selection? selection, {
SelectionUpdateReason reason = SelectionUpdateReason.transaction,
Map? extraInfo,
SelectionType? customSelectionType,
}) async {
final completer = Completer<void>();

if (reason == SelectionUpdateReason.uiEvent) {
selectionType = SelectionType.inline;
selectionType = customSelectionType ?? SelectionType.inline;
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) => completer.complete(),
);
} else if (customSelectionType != null) {
selectionType = customSelectionType;

Check warning on line 243 in lib/src/editor_state.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/editor_state.dart#L243

Added line #L243 was not covered by tests
}

// broadcast to other users here
Expand Down
Loading