-
-
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
Reworked markers #1659
Reworked markers #1659
Conversation
f35d70e
to
bc9d0f1
Compare
I'm up for the renaming to I'm also up for most of the simplification, but not necessarily the split of Is there a way to make this less breaking but also simplified? |
What do you mean by "the anchor could be opposite to the alignment"?
No functionality should have been removed. Let me try to expand: Marker has for the longest time received an Rather than multiplexing on the AnchorPos level, I changed it to two different constructors, which allows the center-aligned and absolute Anchor paths to remain const constructed and gets rid of the pseudo tagged union. In other words: previously it was AnchorPos that had two constructors now it's Marker 🤷♀️ |
Well, if the anchor is at the bottom, the item is 'aligned' to the top of it's box, IYSWIM. Ok, I get the point of two seperate constructors to enable |
I see now. I guess, I've only ever used anchors. Maybe MarkerAlignment then? Open to any name
I'm not sure either. Take this as an RFC. As a anchor user, I was a bit surprised by this extra indirection and pseudo tagged union. Anyway, as for alternatives:
Marker(
width: 30,
height: 30,
anchor: Anchor.fromAlignment(topLeft, 30, 30),
/*...*/
) |
Maybe some opinions from the other guys would be helpful :)
We just need to be careful here. Currently, I think only 'anchor' makes sense - we'd need to invert the behaviour if we start calling it 'alignment'. |
Sorry for the late comment, I agree with @JaffaKetchup here, "anchor" in my opinion makes more sense and I see it as different from an "alignment" property (and it feels weird to have a constructor Other than the name I'm ok with the other changes you've brought. |
Thanks for chiming in. I'm not very partial to any names and happy to adopt any suggestion, I'm just a bit lost in the conversation. Currently we have the following names (starting from Marker):
which names would you like to have changed? My original suggestion was to rename AnchorAlignment so that Marker.align would receive a correspondingly named MarkerAlignment instead 🤷♀️ . Just let me know and I'll change it in a heartbeat. Thanks |
Thanks for the quick reply! I was referring to |
Sorry, I'm a bit slow today. Putting your two responses together:
Are you saying that you're happy with the current state of naming with "AnchorPos" out of the picture in favor of AnchorAlignment? |
Don't worry 😆
No, I was actually saying that I prefer the old |
…const Marker construction in the common case. This is an API backwards-incompatible change.
I totally nailed that 50:50 👼 . I did replace all the "Alignment" with "Pos/position". PTAL |
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.
LGTM 👍
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.
Sorry to tread on an approval, but I think we've missed something major here. There's now very little point to having AnchorPos
at all. It's just a container for an alignment and text direction. If we're happy to forfeit the text direction - which I would be happy to, considering that this doesn't handle text and the position of an anchor shouldn't be affected by text direction - then we can remove AnchorPos
entirely.
I'm also still not totally satisfied with the naming. position
isn't descriptive enough for me, given that it has exactly the same issues as alignment
- nothing is identifying what is in relation to what.
Take a look at the image below:
Look at the marker just below London. I would say that its anchor position is London, and therefore top-center relative to the marker. I would say that the marker position is bottom-center relative to the anchor. The buttons at the top left change the anchor position, previously called anchorPos
, currently called position
. However, they are not actually changing the anchor position - they are changing the marker position. We cannot mix and match position
and AnchorPos
and alignment
without qualifying what they are all in relation to. For example, this PR treats anchor
and position
as equivalent, even though they would appear to refer to completely different things.
However, I'm assuming what anchor means here. I'm assuming that it means center of rotation, which makes sense in my head. But, according to the documentation which doesn't make sense, it means something different: the position of the widget in relation to the normal center. And of course, I haven't at all factored in rotateAlignment
or rotateOrigin
- I'm not even sure what the point in those is.
TLDR: the naming still isn't right, and everything is still self-contradictory. This includes the current code base.
@TesteurManiak @ignatz What do you think about this? Things can be added back as necessary, but this is as clean as possible, for a starting base:
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:flutter_map/src/misc/private/bounds.dart';
import 'package:latlong2/latlong.dart';
/// Represents a coordinate point on the map with an attached widget [builder],
/// rendered by [MarkerLayer]
///
/// Some properties defaults will absorb the values from the parent [MarkerLayer],
/// if the reflected properties are defined there.
@immutable
class Marker {
final Key? key;
/// Coordinates of the marker
///
/// This will be the center of the marker, assuming that [alignment] is
/// [Alignment.center] (default).
final LatLng point;
/// Returns the marker widget itself
final WidgetBuilder builder;
/// Bounding box width of the marker
final double width;
/// Bounding box height of the marker
final double height;
/// Alignment of the marker relative to the normal center at [point]
///
/// For example, [Alignment.topCenter] will mean the entire marker widget is
/// located above the [point].
///
/// The center of rotation will be opposite this.
///
/// Defaults to [Alignment.center] if also unset by [MarkerLayer].
final Alignment? alignment;
/// Whether to counter rotate markers to the map's rotation, to keep a fixed
/// orientation
///
/// When `true`, markers will always appear upright and vertical from the
/// user's perspective. Defaults to `false` if also unset by [MarkerLayer].
///
/// Note that this is not used to apply a custom rotation in degrees to the
/// marker. Use a widget inside [builder] to perform this.
final bool? rotate;
const Marker({
this.key,
required this.point,
required this.builder,
this.width = 30,
this.height = 30,
this.alignment,
this.rotate,
});
}
@immutable
class MarkerLayer extends StatelessWidget {
final List<Marker> markers;
final Alignment alignment;
final bool rotate;
const MarkerLayer({
super.key,
this.markers = const [],
this.alignment = Alignment.center,
this.rotate = false,
});
@override
Widget build(BuildContext context) {
final map = MapCamera.of(context);
return Stack(
children: markers
.map((m) {
// Resolve real alignment
final left = 0.5 * m.width * ((m.alignment ?? alignment).x + 1);
final top = 0.5 * m.height * ((m.alignment ?? alignment).y + 1);
final right = m.width - left;
final bottom = m.height - top;
// Perform projection
final pxPoint = map.project(m.point);
// Cull if out of bounds
if (!map.pixelBounds.containsPartialBounds(
Bounds(
Point(pxPoint.x + left, pxPoint.y - bottom),
Point(pxPoint.x - right, pxPoint.y + top),
),
)) return null;
// Apply map camera to marker position
final pos = pxPoint.subtract(map.pixelOrigin);
return Positioned(
key: m.key,
width: m.width,
height: m.height,
left: pos.x - right,
top: pos.y - bottom,
child: (m.rotate ?? rotate)
? Transform.rotate(
angle: -map.rotationRad,
alignment: (m.alignment ?? alignment) * -1,
child: m.builder(context),
)
: m.builder(context),
);
})
.whereNotNull()
.toList(),
);
}
} |
I agree with this assessment. The underlying issues, i.e. two opposing ways, of achieving the same predates my change. Plus:
Thanks for taking the time to make a counter proposal. I like that it solves the issue of having two ways of achieving the same goal. I also like that it removes the arbitrary restriction of only supporting cardinal alignments, i.e. alignment is now just as flexible as anchor just with relative rather than absolute offsets, which likely is also easier to use. Arguably what aligns to what is still somewhat arbitrary but the docstring makes that very clear. I'd even remove the point on center of rotation. As you said as well, it's not clear to me why you'd ever rotate around anything other than the anchor point 🤷♀️ . Really nothing to critique. Ship it. |
I added it to properly support resolution of One thing that we perhaps forgot is that once a This proposal also means that there is no behaviour change, even though it looks like there should be. This might be a little difficult to explain concisely. I won't add deprecations, as it would require keeping |
It's not clear to me why Marker having a builder function would make Markers impossible to cons-construct. Clearly we did it before. Widget build(BuildContext context) {
final marker = ColoredBox(color: Colors.pink);
return MarkerLayer(
markers: [
Marker(builder: (_) => marker),
],
}
} I'd much prefer: Widget build(BuildContext context) {
return MarkerLayer(
markers: [
Marker(child: ColoredBox(color: Colors.pink)),
// Or if you depend on the build context
Marker(child: Builder(builder: (BuildContext context) => foo(context))),
],
}
} |
I don't think we did. The definition might have been
I'm also not sure why it takes a |
Also, I'm not sure there's a difference by calculating the |
It doesn't need to be const. For the tree-diffing to work effectively w/o providing an explicit key, the widget object id needs to remain stable, in which case it will simply skip calling the Widget's build again. What happened to: "Let's have one way of doing things"? 🥁 🙃 We're already in the business of breaking API compatibility and we'd just be nudging it closer to the Flutter convention, i.e. just receive a child.
Maybe we're talking about different things but Marker used to have a const constructor and my change in its current form restores Marker to have a const constructor.
These are efficient math ops, so we're talking ~3 orders of magnitude less, i.e. nanoseconds. That's an optimization you really won't notice. Especially if you compare it to the still required projection/trigonometry operations. I did a quick micro-benchmark (never trust a micro-benchmark), which showed that const vs non-const construction of an otherwise virtually identical object makes virtually no difference. However, as soon as you add a constructor body it slows 10x. |
You're quite right, keeping me in check :D. One way = best way.
We're talking about similar but different things. USAGE of the Anyway, doesn't matter now!
Yep, that's the conclusion I reached as well. |
Significantly outdated
Welp, all of those changes have been made. Quite the big change, but all for the best I think! |
I love it |
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.
LGTM with feedback from @ignatz. Will try to wait for a second approval as a lot of the code is mine.
And thanks for jumping in with a much better counter proposal 🙏 |
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 came up with the same conclusions as @ignatz, I've compared the current implementation with this one and there seem to be no impact in performances despite the const
constructor but had an impact when the builder
property had a constructor body. (tested with Flutter Web on Chrome with canvaskit enabled)
Otherwise LGTM 👍
So, the question now is: what is the best way to upgrade markers from fixed anchors to alignment? Right now I did this with magic contants (like, |
Update: for Maybe we should make a constructor for anchor position that translates into the default constructor? |
That sounds sensible, I've created #1771 as a feature request. |
Thanks JK, and sorry, I should've made the ticket myself instead of ranting :) |
Hello I'm curious. If anchorPos is completely removed. How then am I supposed to modify my code with this parameter. MarkerClusterLayerWidget(
options: MarkerClusterLayerOptions(
// Function to compute cluster marker size based on the number of markers
computeSize: (markers) {
var count = markers.length;
var scaleFactor =
(log(count + 1) / log(widget.maxClusterSize + 1));
var size = widget.minClusterSize +
scaleFactor *
(widget.maxClusterSize - widget.minClusterSize);
return Size(size, size);
},
maxClusterRadius: widget.maxClusterRadius,
fitBoundsOptions: FitBoundsOptions(
padding: EdgeInsets.all(50),
),
markers: convertedPlacesWithRefs
.map((pair) => Marker(
width: (widget.markerIcon as Icon).size! + 8,
height: (widget.markerIcon as Icon).size! + 8,
**anchorPos: AnchorPos.align(AnchorAlign**
.center), // marker: Marker.align(MarkerAlign.center) this changed prior to Flutter 3.19.1 so we may need to maintain the flutter_map and flutter_map_marker_cluster versions before the flutter version upgrade. Or just change/remove the parameter to the current parameters.
point: pair.coordinates,
builder: (ctx) => GestureDetector(
onTap: () {
FFAppState().selectedPlace = pair.documentRef;
if (widget.callBackAction != null) {
widget.callBackAction!();
}
}, The main code highlighted is anchorPos: AnchorPos.align(AnchorAlign.center) |
Hi @Anyaoha, |
@ignatz:
None of the individual changes is very critical. I'm not sure they're worth the API-breaking change. Maybe simplification + increase consistency can justify it. I can be swayed either way. Let me know what you think.@JaffaKetchup:
builder
s: now we useMarker.child
for extra efficiencyMarker
's constructor is nowconst
ant, along withMarkerLayer
rotateOrigin
androtateAlignment
have been removed, as not sure of their valid use-caseAnchor
,AnchorPos
, and all anchor terminology have been removed, to simplifyAlignment
s now supported, but now also non pre-provided onesQuite the breaking change!