Skip to content

Commit

Permalink
Minor: simplify marker alignment handling, fix naming, and allow for …
Browse files Browse the repository at this point in the history
…const Marker construction in the common case.

This is an API backwards-incompatible change.
  • Loading branch information
ignatz committed Sep 21, 2023
1 parent d35d672 commit f35d70e
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 73 deletions.
12 changes: 6 additions & 6 deletions example/lib/pages/markers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ class MarkerPageState extends State<MarkerPage> {
),
MarkerLayer(
rotate: counterRotate,
anchorPos: AnchorPos.defaultAnchorPos,
alignment: AnchorAlignment.defaultAnchorAlignment,
markers: [
Marker(
Marker.withAlignment(
point:
const LatLng(47.18664724067855, -1.5436768515939427),
width: 64,
height: 64,
anchorPos: const AnchorPos.align(Alignment.centerLeft),
alignment: const AnchorAlignment(Alignment.centerLeft),
builder: (context) => const ColoredBox(
color: Colors.lightBlue,
child: Align(
Expand All @@ -136,12 +136,12 @@ class MarkerPageState extends State<MarkerPage> {
),
),
),
Marker(
Marker.withAlignment(
point:
const LatLng(47.18664724067855, -1.5436768515939427),
width: 64,
height: 64,
anchorPos: const AnchorPos.align(Alignment.centerRight),
alignment: const AnchorAlignment(Alignment.centerRight),
builder: (context) => const ColoredBox(
color: Colors.pink,
child: Align(
Expand All @@ -162,7 +162,7 @@ class MarkerPageState extends State<MarkerPage> {
MarkerLayer(
markers: customMarkers,
rotate: counterRotate,
anchorPos: AnchorPos.align(anchorAlign),
alignment: AnchorAlignment(anchorAlign),
),
],
),
Expand Down
133 changes: 66 additions & 67 deletions lib/src/layer/marker_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,47 @@ import 'package:latlong2/latlong.dart';
/// Defines the positioning of a [Marker.builder] widget relative to the center
/// of its bounding box
///
/// Can be defined exactly (using [AnchorPos.exactly] with an [Anchor]) or in
/// a relative/dynamic alignment (using [AnchorPos.align] with an [Alignment]).
/// Can be defined exactly (using [AnchorAlignment.exactly] with an [Anchor]) or in
/// a relative/dynamic alignment (using [AnchorAlignment.align] with an [Alignment]).
///
/// If using [AnchorPos.align], the provided [AlignmentGeometry]'s factors must
/// If using [AnchorAlignment.align], the provided [AlignmentGeometry]'s factors must
/// be either -1, 1, or 0 only (ie. the pre-provided [Alignment]s).
/// [textDirection] will default to [TextDirection.ltr], and is used to resolve
/// the [AlignmentGeometry].
@immutable
class AnchorPos {
class AnchorAlignment {
/// The default, central alignment
static const defaultAnchorPos = AnchorPos.align(Alignment.center);
static const defaultAnchorAlignment = AnchorAlignment(Alignment.center);

/// Exact left/top anchor
///
/// Set only if constructed with [AnchorPos.exactly].
final Anchor? anchor;

/// Relative/dynamic alignment
/// Relative/dynamic alignment geometry.
///
/// Transformed into [anchor] at runtime by [Anchor.fromPos]. Resolved by
/// [textDirection].
///
/// Set only if constructed with [AnchorPos.align].
final AlignmentGeometry? alignment;
/// Set only if constructed with [AnchorAlignment.align].
final AlignmentGeometry geometry;

/// Used to resolve [alignment].
///
/// Set only if constructed with [AnchorPos.align].
final TextDirection? textDirection;

/// Defines the positioning of a [Marker.builder] widget relative to the center
/// of its bounding box, with an exact left/top anchor
const AnchorPos.exactly(Anchor this.anchor)
: alignment = null,
textDirection = null;
/// Set only if constructed with [AnchorAlignment.align].
final TextDirection textDirection;

/// Defines the positioning of a [Marker.builder] widget relative to the center
/// of its bounding box, with a relative/dynamic alignment
///
/// [alignment]'s factors must be either -1, 1, or 0 only (ie. the pre-provided
/// [Alignment]s). [textDirection] will default to [TextDirection.ltr], and is
/// used to resolve the [AlignmentGeometry].
const AnchorPos.align(
AlignmentGeometry this.alignment, {
const AnchorAlignment(
this.geometry, {
this.textDirection = TextDirection.ltr,
}) : anchor = null;
});
}

/// Exact alignment for a [Marker.builder] widget relative to the center
/// of its bounding box defined by its [Marker.height] & [Marker.width]
///
/// May be generated from an [AnchorPos] (usually with [AnchorPos.alignment]
/// May be generated from an [AnchorAlignment] (usually with [AnchorAlignment.geometry]
/// defined) and dimensions through [Anchor.fromPos].
@immutable
class Anchor {
Expand All @@ -69,33 +58,31 @@ class Anchor {

const Anchor(this.left, this.top);

factory Anchor.fromPos(AnchorPos pos, double width, double height) {
if (pos.anchor case final anchor?) return anchor;
if (pos.alignment case final alignment?) {
return Anchor(
switch (alignment.resolve(pos.textDirection).x) {
-1 => 0,
1 => width,
0 => width / 2,
_ => throw ArgumentError.value(
alignment,
'alignment',
'The `x` factor must be -1, 1, or 0 only (ie. the pre-provided alignments)',
),
},
switch (alignment.resolve(pos.textDirection).y) {
-1 => 0,
1 => height,
0 => height / 2,
_ => throw ArgumentError.value(
alignment,
'alignment',
'The `y` factor must be -1, 1, or 0 only (ie. the pre-provided alignments)',
),
},
);
}
throw Exception();
factory Anchor.fromPos(
AnchorAlignment alignment, double width, double height) {
final geometry = alignment.geometry;
return Anchor(
switch (geometry.resolve(alignment.textDirection).x) {
-1 => 0,
1 => width,
0 => width / 2,
_ => throw ArgumentError.value(
geometry,
'alignment',
'The `x` factor must be -1, 1, or 0 only (ie. the pre-provided alignments)',
),
},
switch (geometry.resolve(alignment.textDirection).y) {
-1 => 0,
1 => height,
0 => height / 2,
_ => throw ArgumentError.value(
geometry,
'alignment',
'The `y` factor must be -1, 1, or 0 only (ie. the pre-provided alignments)',
),
},
);
}
}

Expand Down Expand Up @@ -137,8 +124,8 @@ class Marker {

/// The alignment of the origin, relative to the size of the box.
///
/// Automatically set to the opposite of `anchorPos`, if it was constructed by
/// [AnchorPos.align], but can be overridden.
/// Automatically set to the opposite of `alignment`, if it was constructed by
/// [AnchorAlignment.align], but can be overridden.
///
/// This is equivalent to setting an origin based on the size of the box.
/// If it is specified at the same time as the [rotateOrigin], both are applied.
Expand All @@ -152,20 +139,32 @@ class Marker {
/// [Directionality.of] returns [TextDirection.rtl].
final AlignmentGeometry? rotateAlignment;

Marker({
const Marker({
this.key,
required this.point,
required this.builder,
this.width = 30.0,
this.height = 30.0,
this.anchor,
this.rotate,
this.rotateOrigin,
this.rotateAlignment,
});

Marker.withAlignment({
this.key,
required this.point,
required this.builder,
this.width = 30.0,
this.height = 30.0,
AnchorPos? anchorPos,
AnchorAlignment? alignment,
this.rotate,
this.rotateOrigin,
AlignmentGeometry? rotateAlignment,
}) : anchor =
anchorPos == null ? null : Anchor.fromPos(anchorPos, width, height),
alignment == null ? null : Anchor.fromPos(alignment, width, height),
rotateAlignment = rotateAlignment ??
(anchorPos?.alignment != null ? anchorPos!.alignment! * -1 : null);
(alignment != null ? alignment.geometry * -1 : null);
}

@immutable
Expand All @@ -175,8 +174,8 @@ class MarkerLayer extends StatelessWidget {
/// Positioning of the [Marker.builder] widget relative to the center of its
/// bounding box defined by its [Marker.height] & [Marker.width]
///
/// Overriden on a per [Marker] basis if [Marker.anchorPos] is specified.
final AnchorPos? anchorPos;
/// Overriden on a per [Marker] basis if [Marker.alignment] is specified.
final AnchorAlignment? alignment;

/// Whether to counter rotate markers to the map's rotation, to keep a fixed
/// orientation
Expand All @@ -195,8 +194,8 @@ class MarkerLayer extends StatelessWidget {

/// The alignment of the origin, relative to the size of the box.
///
/// Automatically set to the opposite of `anchorPos`, if it was constructed by
/// [AnchorPos.align], but can be overridden.
/// Automatically set to the opposite of `alignment`, if it was constructed by
/// [AnchorAlignment.align], but can be overridden.
///
/// This is equivalent to setting an origin based on the size of the box.
/// If it is specified at the same time as the [rotateOrigin], both are applied.
Expand All @@ -215,7 +214,7 @@ class MarkerLayer extends StatelessWidget {
const MarkerLayer({
super.key,
this.markers = const [],
this.anchorPos,
this.alignment,
this.rotate = false,
this.rotateOrigin,
this.rotateAlignment,
Expand All @@ -236,7 +235,7 @@ class MarkerLayer extends StatelessWidget {
// unlike the map coordinates.
final anchor = marker.anchor ??
Anchor.fromPos(
anchorPos ?? AnchorPos.defaultAnchorPos,
alignment ?? AnchorAlignment.defaultAnchorAlignment,
marker.width,
marker.height,
);
Expand All @@ -250,8 +249,8 @@ class MarkerLayer extends StatelessWidget {
continue;
}

final defaultAlignment = anchorPos?.alignment != null
? anchorPos!.alignment! * -1
final defaultRotateAlignment = alignment != null
? alignment!.geometry * -1
: Alignment.center;

final pos = pxPoint.subtract(map.pixelOrigin);
Expand All @@ -260,7 +259,7 @@ class MarkerLayer extends StatelessWidget {
angle: -map.rotationRad,
origin: marker.rotateOrigin ?? rotateOrigin ?? Offset.zero,
alignment:
marker.rotateAlignment ?? rotateAlignment ?? defaultAlignment,
marker.rotateAlignment ?? rotateAlignment ?? defaultRotateAlignment,
child: marker.builder(context),
)
: marker.builder(context);
Expand Down

0 comments on commit f35d70e

Please sign in to comment.