Skip to content

Commit

Permalink
WIP: Implemented painting non-primary text selections in SuperEditor
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-carroll committed Jul 19, 2022
1 parent 8fb1881 commit 34e56ba
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,6 @@ class HeaderWithHintComponentBuilder implements ComponentBuilder {
return null;
}

final textSelection = componentViewModel.selection;

return TextWithHintComponent(
key: componentContext.componentKey,
text: componentViewModel.text,
Expand All @@ -188,8 +186,7 @@ class HeaderWithHintComponentBuilder implements ComponentBuilder {
hintStyleBuilder: (Set<Attribution> attributions) => _textStyleBuilder(attributions).copyWith(
color: const Color(0xFFDDDDDD),
),
textSelection: textSelection,
selectionColor: componentViewModel.selectionColor,
styledSelections: componentViewModel.styledSelections,
showCaret: componentViewModel.caret != null,
);
}
Expand Down
33 changes: 10 additions & 23 deletions super_editor/example/lib/demos/example_editor/_task.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:super_editor/super_editor.dart';

Expand Down Expand Up @@ -96,7 +97,6 @@ class TaskComponentBuilder implements ComponentBuilder {
},
text: node.text,
textStyleBuilder: noStyleBuilder,
selectionColor: const Color(0x00000000),
caretColor: const Color(0x00000000),
);
}
Expand Down Expand Up @@ -132,12 +132,11 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
required this.textStyleBuilder,
this.textDirection = TextDirection.ltr,
this.textAlignment = TextAlign.left,
this.selection,
required this.selectionColor,
List<StyledSelection<TextSelection>>? styledSelections,
required this.caretColor,
this.caret,
this.highlightWhenEmpty = false,
}) : super(nodeId: nodeId, maxWidth: maxWidth, padding: padding);
}) : styledSelections = styledSelections ?? [],
super(nodeId: nodeId, maxWidth: maxWidth, padding: padding);

bool isComplete;
void Function(bool) setComplete;
Expand All @@ -150,15 +149,11 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
@override
TextAlign textAlignment;
@override
TextSelection? selection;
@override
Color selectionColor;
List<StyledSelection<TextSelection>> styledSelections;
@override
TextPosition? caret;
@override
Color caretColor;
@override
bool highlightWhenEmpty;

@override
TaskComponentViewModel copy() {
Expand All @@ -171,11 +166,9 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
text: text,
textStyleBuilder: textStyleBuilder,
textDirection: textDirection,
selection: selection,
selectionColor: selectionColor,
styledSelections: List.from(styledSelections),
caret: caret,
caretColor: caretColor,
highlightWhenEmpty: highlightWhenEmpty,
);
}

Expand All @@ -191,11 +184,9 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
textStyleBuilder == other.textStyleBuilder &&
textDirection == other.textDirection &&
textAlignment == other.textAlignment &&
selection == other.selection &&
selectionColor == other.selectionColor &&
caret == other.caret &&
caretColor == other.caretColor &&
highlightWhenEmpty == other.highlightWhenEmpty;
const DeepCollectionEquality().equals(styledSelections, other.styledSelections);

@override
int get hashCode =>
Expand All @@ -206,11 +197,9 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
textStyleBuilder.hashCode ^
textDirection.hashCode ^
textAlignment.hashCode ^
selection.hashCode ^
selectionColor.hashCode ^
styledSelections.hashCode ^
caret.hashCode ^
caretColor.hashCode ^
highlightWhenEmpty.hashCode;
caretColor.hashCode;
}

/// A document component that displays a complete-able task.
Expand Down Expand Up @@ -262,11 +251,9 @@ class TaskComponent extends StatelessWidget {
)
: style;
},
textSelection: viewModel.selection,
selectionColor: viewModel.selectionColor,
styledSelections: viewModel.styledSelections,
showCaret: viewModel.caret != null,
caretColor: viewModel.caretColor,
highlightWhenEmpty: viewModel.highlightWhenEmpty,
showDebugPaint: showDebugPaint,
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,21 @@ class _ExampleEditorState extends State<ExampleEditor> {
super.initState();
_doc = createInitialDocument()..addListener(_hideOrShowToolbar);
_docEditor = DocumentEditor(document: _doc as MutableDocument);
_composer = DocumentComposer()..addListener(_hideOrShowToolbar);
final nodeId = _doc.nodes[2].id;
_composer = DocumentComposer()
..addListener(_hideOrShowToolbar)
..setNonPrimarySelection(
"john",
DocumentSelection(
base: DocumentPosition(
nodeId: nodeId,
nodePosition: TextNodePosition.fromTextPosition(TextPosition(offset: 10)),
),
extent: DocumentPosition(
nodeId: nodeId,
nodePosition: TextNodePosition.fromTextPosition(TextPosition(offset: 50)),
),
));
_docOps = CommonEditorOperations(
editor: _docEditor,
composer: _composer,
Expand Down Expand Up @@ -342,6 +356,12 @@ class _ExampleEditorState extends State<ExampleEditor> {
taskStyles,
],
),
nonPrimarySelectionStyler: (NonPrimarySelection selection) {
return SelectionStyles(
caretColor: Colors.black,
selectionColor: Colors.purpleAccent,
);
},
componentBuilders: [
...defaultComponentBuilders,
TaskComponentBuilder(_docEditor),
Expand Down
2 changes: 1 addition & 1 deletion super_editor/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ packages:
source: hosted
version: "1.1.0"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
url: "https://pub.dartlang.org"
Expand Down
1 change: 1 addition & 0 deletions super_editor/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies:
cupertino_icons: ^1.0.1

charcode: ^1.1.3
collection: ^1.15.0
flutter_keyboard_visibility: ^5.0.3
google_fonts: ^2.0.0
http: ^0.13.1
Expand Down
2 changes: 2 additions & 0 deletions super_editor/lib/src/core/document_composer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ class DocumentComposer with ChangeNotifier {
_notifyNonPrimarySelectionChange(nonPrimarySelection);
} else {
// This is a new selection.
_nonPrimarySelections[id] = nonPrimarySelection;
_notifyNonPrimarySelectionAdded(nonPrimarySelection);
}
} else if (_nonPrimarySelections.containsKey(id)) {
// Remove an existing selection.
_nonPrimarySelections.remove(id);
_notifyNonPrimarySelectionRemoval(id);
}
}
Expand Down
36 changes: 32 additions & 4 deletions super_editor/lib/src/core/styles.dart
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,33 @@ class CascadingPadding {
int get hashCode => left.hashCode ^ right.hashCode ^ top.hashCode ^ bottom.hashCode;
}

/// Styles applied to the user's selection, e.g., caret, selected text.
/// A document selection with associated visual styles.
class StyledSelection<SelectionType> {
const StyledSelection(this.selection, this.styles);

final SelectionType selection;
final SelectionStyles styles;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is StyledSelection &&
runtimeType == other.runtimeType &&
selection == other.selection &&
styles == other.styles;

@override
int get hashCode => selection.hashCode ^ styles.hashCode;
}

/// Styles applied to a document selection, e.g., caret, selected text.
class SelectionStyles {
const SelectionStyles({
required this.caretColor,
required this.selectionColor,
this.highlightEmptyTextBlocks = true,
});

// TODO: multiple user selections
// TODO: how do we handle a non-primary selection that spans an empty paragraph?

/// The color of the caret.
final Color caretColor;

Expand All @@ -289,4 +305,16 @@ class SelectionStyles {
/// Whether to show a small highlight at the beginning of an
/// empty block of text, when the user selects multiple blocks.
final bool highlightEmptyTextBlocks;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SelectionStyles &&
runtimeType == other.runtimeType &&
caretColor == other.caretColor &&
selectionColor == other.selectionColor &&
highlightEmptyTextBlocks == other.highlightEmptyTextBlocks;

@override
int get hashCode => caretColor.hashCode ^ selectionColor.hashCode ^ highlightEmptyTextBlocks.hashCode;
}
46 changes: 14 additions & 32 deletions super_editor/lib/src/default_editor/blockquote.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:attributed_text/attributed_text.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:super_editor/src/core/edit_context.dart';
import 'package:super_editor/src/core/styles.dart';
import 'package:super_editor/src/default_editor/attributions.dart';
import 'package:super_editor/src/infrastructure/_logging.dart';
import 'package:super_editor/src/infrastructure/attributed_text_styles.dart';
Expand Down Expand Up @@ -56,7 +58,6 @@ class BlockquoteComponentBuilder implements ComponentBuilder {
borderRadius: BorderRadius.zero,
textDirection: textDirection,
textAlignment: textAlign,
selectionColor: const Color(0x00000000),
caretColor: const Color(0x00000000),
);
}
Expand All @@ -74,11 +75,9 @@ class BlockquoteComponentBuilder implements ComponentBuilder {
styleBuilder: componentViewModel.textStyleBuilder,
backgroundColor: componentViewModel.backgroundColor,
borderRadius: componentViewModel.borderRadius,
textSelection: componentViewModel.selection,
selectionColor: componentViewModel.selectionColor,
styledSelections: List.from(componentViewModel.styledSelections),
showCaret: componentViewModel.caret != null,
caretColor: componentViewModel.caretColor,
highlightWhenEmpty: componentViewModel.highlightWhenEmpty,
);
}
}
Expand All @@ -94,12 +93,11 @@ class BlockquoteComponentViewModel extends SingleColumnLayoutComponentViewModel
this.textAlignment = TextAlign.left,
required this.backgroundColor,
required this.borderRadius,
this.selection,
required this.selectionColor,
List<StyledSelection<TextSelection>>? styledSelections,
this.caret,
required this.caretColor,
this.highlightWhenEmpty = false,
}) : super(nodeId: nodeId, maxWidth: maxWidth, padding: padding);
}) : styledSelections = styledSelections ?? [],
super(nodeId: nodeId, maxWidth: maxWidth, padding: padding);

AttributedText text;

Expand All @@ -110,15 +108,11 @@ class BlockquoteComponentViewModel extends SingleColumnLayoutComponentViewModel
@override
TextAlign textAlignment;
@override
TextSelection? selection;
@override
Color selectionColor;
List<StyledSelection<TextSelection>> styledSelections;
@override
TextPosition? caret;
@override
Color caretColor;
@override
bool highlightWhenEmpty;

Color backgroundColor;
BorderRadius borderRadius;
Expand All @@ -142,11 +136,9 @@ class BlockquoteComponentViewModel extends SingleColumnLayoutComponentViewModel
textAlignment: textAlignment,
backgroundColor: backgroundColor,
borderRadius: borderRadius,
selection: selection,
selectionColor: selectionColor,
styledSelections: List.from(styledSelections),
caret: caret,
caretColor: caretColor,
highlightWhenEmpty: highlightWhenEmpty,
);
}

Expand All @@ -163,11 +155,9 @@ class BlockquoteComponentViewModel extends SingleColumnLayoutComponentViewModel
textAlignment == other.textAlignment &&
backgroundColor == other.backgroundColor &&
borderRadius == other.borderRadius &&
selection == other.selection &&
selectionColor == other.selectionColor &&
caret == other.caret &&
caretColor == other.caretColor &&
highlightWhenEmpty == other.highlightWhenEmpty;
const DeepCollectionEquality().equals(styledSelections, other.styledSelections);

@override
int get hashCode =>
Expand All @@ -179,11 +169,9 @@ class BlockquoteComponentViewModel extends SingleColumnLayoutComponentViewModel
textAlignment.hashCode ^
backgroundColor.hashCode ^
borderRadius.hashCode ^
selection.hashCode ^
selectionColor.hashCode ^
styledSelections.hashCode ^
caret.hashCode ^
caretColor.hashCode ^
highlightWhenEmpty.hashCode;
caretColor.hashCode;
}

/// Displays a blockquote in a document.
Expand All @@ -193,26 +181,22 @@ class BlockquoteComponent extends StatelessWidget {
required this.textKey,
required this.text,
required this.styleBuilder,
this.textSelection,
this.selectionColor = Colors.lightBlueAccent,
this.styledSelections = const [],
required this.backgroundColor,
required this.borderRadius,
this.showCaret = false,
this.caretColor = Colors.black,
this.showDebugPaint = false,
this.highlightWhenEmpty = false,
}) : super(key: key);

final GlobalKey textKey;
final AttributedText text;
final AttributionStyleBuilder styleBuilder;
final TextSelection? textSelection;
final Color selectionColor;
final Color backgroundColor;
final BorderRadius borderRadius;
final List<StyledSelection<TextSelection>> styledSelections;
final bool showCaret;
final Color caretColor;
final bool highlightWhenEmpty;
final bool showDebugPaint;

@override
Expand All @@ -227,11 +211,9 @@ class BlockquoteComponent extends StatelessWidget {
key: textKey,
text: text,
textStyleBuilder: styleBuilder,
textSelection: textSelection,
selectionColor: selectionColor,
styledSelections: styledSelections,
showCaret: showCaret,
caretColor: caretColor,
highlightWhenEmpty: highlightWhenEmpty,
showDebugPaint: showDebugPaint,
),
);
Expand Down
Loading

0 comments on commit 34e56ba

Please sign in to comment.