Skip to content

Commit

Permalink
Added maxContentWidth constraint to ZefyrEditor (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
pulyaevskiy committed Dec 31, 2021
1 parent ef283f7 commit c5606a2
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 10 deletions.
11 changes: 11 additions & 0 deletions packages/zefyr/CHANGELOG.md
@@ -1,3 +1,14 @@
## 1.0.0-rc.4

* Added `maxContentWidth` constraint to ZefyrEditor. When this property is not null the editor's
content will only stretch up to the specified value. This does not affect the overall editor
width, it will still try to expand horizontally to fill the entire available area. Side effects
of setting `maxContentWidth` include:
- the scrollbar (for desktop platforms) will appear at the right edge of the full editor width,
which may not be the same as the right edge of the content.
- gesture and mouse events outside of the content area are still handled by the editor.
Both of the above are desired behaviors.

## 1.0.0-rc.3

* Added keyboard shortcuts for bold, italic and underline styles. ([#580](https://github.com/memspace/zefyr/pull/580))
Expand Down
3 changes: 2 additions & 1 deletion packages/zefyr/example/lib/src/home.dart
Expand Up @@ -166,14 +166,15 @@ class _HomePageState extends State<HomePage> {
Expanded(
child: Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
padding: const EdgeInsets.only(left: 16.0, right: 0.0),
child: ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
autofocus: true,
// readOnly: true,
// padding: EdgeInsets.only(left: 16, right: 16),
onLaunchUrl: _launchUrl,
maxContentWidth: 800,
),
),
),
Expand Down
16 changes: 8 additions & 8 deletions packages/zefyr/lib/src/rendering/editable_box.dart
Expand Up @@ -207,7 +207,7 @@ class RenderEditableContainerBox extends RenderBox
EdgeInsets? get resolvedPadding => _resolvedPadding;
EdgeInsets? _resolvedPadding;

void _resolvePadding() {
void resolvePadding() {
if (_resolvedPadding != null) {
return;
}
Expand Down Expand Up @@ -251,7 +251,7 @@ class RenderEditableContainerBox extends RenderBox
/// returns the last child.
RenderEditableBox childAtOffset(Offset offset) {
assert(firstChild != null);
_resolvePadding();
resolvePadding();

if (offset.dy <= _resolvedPadding!.top) return firstChild!;
if (offset.dy >= size.height - _resolvedPadding!.bottom) return lastChild!;
Expand Down Expand Up @@ -306,7 +306,7 @@ class RenderEditableContainerBox extends RenderBox
]);
}());

_resolvePadding();
resolvePadding();
assert(_resolvedPadding != null);

var mainAxisExtent = _resolvedPadding!.top;
Expand Down Expand Up @@ -358,7 +358,7 @@ class RenderEditableContainerBox extends RenderBox

@override
double computeMinIntrinsicWidth(double height) {
_resolvePadding();
resolvePadding();
final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
return _getIntrinsicCrossAxis((RenderBox child) {
Expand All @@ -369,7 +369,7 @@ class RenderEditableContainerBox extends RenderBox

@override
double computeMaxIntrinsicWidth(double height) {
_resolvePadding();
resolvePadding();
final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
return _getIntrinsicCrossAxis((RenderBox child) {
Expand All @@ -380,7 +380,7 @@ class RenderEditableContainerBox extends RenderBox

@override
double computeMinIntrinsicHeight(double width) {
_resolvePadding();
resolvePadding();
final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
return _getIntrinsicMainAxis((RenderBox child) {
Expand All @@ -391,7 +391,7 @@ class RenderEditableContainerBox extends RenderBox

@override
double computeMaxIntrinsicHeight(double width) {
_resolvePadding();
resolvePadding();
final horizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right;
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
return _getIntrinsicMainAxis((RenderBox child) {
Expand All @@ -402,7 +402,7 @@ class RenderEditableContainerBox extends RenderBox

@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
_resolvePadding();
resolvePadding();
return defaultComputeDistanceToFirstActualBaseline(baseline)! +
_resolvedPadding!.top;
}
Expand Down
64 changes: 64 additions & 0 deletions packages/zefyr/lib/src/rendering/editor.dart
Expand Up @@ -136,13 +136,15 @@ class RenderEditor extends RenderEditableContainerBox
this.onSelectionChanged,
EdgeInsets floatingCursorAddedMargin =
const EdgeInsets.fromLTRB(4, 4, 4, 5),
double? maxContentWidth,
}) : _document = document,
_hasFocus = hasFocus,
_selection = selection,
_extendSelectionOrigin = selection,
_startHandleLayerLink = startHandleLayerLink,
_endHandleLayerLink = endHandleLayerLink,
_cursorController = cursorController,
_maxContentWidth = maxContentWidth,
super(
children: children,
node: document.root,
Expand Down Expand Up @@ -236,6 +238,13 @@ class RenderEditor extends RenderEditableContainerBox
markNeedsPaint();
}

double? _maxContentWidth;
set maxContentWidth(double? value) {
if (_maxContentWidth == value) return;
_maxContentWidth = value;
markNeedsLayout();
}

final CursorController _cursorController;

/// Track whether position of the start of the selected text is within the viewport.
Expand Down Expand Up @@ -610,6 +619,61 @@ class RenderEditor extends RenderEditableContainerBox

// Start RenderBox implementation

@override
void performLayout() {
assert(() {
if (!constraints.hasBoundedHeight) return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'RenderEditableContainerBox must have unlimited space along its main axis.'),
ErrorDescription(
'RenderEditableContainerBox does not clip or resize its children, so it must be '
'placed in a parent that does not constrain the main '
'axis.'),
ErrorHint(
'You probably want to put the RenderEditableContainerBox inside a '
'RenderViewport with a matching main axis.')
]);
}());
assert(() {
if (constraints.hasBoundedWidth) return true;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary(
'RenderEditableContainerBox must have a bounded constraint for its cross axis.'),
ErrorDescription(
'RenderEditableContainerBox forces its children to expand to fit the RenderEditableContainerBox\'s container, '
'so it must be placed in a parent that constrains the cross '
'axis to a finite dimension.'),
]);
}());

resolvePadding();
assert(resolvedPadding != null);

var mainAxisExtent = resolvedPadding!.top;
var child = firstChild;
final innerConstraints = BoxConstraints.tightFor(
width: math.min(
_maxContentWidth ?? double.infinity, constraints.maxWidth))
.deflate(resolvedPadding!);
final leftOffset = _maxContentWidth == null
? 0.0
: math.max((constraints.maxWidth - _maxContentWidth!) / 2, 0);
while (child != null) {
child.layout(innerConstraints, parentUsesSize: true);
final childParentData = child.parentData as EditableContainerParentData;
childParentData.offset =
Offset(resolvedPadding!.left + leftOffset, mainAxisExtent);
mainAxisExtent += child.size.height;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
mainAxisExtent += resolvedPadding!.bottom;
size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));

assert(size.isFinite);
}

@override
void paint(PaintingContext context, Offset offset) {
if (hasFocus &&
Expand Down
23 changes: 23 additions & 0 deletions packages/zefyr/lib/src/widgets/editor.dart
Expand Up @@ -134,6 +134,13 @@ class ZefyrEditor extends StatefulWidget {
/// set to `false`.
final double? maxHeight;

/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;

/// Whether this editor's height will be sized to fill its parent.
///
/// This only has effect if [scrollable] is set to `true`.
Expand Down Expand Up @@ -208,6 +215,7 @@ class ZefyrEditor extends StatefulWidget {
this.enableInteractiveSelection = true,
this.minHeight,
this.maxHeight,
this.maxContentWidth,
this.expands = false,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardAppearance = Brightness.light,
Expand Down Expand Up @@ -310,6 +318,7 @@ class _ZefyrEditorState extends State<ZefyrEditor>
enableInteractiveSelection: widget.enableInteractiveSelection,
minHeight: widget.minHeight,
maxHeight: widget.maxHeight,
maxContentWidth: widget.maxContentWidth,
expands: widget.expands,
textCapitalization: widget.textCapitalization,
keyboardAppearance: widget.keyboardAppearance,
Expand Down Expand Up @@ -476,6 +485,7 @@ class RawEditor extends StatefulWidget {
this.enableInteractiveSelection = true,
this.minHeight,
this.maxHeight,
this.maxContentWidth,
this.expands = false,
this.textCapitalization = TextCapitalization.none,
this.keyboardAppearance = Brightness.light,
Expand Down Expand Up @@ -584,6 +594,13 @@ class RawEditor extends StatefulWidget {
/// The minimum height this editor can have.
final double? minHeight;

/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;

/// Whether this widget's height will be sized to fill its parent.
///
/// If set to true and wrapped in a parent widget like [Expanded] or
Expand Down Expand Up @@ -1210,6 +1227,7 @@ class RawEditorState extends EditorState
endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged,
padding: widget.padding,
maxContentWidth: widget.maxContentWidth,
children: _buildChildren(context),
),
),
Expand Down Expand Up @@ -1243,6 +1261,7 @@ class RawEditorState extends EditorState
endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged,
padding: widget.padding,
maxContentWidth: widget.maxContentWidth,
cursorController: _cursorController,
children: _buildChildren(context),
),
Expand Down Expand Up @@ -1365,6 +1384,7 @@ class _Editor extends MultiChildRenderObjectWidget {
required this.onSelectionChanged,
required this.cursorController,
this.padding = EdgeInsets.zero,
this.maxContentWidth,
}) : super(key: key, children: children);

final ViewportOffset? offset;
Expand All @@ -1376,6 +1396,7 @@ class _Editor extends MultiChildRenderObjectWidget {
final LayerLink endHandleLayerLink;
final TextSelectionChangedHandler onSelectionChanged;
final EdgeInsetsGeometry padding;
final double? maxContentWidth;
final CursorController cursorController;

@override
Expand All @@ -1391,6 +1412,7 @@ class _Editor extends MultiChildRenderObjectWidget {
onSelectionChanged: onSelectionChanged,
cursorController: cursorController,
padding: padding,
maxContentWidth: maxContentWidth,
);
}

Expand All @@ -1407,6 +1429,7 @@ class _Editor extends MultiChildRenderObjectWidget {
renderObject.endHandleLayerLink = endHandleLayerLink;
renderObject.onSelectionChanged = onSelectionChanged;
renderObject.padding = padding;
renderObject.maxContentWidth = maxContentWidth;
}

@override
Expand Down
2 changes: 1 addition & 1 deletion packages/zefyr/pubspec.yaml
@@ -1,6 +1,6 @@
name: zefyr
description: Clean, minimalistic and collaboration-ready rich text editor for Flutter.
version: 1.0.0-rc.3
version: 1.0.0-rc.4
homepage: https://github.com/memspace/zefyr

environment:
Expand Down

0 comments on commit c5606a2

Please sign in to comment.