-
-
Notifications
You must be signed in to change notification settings - Fork 860
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
[FEATURE] Implement better method to organize the position of layers #1564
Comments
I've been focusing on the refactors recently so I haven't had a change to respond until now. For me the main issue with One way we could improve this would be to figure out a way to throw an exception to warn the user when |
Sure, one thing at a time. Just trying to plan ahead a little - and I'm happy to implement this one - because I've got 10 weeks of holiday (perks of finishing major exams), so would like to aim for a release by the end of that at the latest. Ok, I can kinda see what you mean about this approach being potentially confusing to users. As you've probably realized 😉 I'm a fan of documentation and think behaviour like that can be dealt with that way. However, it would be nice to do what you suggested and throw an error/warning if the widget is used incorrectly. That should certainly prevent people from asking questions which have answers already in the docs, as the error and resolution would be right there for them: less questions for us to answer :). What if we used something like How does that sound to you? Not sure if there are any other ways to check the parent widget. |
Just stumbled across this and wanted to add my two cents. I feel like the whole confusion/problem just arise by the name |
@Robbendebiene I can't imagine why, but some people may want to put a standard child above a non-rotated child, which is what this is trying to allow for, in which case, But you have made a good point in that I can't really think why anyone would need this. @rorystephenson Did you have a use case? |
One could argue that this is a very special use case that shouldn't be handled by the package itself and instead be moved to a plugin (the problem probably is that a plugin currently doesn't have the power to "unrotate" itself?). Still for a developer it might be possible to get the desired result by creating multiple
I have to admit that's how I like to use it (putting a The only reason for me as a developer to not just simply wrap a |
Yeah, there's no good way for a plugin to un-rotate. Theoretically, it could apply an inverse rotation transformation to itself, but I'm not sure whether Flutter is smart enough to optimise that, and without optimization, that may be not very performant.
We have thought about this before, but it turns out it is very useful to allow something like a
Yep, that is it's intended use case. These things need access to map state or a controller, so it makes sense to not need a controller, and just allow it to use state directly. |
I've found a way to detect whether it is a top level widget. It requires at most two iterations of map/non_rotated_layer.dart (new)/// Provide a detection point for [NonRotatedLayerMixin]
///
/// Although any other internal-only widget could be used as the detection point,
/// this is provided to reduce the number of iterations
/// `context.visitAncestorElements` requires to ascertain whether
/// [NonRotatedLayerMixin] is being used correctly.
class _NRLDetectorAncestor extends StatelessWidget {
const _NRLDetectorAncestor({required this.child});
final Widget child;
@override
Widget build(BuildContext context) => child;
}
/// Prevent a widget's rotation with the map, when built within the standard
/// [FlutterMap.children] list instead of the [FlutterMap.nonRotatedChildren]
/// list (where this is not required)
///
/// The widget mixing this in must always be a top level widget in
/// [FlutterMap.children], ie. it must not be a child of another widget. Failure
/// to do this will throw an error, as the behaviour will not be correct.
///
/// Always call `super.build(context)` from within the widget's `build` method.
/// Ignore its result, and build children as normal.
///
/// Prefer using [NonRotatedLayer], which mixes this in automatically.
mixin NonRotatedLayerMixin on StatelessWidget {
@override
@mustCallSuper
Widget build(BuildContext context) {
// `context.visitAncestorElements` can be expensive, so set a maximum number
// of iterations
int i = 0;
context.visitAncestorElements((e) {
i++;
if (i != 2 || e.widget is _NRLDetectorAncestor) return true;
throw FlutterError(
'The widget with `NonRotatedLayerMixin` (or `NonRotatedLayer`) must only '
'be used as a top level widget in `FlutterMap.children`\n'
'Failure to do so will mean that the layer still rotates as normal.\n'
'To resolve this, and restore functionality, swap the parent\'s of this '
'widget with its `child`.\n',
);
});
// The user shouldn't build the output of this method
return Builder(
builder: (_) => throw FlutterError(
'Widgets that mixin `NonRotatedLayerMixin` must call '
'`super.build(context)`, but must also ignore the return value',
),
);
}
}
/// Prevent a widget's rotation with the map, when built within the standard
/// [FlutterMap.children] list instead of the [FlutterMap.nonRotatedChildren]
/// list (where this is not required)
///
/// This widget must always be a top level widget in [FlutterMap.children], ie.
/// it must not be a child of another widget. Failure to do this will throw an
/// error, as the behaviour will not be correct.
class NonRotatedLayer extends StatelessWidget with NonRotatedLayerMixin {
const NonRotatedLayer({
super.key,
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
super.build(context);
return child;
}
} map/state.dart Iterable<Widget> _prepareChildren(List<Widget> children) sync* {
final stackChildren = <Widget>[];
Widget prepareRotateStack() {
final box = OverflowBox(
minWidth: size.x,
maxWidth: size.x,
minHeight: size.y,
maxHeight: size.y,
child: Transform.rotate(
angle: rotationRad,
child: Stack(children: List.from(stackChildren)),
),
);
stackChildren.clear();
return box;
}
for (final Widget child in children) {
if (child is NonRotatedLayerMixin) {
if (stackChildren.isNotEmpty) yield prepareRotateStack();
yield child;
} else {
stackChildren.add(child);
}
}
if (stackChildren.isNotEmpty) yield prepareRotateStack();
}
// To build the children
RawGestureDetector(
gestures: gestures,
child: ClipRect(
child: _NRLDetectorAncestor(
child: Stack(
children: [
..._prepareChildren(widget.children),
...widget.nonRotatedChildren,
],
),
),
),
), |
Congrats on finishing your exams @JaffaKetchup , hope they went well! Both of your comments made me revisit why I proposed a change in the first place and there are two main reasons:
I think you're probably right that if such a use case exists it is probably, as @Robbendebiene said, a very special use case. So if we consider that unsupported behaviour then renaming to overlaidChildren (or just This resolves both of my initial motivations for making this change and, if anyone ever provides a good example of why interleaving rotated/non-rotated widgets is necessary I think we can reconsider this. Interesting find on visitAncestorElements @JaffaKetchup , I wasn't aware of that. |
Thanks, I'll have to wait and see about that ;)
I would say that both are affected by the parent. Just one is affected more than the other - the non-rotated children can still access and control the parent's state as necessary. Plus, flutter_map is kind of an unusual pattern throughout :D.
I doubt most people will even think about the positioning of the layers - For now, this can sit here, and I'll lower the priority. If anyone suddenly needs this functionality, the code is pretty much good to go, and can be added relatively quickly. |
Just a note, it may have been me that raised a similar issue historically, but iirc this was for when we had both sets of layers as well (so children, nonrotatedchildren, layers and nonrotatedlayers I think), and that made it extra confusing because order mattered even more there. Haven't been following this too closely, apologies. |
I think what you're describing is exactly the same as the |
@rorystephenson While it's not common there are widgets that have multiple child properties like Scaffold, AppBar, AlertDialog and others. So I would say it depends if one would expect this functionality/property from a Widget or not.
@JaffaKetchup I would vote for a deprecation of the Edit: I know this feels like a step backwards, but maybe renaming |
Sorry for being late to the party. Fundamentally, I'd think about the distribution of responsibilities. Specifically, who decides whether a layer is functionally rotating or not? It's not the user, it's always the layer itself. In other words, I can't think of a single case where a layer would meaningfully work both in a rotating and non-rotating mode and the user can choose which box it should go to. Giving the user two properties/buckets in FlutterMap gives them at best the illusion of choice, so you might as well take it away. I liked the idea of tagging layer implementations (whether that's done through mixins, traits, or carrier pidgins). A single bucket with a defined ordering will always be easier than two buckets. (and anything is better than leaflet where some layers are being reordered by the framework) |
Originally discussed in #1553, converted to issue to allow tracking in v6 Release Planning.
In flutter_map, all
nonRotatedChildren
always appear on top of allchildren
. This can cause issues for some users, who need to be able to control exactly where each layer is positioned within the layer stack, and also need to control whether the layer rotates.As such, it may be desirable in the long term to find a solution to this issue. Three proposals discussed somewhat in #1551 (#1551 (comment), #1551 (comment), #1551 (comment), #1551 (comment)) are listed below, but all FM users are welcome to add their ideas or opinions!
The
builder
proposalAn initial suggestion by @rorystephenson, started in #1551, was to introduce (or potentially replace the current
children
/nonRotatedChildren
arguments with) abuilder
method. The user would create aStack
widget themselves, using a new widgetFlutterMapTransform.rotate
to enable rotation on specific layers.I (@JaffaKetchup) think that having the user handle the stack and opt-in to rotation on layers:
Stack
is required, or anotherFlutterMapTransform
wrapper is requiredFlutterMap
or a related widgetrequires migration across all users(a later proposal made the builder optional via an alternative constructor)The wrapper widget and mixin proposal
I (@JaffaKetchup) proposed an alternative that aimed to solve most of these. Create a widget
NonRotatedLayer
that did nothing more than direct it's builder to build thechild
argument, meaning it essentially acts like a tag.nonRotatedChildren
still appear above all otherchildren
, and don't require an explicitNonRotatedLayer
, howeverchildren
can use aNonRotatedLayer
just to force non-rotation to its direct descendant, as FM internals would recognise the tag.To avoid redundant wrapping, this could (also) be implemented via a mixin (as proposed by @rorystephenson), and be allowed to be applied to all widgets. This would allow maximum flexibility.
However, as pointed out by @rorystephenson, this will not work if the layer is not directly below the
children
. If it is a descendant of, lets say aFutureBuilder
, the effect will not apply.I'm (@JaffaKetchup) not sure this is an issue, as either method does not restrict it's
child
type, so the order of theFutureBuilder
and mixin/NonRotatedLayer
could be swapped to restore functionality.The text was updated successfully, but these errors were encountered: