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
Push SliverToBoxAdapter down to SingleColumnDocumentLayout #1916
base: main
Are you sure you want to change the base?
Conversation
@@ -175,7 +176,7 @@ void main() { | |||
DocumentSelection( | |||
base: DocumentPosition( | |||
nodeId: lastParagraph.id, | |||
nodePosition: lastParagraph.endPosition.copyWith(affinity: TextAffinity.upstream), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the affinity: TextAffinity.upstream
was actually incorrect, and was just an artifact of AutoScroller.deltaWhileAutoScrolling
being one frame late, giving slightly wrong base position after scrolling.
I haven't looked at the source code yet, but I have some thoughts based on the write-up. Mostly, I want to make sure that we don't over correct in the direction of slivers, to the detriment of those who would like to operate on boxes.
Unless I'm misunderstanding, this seems like a problem for those who want to work with boxes and not slivers. Boxes make it trivial to calculate offsets and assemble visual rectangle regions, so my first thought is that we don't want to eliminate the ability to treat a document as a box, if the user is actually using it as a box.
I've long planned to move |
Slivers are a superset of boxes. Whatever you want to do with boxes you can do with slivers.
We can expose the underlying box from the document if needed. But you can calculate those from slivers too if needed.
This would make things much more complicated. Basically to be able to use sliver as layout, every render object produced by super editor all the way down to the layout must be a RenderSliver. If you want to use both boxes and slivers throughout to super editor you'll need to duplicate all the render objects and then pass along a flag to know which one to produce. |
Let's dig into this claim a bit. Certainly, from an API standpoint, this isn't the case. https://api.flutter.dev/flutter/rendering/RenderObject-class.html https://api.flutter.dev/flutter/rendering/RenderBox-class.html https://api.flutter.dev/flutter/rendering/RenderSliver-class.html Looking beyond the inheritance differences, let's look at a few methods and properties. It's very common to want to call
We've already talked a little bit about intrinsic width and height. While it may be acceptable for a specific chosen I have some concerns in other areas as well, but let's start by aligning on what the relationship really looks like between slivers and boxes. I want to make sure we have a crystal clear picture for everyone involved as to what it means to eliminate the guarantee of boxes and force people to either work with slivers, or force people to work only with generic |
@matthew-carroll, please look at render_object_ext inside the PR. It implements |
The alternative for intrinsic height is |
But |
Yes. ShrinkWrap will cause the scrollview to size itself to content, but does not allow querying intrinsic size. The question is, whether this difference is significant enough in practice to warrant supporting both slivers and boxes through-out super editor, which would increase the complexity significantly and add pretty big surface for runtime errors in cases where sliver and boxes would be mixed by mistake. |
I agree that's the final question. I'm trying to start by putting all the tradeoffs on the table. I'll take a look at your implementation of box stuff in the sliver extensions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did an initial pass of the PR. Overall I'd say it's a good sign that relatively little code was needed to migrate SuperEditor
and SuperReader
behavior to a sliver world.
I do have concerns about some places where this migration has narrowed the composability and/or features of existing artifacts, including ContentLayers
and the SingleColumnDocumentLayout
. I'm also concerned about the RenderObject
extension that bridges between RenderSliver
and RenderBox
.
super_editor/lib/src/default_editor/document_gestures_touch_ios.dart
Outdated
Show resolved
Hide resolved
controller: _overlayController, | ||
overlayChildBuilder: _buildOverlay, | ||
child: widget.child ?? const SizedBox(), | ||
return SliverHybridStack( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To help understand UX tradeoffs, I'm going to note the places like this where a SliverHybridStack
apparently is required, but is probably a surprise to the developer. I'm sure the explanation for why this is needed here is very simple, but even reading this now, I can't immediately see why slivers are relevant to this little subtree.
Related to that concern is a concern is about composition. If the reason that this subtree is wrapped in a SliverHybridStack
is because it happens to be a top-level child within SuperEditor
, then that means we're installing global knowledge about where a given widget sits within the SuperEditor
tree. There are the widgets which are immediate children of the scrolling SuperEditor
system, and then there are all other widgets. Discriminating between those positions in the tree makes it more difficult to support generic widget composition within SuperEditor
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that SuperEditor insist on bringing it own scrolling infrastructure is the surprising part to me. This seems like a peculiar decision and results in a rather magical behavior where SuperEditor builds differently when placed into a scrollable or not.
If SuperEditor did not bring it's own scrollable, then conceptually every layer of it would need to be sliver, which would basically mean replacing all Stack instances with SliverHybridStack and making ContentLayers a sliver.
However because super editor does bring it's own scrollable sometimes, the PR made some adjustment to ensure that the scrollable is at the top level, and all layers are within the scrollable. There are no layers of SuperEditor anymore on top of scrollable.
So the only difference is that when not put into a scrollable, SuperEditor wraps itself in a CustomScrollView. All the other layers remain same regardless of whether put in a scrollable or not. This also means that each layer of SuperEditor is a sliver now. I'm hoping that explains all the "Surprise" sliver references. Yes, this PR turns SuperEditor into a sliver, which means every layer of it down to the layout is a Sliver.
Turning SuperEditor into a sliver does make things slightly more complicated, but I don't see what other way there is to achieve acceptable performance for medium to large documents.
controller: _overlayPortalController, | ||
overlayChildBuilder: _buildToolbar, | ||
child: widget.child ?? const SizedBox(), | ||
return SliverHybridStack( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprise sliver reference.
controller: _overlayPortalController, | ||
overlayChildBuilder: _buildMagnifier, | ||
child: widget.child ?? const SizedBox(), | ||
return SliverHybridStack( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprise sliver reference.
super_editor/lib/src/default_editor/document_gestures_touch_ios.dart
Outdated
Show resolved
Hide resolved
minWidth: constraints.crossAxisExtent, | ||
maxWidth: constraints.crossAxisExtent, | ||
minHeight: sliverLayoutGeometry.scrollExtent, | ||
maxHeight: sliverLayoutGeometry.scrollExtent, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any implications with this definition of constraints in the case that SuperEditor
is used with an ancestor scrollable, in which there's additional scrollable content above and/or below the editor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None I can think of.
@@ -687,19 +676,44 @@ class RenderContentLayers extends RenderBox { | |||
return; | |||
} | |||
|
|||
void paintChild(RenderObject child) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you do this without defining a method inside of a method?
} | ||
} | ||
|
||
@override | ||
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given how convoluted render objects are, can you please document each of the standard behaviors that you've added and adjusted in this PR? I'd like to make sure that every Super Editor contributor is able to understand the intended behavior of these overrides.
child: Text("Change Size"), | ||
), | ||
const Spacer(), | ||
child: CustomScrollView( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was a CustomScrollView
added here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Becuase ContentLayers is a Sliver and as such it needs to be put in a CustomScrollView.
126a213
to
42167d2
Compare
I tried running Superlist with this branch, and apart from making sure there are no render boxes outside of editor (which is a given) the only change I needed was to not make assumption about the layout object render type: That meant changing code that positions overlay from final docBox =
_documentLayoutKey.currentContext?.findRenderObject() as RenderBox?;
if (docBox == null) return;
final position = Rect.fromPoints(
docBox.localToGlobal(selectionBoundingBox.topLeft),
docBox.localToGlobal(selectionBoundingBox.bottomRight),
); Into final layout = _documentLayoutKey.currentState as DocumentLayout;
final position = Rect.fromPoints(
layout.getGlobalOffsetFromDocumentOffset(selectionBoundingBox.topLeft),
layout.getGlobalOffsetFromDocumentOffset(selectionBoundingBox.bottomRight),
); |
@knopp I think the most reasonable path forward is to just land this and see how it shakes out. If we need to bring more box stuff back in, we can. Can you address the few minor comments that were originally made? |
@knopp ping on this |
Apologies for the delay. Right now trying to dogfooding this in superlist to figure out if there are some loose ends that need to be addressed in this PR. |
Background: The way SuperEditor currently handles document widgets where all the widgets in the document are instantiated at all times is presenting a significant bottleneck to us. We aim to implement an alternative document layout based on super_sliver_list, but in order to accommodate a sliver inside layout some adjustments needs to be made to
SuperEditor
infrastructure.This PR ensures that when
SuperEditor
is placed into aCustomScrollView
, it propagates the sliver geometry all the way down to the layout, instead of relying to be wrapped inside aSliverToBoxAdapter
.Breaking use-facing changes introduced in this PR:
SuperEditor
inside a custom scroll view, it must no longer be wrapped in aSliverToBoxAdapter
. That's because theSuperEditor
itself acts like a sliver. When SuperEditor is used as "standalone" widget, no changes are necessary.IntrinsicHeight
around SuperEditor, as this does not work withCustomScrollView
, which is now used instead ofSingleChildScrollView
inDocumentScrollable
. Instead of that,shrinkWrap
argument has been added toSuperEditor
andSuperReader
to accomplish similar behavior.RenderBox
will fail, because the layout is aRenderSliver
.Some implementation details:
SliverHybridStack
widget was introduced to allow combining slivers and render boxes. Exactly on of the children in the stack must produce aRenderSliver
, while other children produceRenderObject
s. The render objects are laid-out and painted in a way where they cover the entire sliver scrollExtent.ContentLayers
has been modified to work similar toSliverHybridStack
, where the content is a sliver while the overlays and underlays are render boxes. I think this is acceptable solution as the underlays and overlays are expected to be much sparsely populated then the content itself.ViewportBoundsReporter
andRenderViewportBoundsReplicator
are gone. The viewport bounds are part of sliver constraints.SliverHybridStack
has an optional mode which ensures that the renderboxes fill up entire viewport, which is enabled for gesture system RenderBox in case the entire works in standalone mode (not in user provided scrollable).