flet-map 0.1.0.dev2__py3-none-any.whl → 0.2.0.dev45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flet-map might be problematic. Click here for more details.

Files changed (37) hide show
  1. flet_map/__init__.py +30 -19
  2. flet_map/circle_layer.py +46 -139
  3. flet_map/map.py +462 -604
  4. flet_map/map_layer.py +14 -20
  5. flet_map/marker_layer.py +83 -169
  6. flet_map/polygon_layer.py +95 -232
  7. flet_map/polyline_layer.py +85 -262
  8. flet_map/rich_attribution.py +48 -126
  9. flet_map/simple_attribution.py +24 -75
  10. flet_map/source_attribution.py +73 -0
  11. flet_map/tile_layer.py +224 -266
  12. flet_map/types.py +953 -0
  13. flet_map-0.2.0.dev45.dist-info/METADATA +66 -0
  14. flet_map-0.2.0.dev45.dist-info/RECORD +35 -0
  15. {flet_map-0.1.0.dev2.dist-info → flet_map-0.2.0.dev45.dist-info}/WHEEL +1 -1
  16. flet_map-0.2.0.dev45.dist-info/licenses/LICENSE +201 -0
  17. flutter/flet_map/CHANGELOG.md +4 -0
  18. flutter/flet_map/lib/flet_map.dart +1 -1
  19. flutter/flet_map/lib/src/circle_layer.dart +15 -25
  20. flutter/flet_map/lib/src/extension.dart +37 -0
  21. flutter/flet_map/lib/src/map.dart +93 -105
  22. flutter/flet_map/lib/src/marker_layer.dart +21 -33
  23. flutter/flet_map/lib/src/polygon_layer.dart +32 -52
  24. flutter/flet_map/lib/src/polyline_layer.dart +41 -64
  25. flutter/flet_map/lib/src/rich_attribution.dart +34 -34
  26. flutter/flet_map/lib/src/simple_attribution.dart +9 -23
  27. flutter/flet_map/lib/src/tile_layer.dart +47 -60
  28. flutter/flet_map/lib/src/utils/attribution_alignment.dart +1 -3
  29. flutter/flet_map/lib/src/utils/map.dart +257 -203
  30. flutter/flet_map/pubspec.lock +179 -130
  31. flutter/flet_map/pubspec.yaml +10 -5
  32. flet_map/text_source_attribution.py +0 -87
  33. flet_map-0.1.0.dev2.dist-info/METADATA +0 -168
  34. flet_map-0.1.0.dev2.dist-info/RECORD +0 -34
  35. flutter/flet_map/lib/src/create_control.dart +0 -70
  36. flutter/flet_map/lib/src/text_source_attribution.dart +0 -29
  37. {flet_map-0.1.0.dev2.dist-info → flet_map-0.2.0.dev45.dist-info}/top_level.txt +0 -0
flet_map/types.py ADDED
@@ -0,0 +1,953 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum, IntFlag
3
+ from typing import List, Optional
4
+
5
+ import flet as ft
6
+
7
+ __all__ = [
8
+ "TileLayerEvictErrorTileStrategy",
9
+ "AttributionAlignment",
10
+ "PatternFit",
11
+ "Camera",
12
+ "StrokePattern",
13
+ "SolidStrokePattern",
14
+ "DashedStrokePattern",
15
+ "DottedStrokePattern",
16
+ "MapLatitudeLongitude",
17
+ "MapLatitudeLongitudeBounds",
18
+ "InteractionFlag",
19
+ "MultiFingerGesture",
20
+ "InteractionConfiguration",
21
+ "MapEventSource",
22
+ "CameraFit",
23
+ "MapTapEvent",
24
+ "MapHoverEvent",
25
+ "MapPositionChangeEvent",
26
+ "MapPointerEvent",
27
+ "MapEvent",
28
+ "TileDisplay",
29
+ "InstantaneousTileDisplay",
30
+ "FadeInTileDisplay",
31
+ "KeyboardConfiguration",
32
+ "CursorRotationBehaviour",
33
+ "CursorKeyboardRotationConfiguration",
34
+ ]
35
+
36
+
37
+ class TileLayerEvictErrorTileStrategy(Enum):
38
+ """Strategies on how to handle tile errors."""
39
+
40
+ NONE = "none"
41
+ """Never evict images for tiles which failed to load."""
42
+
43
+ DISPOSE = "dispose"
44
+ """Evict images for tiles which failed to load when they are pruned."""
45
+
46
+ NOT_VISIBLE = "notVisible"
47
+ """
48
+ Evict images for tiles which failed to load and:
49
+ - do not belong to the current zoom level AND/OR
50
+ - are not visible
51
+ """
52
+
53
+ NOT_VISIBLE_RESPECT_MARGIN = "notVisibleRespectMargin"
54
+ """
55
+ Evict images for tiles which failed to load and:
56
+ - do not belong to the current zoom level AND/OR
57
+ - are not visible, respecting the pruning buffer (the maximum of the `keep_buffer` and `pan_buffer`).
58
+ """
59
+
60
+
61
+ class AttributionAlignment(Enum):
62
+ """Position to anchor [`RichAttribution`][(p).] control relative to the map."""
63
+
64
+ BOTTOM_LEFT = "bottomLeft"
65
+ """The bottom left corner."""
66
+
67
+ BOTTOM_RIGHT = "bottomRight"
68
+ """The bottom right corner."""
69
+
70
+
71
+ class PatternFit(Enum):
72
+ """
73
+ Determines how a non-solid [`StrokePattern`][(p).] should be fit to a line
74
+ when their lengths are not equal or multiples
75
+ """
76
+
77
+ NONE = "none"
78
+ """
79
+ Don't apply any specific fit to the pattern - repeat exactly as specified,
80
+ and stop when the last point is reached.
81
+
82
+ Not recommended, as it may leave a gap between the final segment and the last
83
+ point, making it unclear where the line ends.
84
+ """
85
+
86
+ SCALE_DOWN = "scaleDown"
87
+ """
88
+ Scale the pattern to ensure it fits an integer number of times into the
89
+ polyline (smaller version regarding rounding, cf. [`SCALE_UP`][..]).
90
+ """
91
+
92
+ SCALE_UP = "scaleUp"
93
+ """
94
+ Scale the pattern to ensure it fits an integer number of times into the
95
+ polyline (bigger version regarding rounding, cf. [`SCALE_DOWN`][..]).
96
+ """
97
+
98
+ APPEND_DOT = "appendDot"
99
+ """
100
+ Uses the pattern exactly, truncating the final dash if it does not fit, or
101
+ adding a single dot at the last point if the final dash does not reach the
102
+ last point (there is a gap at that location).
103
+ """
104
+
105
+ EXTEND_FINAL_DASH = "extendFinalDash"
106
+ """
107
+ Uses the pattern exactly, truncating the final dash if it does not fit, or
108
+ extending the final dash to the last point if it would not normally reach
109
+ that point (there is a gap at that location).
110
+
111
+ Only useful when working with [`DashedStrokePattern`][(p).].
112
+ Similar to `APPEND_DOT` for `DottedStrokePattern`.
113
+ """
114
+
115
+
116
+ @dataclass
117
+ class Camera:
118
+ center: "MapLatitudeLongitude"
119
+ """
120
+ The center of this camera.
121
+ """
122
+
123
+ zoom: ft.Number
124
+ """
125
+ Defines how far this camera is zoomed.
126
+ """
127
+
128
+ min_zoom: ft.Number
129
+ """
130
+ The minimum allowed zoom level.
131
+ """
132
+
133
+ max_zoom: ft.Number
134
+ """
135
+ The maximum allowed zoom level.
136
+ """
137
+
138
+ rotation: ft.Number
139
+ """
140
+ The rotation (in degrees) of the camera.
141
+ """
142
+
143
+
144
+ @dataclass(kw_only=True)
145
+ class StrokePattern:
146
+ """
147
+ Determines whether a stroke should be solid, dotted, or dashed,
148
+ and the exact characteristics of each.
149
+
150
+ This is an abstract class and shouldn't be used directly.
151
+
152
+ See usable derivatives:
153
+ - [`SolidStrokePattern`][(p).]
154
+ - [`DashedStrokePattern`][(p).]
155
+ - [`DottedStrokePattern`][(p).]
156
+ """
157
+
158
+ _type: str = ""
159
+
160
+
161
+ @dataclass
162
+ class SolidStrokePattern(StrokePattern):
163
+ """A solid/unbroken stroke pattern."""
164
+
165
+ def __post_init__(self):
166
+ self._type = "solid"
167
+
168
+
169
+ @dataclass
170
+ class DashedStrokePattern(StrokePattern):
171
+ """
172
+ A stroke pattern of alternating dashes and gaps, defined by [`segments`][(c).].
173
+
174
+ Raises:
175
+ AssertionError: If `segments` does not contain at least two items, or has an odd length.
176
+ """
177
+
178
+ segments: List[ft.Number] = field(default_factory=list)
179
+ """
180
+ A list of alternating dash and gap lengths, in pixels.
181
+
182
+ Note:
183
+ Must contain at least two items, and have an even length.
184
+ """
185
+ pattern_fit: PatternFit = PatternFit.SCALE_UP
186
+ """
187
+ Determines how this stroke pattern should be fit to a line when their lengths are not equal or multiples.
188
+ """
189
+
190
+ def __post_init__(self):
191
+ assert len(self.segments) >= 2, "segments must contain at least two items"
192
+ assert len(self.segments) % 2 == 0, "segments must have an even length"
193
+ self._type = "dashed"
194
+
195
+
196
+ @dataclass
197
+ class DottedStrokePattern(StrokePattern):
198
+ """
199
+ A stroke pattern of circular dots, spaced with [`spacing_factor`][(c).].
200
+
201
+ Raises:
202
+ AssertionError: If [`spacing_factor`][(c).] is negative.
203
+ """
204
+
205
+ spacing_factor: ft.Number = 1.5
206
+ """
207
+ The multiplier used to calculate the spacing between dots in a dotted polyline,
208
+ with respect to `Polyline.stroke_width` / `Polygon.border_stroke_width`.
209
+ A value of `1.0` will result in spacing equal to the `stroke_width`.
210
+ Increasing the value increases the spacing with the same scaling.
211
+
212
+ May also be scaled by the use of [`PatternFit.SCALE_UP`][(p).].
213
+
214
+ Note:
215
+ Must be non-negative.
216
+ """
217
+ pattern_fit: PatternFit = PatternFit.SCALE_UP
218
+ """
219
+ Determines how this stroke pattern should be fit to a line when their lengths are not equal or multiples.
220
+ """
221
+
222
+ def __post_init__(self):
223
+ assert (
224
+ self.spacing_factor > 0
225
+ ), "spacing_factor must be greater than or equal to 0.0"
226
+ self._type = "dotted"
227
+
228
+
229
+ @dataclass
230
+ class MapLatitudeLongitude:
231
+ """Map coordinates in degrees."""
232
+
233
+ latitude: ft.Number
234
+ """The latitude point of this coordinate."""
235
+
236
+ longitude: ft.Number
237
+ """The longitude point of this coordinate."""
238
+
239
+
240
+ @dataclass
241
+ class MapLatitudeLongitudeBounds:
242
+ """
243
+ Both corners have to be on opposite sites, but it doesn't matter
244
+ which opposite corners or in what order the corners are provided.
245
+ """
246
+
247
+ corner_1: MapLatitudeLongitude
248
+ """The corner 1."""
249
+
250
+ corner_2: MapLatitudeLongitude
251
+ """The corner 2."""
252
+
253
+
254
+ class InteractionFlag(IntFlag):
255
+ """
256
+ Flags to enable/disable certain interaction events on the map.
257
+
258
+ Example:
259
+ - `InteractionFlag.ALL` to enable all events
260
+ - `InteractionFlag.NONE` to disable all events
261
+ """
262
+
263
+ NONE = 0
264
+ """No interaction."""
265
+
266
+ DRAG = 1 << 0
267
+ """Panning with a single finger or cursor."""
268
+
269
+ FLING_ANIMATION = 1 << 1
270
+ """Fling animation after panning if velocity is great enough."""
271
+
272
+ PINCH_MOVE = 1 << 2
273
+ """Panning with multiple fingers."""
274
+
275
+ PINCH_ZOOM = 1 << 3
276
+ """Zooming with a multi-finger pinch gesture."""
277
+
278
+ DOUBLE_TAP_ZOOM = 1 << 4
279
+ """Zooming with a single-finger double tap gesture."""
280
+
281
+ DOUBLE_TAP_DRAG_ZOOM = 1 << 5
282
+ """Zooming with a single-finger double-tap-drag gesture."""
283
+
284
+ SCROLL_WHEEL_ZOOM = 1 << 6
285
+ """Zooming with a mouse scroll wheel."""
286
+
287
+ ROTATE = 1 << 7
288
+ """Rotation with two-finger twist gesture."""
289
+
290
+ ALL = (
291
+ (1 << 0)
292
+ | (1 << 1)
293
+ | (1 << 2)
294
+ | (1 << 3)
295
+ | (1 << 4)
296
+ | (1 << 5)
297
+ | (1 << 6)
298
+ | (1 << 7)
299
+ )
300
+ """All available interactive flags."""
301
+
302
+ @staticmethod
303
+ def has_flag(left_flags: int, right_flags: int) -> bool:
304
+ """
305
+ Returns:
306
+ `True` if `left_flags` has at least one member in `right_flags` (intersection).
307
+ """
308
+ return left_flags & right_flags != 0
309
+
310
+ @staticmethod
311
+ def has_multi_finger(flags: int) -> bool:
312
+ """
313
+ Returns:
314
+ `True` if any multi-finger gesture flags
315
+ ([`MultiFingerGesture.PINCH_MOVE`][(p).], [`MultiFingerGesture.PINCH_ZOOM`][(p).], [`MultiFingerGesture.ROTATE`][(p).]) are enabled.
316
+ """
317
+ return InteractionFlag.has_flag(
318
+ flags,
319
+ (
320
+ MultiFingerGesture.PINCH_MOVE
321
+ | MultiFingerGesture.PINCH_ZOOM
322
+ | MultiFingerGesture.ROTATE
323
+ ),
324
+ )
325
+
326
+ @staticmethod
327
+ def has_drag(flags: int) -> bool:
328
+ """
329
+ Returns:
330
+ `True` if the [`DRAG`][..] interactive flag is enabled.
331
+ """
332
+ return InteractionFlag.has_flag(flags, InteractionFlag.DRAG)
333
+
334
+ @staticmethod
335
+ def has_fling_animation(flags: int) -> bool:
336
+ """
337
+ Returns:
338
+ `True` if the [`FLING_ANIMATION`][..] interactive flag is enabled.
339
+ """
340
+ return InteractionFlag.has_flag(flags, InteractionFlag.FLING_ANIMATION)
341
+
342
+ @staticmethod
343
+ def has_pinch_move(flags: int) -> bool:
344
+ """
345
+ Returns:
346
+ `True` if the [`PINCH_MOVE`][..] interactive flag is enabled.
347
+ """
348
+ return InteractionFlag.has_flag(flags, InteractionFlag.PINCH_MOVE)
349
+
350
+ @staticmethod
351
+ def has_fling_pinch_zoom(flags: int) -> bool:
352
+ """
353
+ Returns:
354
+ `True` if the [`PINCH_ZOOM`][..] interactive flag is enabled.
355
+ """
356
+ return InteractionFlag.has_flag(flags, InteractionFlag.PINCH_ZOOM)
357
+
358
+ @staticmethod
359
+ def has_double_tap_drag_zoom(flags: int) -> bool:
360
+ """
361
+ Returns:
362
+ `True` if the [`DOUBLE_TAP_DRAG_ZOOM`][..] interactive flag is enabled.
363
+ """
364
+ return InteractionFlag.has_flag(flags, InteractionFlag.DOUBLE_TAP_DRAG_ZOOM)
365
+
366
+ @staticmethod
367
+ def has_double_tap_zoom(flags: int) -> bool:
368
+ """
369
+ Returns:
370
+ `True` if the [`DOUBLE_TAP_ZOOM`][..] interactive flag is enabled.
371
+ """
372
+ return InteractionFlag.has_flag(flags, InteractionFlag.DOUBLE_TAP_ZOOM)
373
+
374
+ @staticmethod
375
+ def has_rotate(flags: int) -> bool:
376
+ """
377
+ Returns:
378
+ `True` if the [`ROTATE`][..] interactive flag is enabled.
379
+ """
380
+ return InteractionFlag.has_flag(flags, InteractionFlag.ROTATE)
381
+
382
+ @staticmethod
383
+ def has_scroll_wheel_zoom(flags: int) -> bool:
384
+ """
385
+ Returns:
386
+ `True` if the [`SCROLL_WHEEL_ZOOM`][..] interactive flag is enabled.
387
+ """
388
+ return InteractionFlag.has_flag(flags, InteractionFlag.SCROLL_WHEEL_ZOOM)
389
+
390
+
391
+ class MultiFingerGesture(IntFlag):
392
+ """Flags to enable/disable certain multi-finger gestures on the map."""
393
+
394
+ NONE = 0
395
+ """No multi-finger gesture."""
396
+
397
+ PINCH_MOVE = 1 << 0
398
+ """Pinch move gesture, which allows moving the map by dragging with two fingers."""
399
+
400
+ PINCH_ZOOM = 1 << 1
401
+ """Pinch zoom gesture, which allows zooming in and out by pinching with two fingers."""
402
+
403
+ ROTATE = 1 << 2
404
+ """Rotate gesture, which allows rotating the map by twisting two fingers."""
405
+
406
+ ALL = (1 << 0) | (1 << 1) | (1 << 2)
407
+ """All multi-finger gestures defined in this enum."""
408
+
409
+
410
+ @dataclass
411
+ class InteractionConfiguration:
412
+ enable_multi_finger_gesture_race: bool = False
413
+ """
414
+ If `True`, then [`rotation_threshold`][..] and [`pinch_zoom_threshold`][..] and [`pinch_move_threshold`][..] will race.
415
+ If multiple gestures win at the same time,
416
+ then precedence: [`pinch_zoom_win_gestures`][..] > [`rotation_win_gestures`][..] > [`pinch_move_win_gestures`][..]
417
+ """
418
+
419
+ pinch_move_threshold: ft.Number = 40.0
420
+ """
421
+ Map starts to move when `pinch_move_threshold` has been achieved
422
+ or another multi finger gesture wins which allows [`MultiFingerGesture.PINCH_MOVE`][(p).].
423
+
424
+ Note:
425
+ If [`InteractionConfiguration.flags`][(p).] doesn't contain [`InteractionFlag.PINCH_MOVE`][(p).]
426
+ or [`enable_multi_finger_gesture_race`][..] is false then pinch move cannot win.
427
+ """
428
+
429
+ scroll_wheel_velocity: ft.Number = 0.005
430
+ """
431
+ The used velocity how fast the map should zoom in or out by scrolling with the scroll wheel of a mouse.
432
+ """
433
+
434
+ pinch_zoom_threshold: ft.Number = 0.5
435
+ """
436
+ Map starts to zoom when `pinch_zoom_threshold` has been achieved or
437
+ another multi finger gesture wins which allows [`MultiFingerGesture.PINCH_ZOOM`][(p).].
438
+
439
+ Note:
440
+ If [`InteractionConfiguration.flags`][(p).] doesn't contain [`InteractionFlag.PINCH_ZOOM`][(p).]
441
+ or [`enable_multi_finger_gesture_race`][..] is false then zoom cannot win.
442
+ """
443
+
444
+ rotation_threshold: ft.Number = 20.0
445
+ """
446
+ Map starts to rotate when `rotation_threshold` has been achieved or
447
+ another multi finger gesture wins which allows [`MultiFingerGesture.ROTATE`][(p).].
448
+
449
+ Note:
450
+ If [`InteractionConfiguration.flags`][(p).] doesn't contain [`InteractionFlag.ROTATE`][(p).]
451
+ or [`enable_multi_finger_gesture_race`][..] is false then rotate cannot win.
452
+ """
453
+
454
+ flags: InteractionFlag = InteractionFlag.ALL
455
+ """
456
+ Defines the map events to be enabled/disabled.
457
+ """
458
+
459
+ rotation_win_gestures: MultiFingerGesture = MultiFingerGesture.ROTATE
460
+ """
461
+ When [`rotation_threshold`[..] wins over [`pinch_zoom_threshold`[..] and
462
+ [`pinch_move_threshold`[..] then `rotation_win_gestures` gestures will be used.
463
+ """
464
+
465
+ pinch_move_win_gestures: MultiFingerGesture = (
466
+ MultiFingerGesture.PINCH_ZOOM | MultiFingerGesture.PINCH_MOVE
467
+ )
468
+ """
469
+ When [`pinch_move_threshold`][..] wins over [`rotation_threshold`][..]
470
+ and [`pinch_zoom_threshold`][..] then `pinch_move_win_gestures` gestures will be used.
471
+
472
+ By default [`MultiFingerGesture.PINCH_MOVE`][(p).] and [`MultiFingerGesture.PINCH_ZOOM`][(p).]
473
+ gestures will take effect see [`MultiFingerGesture`][(p).] for custom settings.
474
+ """
475
+
476
+ pinch_zoom_win_gestures: MultiFingerGesture = (
477
+ MultiFingerGesture.PINCH_ZOOM | MultiFingerGesture.PINCH_MOVE
478
+ )
479
+ """
480
+ When [`pinch_zoom_threshold`][..] wins over [`rotation_threshold`][..] and [`pinch_move_threshold`][..]
481
+ then `pinch_zoom_win_gestures` gestures will be used.
482
+
483
+ By default [`MultiFingerGesture.PINCH_ZOOM`][(p).] and [`MultiFingerGesture.PINCH_MOVE`][(p).]
484
+ gestures will take effect see `MultiFingerGesture` for custom settings.
485
+ """
486
+
487
+ keyboard_configuration: "KeyboardConfiguration" = field(
488
+ default_factory=lambda: KeyboardConfiguration()
489
+ )
490
+ """
491
+ Options to configure how keyboard keys may be used to control the map.
492
+
493
+ Keyboard movements using the arrow keys are enabled by default.
494
+ """
495
+
496
+ cursor_keyboard_rotation_configuration: "CursorKeyboardRotationConfiguration" = (
497
+ field(default_factory=lambda: CursorKeyboardRotationConfiguration())
498
+ )
499
+ """
500
+ Options to control the keyboard and mouse cursor being used together to rotate the map.
501
+ """
502
+
503
+
504
+ class MapEventSource(Enum):
505
+ """Defines the source of a [`MapEvent`][(p).]."""
506
+
507
+ MAP_CONTROLLER = "mapController"
508
+ """The `MapEvent` is caused programmatically by the `MapController`."""
509
+
510
+ TAP = "tap"
511
+ """The `MapEvent` is caused by a tap gesture."""
512
+
513
+ SECONDARY_TAP = "secondaryTap"
514
+ """The `MapEvent` is caused by a secondary tap gesture."""
515
+
516
+ LONG_PRESS = "longPress"
517
+ """The `MapEvent` is caused by a long press gesture."""
518
+
519
+ DOUBLE_TAP = "doubleTap"
520
+ """The `MapEvent` is caused by a double tap gesture."""
521
+
522
+ DOUBLE_TAP_HOLD = "doubleTapHold"
523
+ """The `MapEvent` is caused by a double tap and hold gesture."""
524
+
525
+ DRAG_START = "dragStart"
526
+ """The `MapEvent` is caused by the start of a drag gesture."""
527
+
528
+ ON_DRAG = "onDrag"
529
+ """The `MapEvent` is caused by a drag update gesture."""
530
+
531
+ DRAG_END = "dragEnd"
532
+ """The `MapEvent` is caused by the end of a drag gesture."""
533
+
534
+ MULTI_FINGER_GESTURE_START = "multiFingerGestureStart"
535
+ """The `MapEvent` is caused by the start of a two finger gesture."""
536
+
537
+ ON_MULTI_FINGER = "onMultiFinger"
538
+ """The `MapEvent` is caused by a two finger gesture update."""
539
+
540
+ MULTI_FINGER_GESTURE_END = "multiFingerEnd"
541
+ """The `MapEvent` is caused by a the end of a two finger gesture."""
542
+
543
+ FLING_ANIMATION_CONTROLLER = "flingAnimationController"
544
+ """
545
+ The `MapEvent` is caused by the `AnimationController` while performing the fling gesture.
546
+ """
547
+
548
+ DOUBLE_TAP_ZOOM_ANIMATION_CONTROLLER = "doubleTapZoomAnimationController"
549
+ """
550
+ The `MapEvent` is caused by the `AnimationController`
551
+ while performing the double tap zoom in animation.
552
+ """
553
+
554
+ INTERACTIVE_FLAGS_CHANGED = "InteractionFlagsChanged"
555
+ """The `MapEvent` is caused by a change of the interactive flags."""
556
+
557
+ FIT_CAMERA = "fitCamera"
558
+ """The `MapEvent` is caused by calling fit_camera."""
559
+
560
+ CUSTOM = "custom"
561
+ """The `MapEvent` is caused by a custom source."""
562
+
563
+ SCROLL_WHEEL = "scrollWheel"
564
+ """The `MapEvent` is caused by a scroll wheel zoom gesture."""
565
+
566
+ NON_ROTATED_SIZE_CHANGE = "nonRotatedSizeChange"
567
+ """The `MapEvent` is caused by a size change of the `Map` constraints."""
568
+
569
+ CURSOR_KEYBOARD_ROTATION = "cursorKeyboardRotation"
570
+ """The `MapEvent` is caused by a 'CTRL + drag' rotation gesture."""
571
+
572
+ KEYBOARD = "keyboard"
573
+ """The `MapEvent` is caused by a keyboard key. See [`KeyboardConfiguration`][(p).] for details."""
574
+
575
+
576
+ @dataclass
577
+ class CameraFit:
578
+ """
579
+ Defines how the camera should fit the bounds or coordinates, depending on which one was provided.
580
+
581
+ Raises:
582
+ AssertionError: If both [`bounds`][(c).] and [`coordinates`][(c).] are `None` or not `None`.
583
+ """
584
+
585
+ bounds: Optional[MapLatitudeLongitudeBounds] = None
586
+ """
587
+ The bounds which the camera should contain once it is fitted.
588
+
589
+ Note:
590
+ If this is not `None`, [`coordinates`][..] should be `None`, and vice versa.
591
+ """
592
+
593
+ coordinates: Optional[List[MapLatitudeLongitude]] = None
594
+ """
595
+ The coordinates which the camera should contain once it is fitted.
596
+
597
+ Note:
598
+ If this is not `None`, [`bounds`][..] should be `None`, and vice versa.
599
+ """
600
+
601
+ max_zoom: Optional[ft.Number] = None
602
+ """
603
+ The inclusive upper zoom limit used for the resulting fit.
604
+ If the zoom level calculated for the fit exceeds the `max_zoom` value, `max_zoom` will be used instead.
605
+ """
606
+
607
+ min_zoom: ft.Number = 0.0
608
+ """
609
+ """
610
+
611
+ padding: ft.PaddingValue = field(default_factory=lambda: ft.Padding.zero())
612
+ """
613
+ Adds a constant/pixel-based padding to the normal fit.
614
+ """
615
+
616
+ force_integer_zoom_level: bool = False
617
+ """
618
+ Whether the zoom level of the resulting fit should be rounded to the nearest integer level.
619
+ """
620
+
621
+ def __post_init__(self):
622
+ assert (self.bounds and not self.coordinates) or (
623
+ self.coordinates and not self.bounds
624
+ ), "only one of bounds or coordinates must be provided, not both"
625
+
626
+
627
+ @dataclass
628
+ class MapTapEvent(ft.TapEvent):
629
+ coordinates: MapLatitudeLongitude
630
+ """Coordinates of the point at which the tap occured."""
631
+
632
+
633
+ @dataclass
634
+ class MapHoverEvent(ft.HoverEvent):
635
+ coordinates: MapLatitudeLongitude
636
+
637
+
638
+ @dataclass
639
+ class MapPositionChangeEvent(ft.Event[ft.EventControlType]):
640
+ coordinates: MapLatitudeLongitude
641
+ camera: Camera
642
+ has_gesture: bool
643
+
644
+
645
+ @dataclass
646
+ class MapPointerEvent(ft.PointerEvent):
647
+ coordinates: MapLatitudeLongitude
648
+ """Coordinates of the point at which the tap occured."""
649
+
650
+
651
+ @dataclass
652
+ class MapEvent(ft.Event[ft.EventControlType]):
653
+ source: MapEventSource
654
+ """Who/what issued the event."""
655
+
656
+ camera: Camera
657
+ """The map camera after the event."""
658
+
659
+
660
+ @dataclass(kw_only=True)
661
+ class TileDisplay:
662
+ """
663
+ Defines how the tile should get displayed on the map.
664
+
665
+ This is an abstract class and shouldn't be used directly.
666
+
667
+ See usable derivatives:
668
+ - `InstantaneousTileDisplay`
669
+ - `FadeInTileDisplay`
670
+ """
671
+
672
+ _type: str = ""
673
+
674
+
675
+ @dataclass
676
+ class InstantaneousTileDisplay(TileDisplay):
677
+ """A `TileDisplay` that should get instantaneously displayed."""
678
+
679
+ opacity: ft.Number = 1.0
680
+ """
681
+ The optional opacity of the tile.
682
+ """
683
+
684
+ def __post_init__(self):
685
+ assert (
686
+ 0.0 <= self.opacity <= 1.0
687
+ ), "start_opacity must be between 0.0 and 1.0 (inclusive)"
688
+ self._type = "instantaneous"
689
+
690
+
691
+ @dataclass
692
+ class FadeInTileDisplay(TileDisplay):
693
+ """A `TileDisplay` that should get faded in."""
694
+
695
+ duration: ft.DurationValue = field(
696
+ default_factory=lambda: ft.Duration(milliseconds=100)
697
+ )
698
+ """
699
+ The duration of the fade in animation.
700
+ """
701
+
702
+ start_opacity: ft.Number = 0.0
703
+ """
704
+ Opacity start value when a tile is faded in.
705
+ """
706
+
707
+ reload_start_opacity: ft.Number = 0.0
708
+ """
709
+ Opacity start value when a tile is reloaded.
710
+ """
711
+
712
+ def __post_init__(self):
713
+ assert (
714
+ 0.0 <= self.start_opacity <= 1.0
715
+ ), "start_opacity must be between 0.0 and 1.0 (inclusive)"
716
+ assert (
717
+ 0.0 <= self.reload_start_opacity <= 1.0
718
+ ), "reload_start_opacity must be between 0.0 and 1.0 (inclusive)"
719
+ self._type = "fadein"
720
+
721
+
722
+ @dataclass
723
+ class KeyboardConfiguration:
724
+ """
725
+ Options to configure how keyboard keys may be used to control the map.
726
+ When a key is pushed down, an animation starts, consisting of a curved
727
+ portion which takes the animation to its maximum velocity, an indefinitely
728
+ long animation at maximum velocity, then ended on the key up with another
729
+ curved portion.
730
+
731
+ If a key is pressed and released quickly, it might trigger a short animation
732
+ called a 'leap'. The leap consists of a part of the curved portion, and also
733
+ scales the velocity of the concerned gesture.
734
+
735
+ Info:
736
+ See [`CursorKeyboardRotationConfiguration`][(p).] for options to control the keyboard and
737
+ mouse cursor being used together to rotate the map.
738
+ """
739
+
740
+ autofocus: bool = True
741
+ """
742
+ Whether to request focus as soon as the map control appears (and to enable keyboard controls).
743
+ """
744
+
745
+ animation_curve_duration: ft.DurationValue = field(
746
+ default_factory=lambda: ft.Duration(milliseconds=450)
747
+ )
748
+ """
749
+ Duration of the curved (`ft.Curve.EASE_IN`) portion of the animation occuring
750
+ after a key down event (and after a key up event if [`animation_curve_reverse_duration`][..] is `None`)
751
+ """
752
+
753
+ animation_curve_reverse_duration: Optional[ft.DurationValue] = field(
754
+ default_factory=lambda: ft.Duration(milliseconds=600)
755
+ )
756
+ """
757
+ Duration of the curved (reverse `ft.Curve.EASE_IN`) portion of the animation
758
+ occuring after a key up event.
759
+
760
+ Set to `None` to use [`animation_curve_duration`][..].
761
+ """
762
+
763
+ animation_curve_curve: bool = True
764
+ """
765
+ Curve of the curved portion of the animation occuring after key down and key up events.
766
+ """
767
+
768
+ enable_arrow_keys_panning: bool = True
769
+ """
770
+ Whether to allow arrow keys to pan the map (in their respective directions).
771
+ """
772
+
773
+ enable_qe_rotating: bool = True
774
+ """
775
+ Whether to allow the Q & E keys (*) to rotate the map (Q rotates anticlockwise, E rotates clockwise).
776
+
777
+ QE are only the physical and logical keys on QWERTY keyboards.
778
+ On non- QWERTY keyboards, such as AZERTY,
779
+ the keys in the same position as on the QWERTY keyboard is used (ie. AE on AZERTY).
780
+ """
781
+
782
+ enable_rf_zooming: bool = True
783
+ """
784
+ Whether to allow the R & F keys to zoom the map (R zooms IN (increases zoom level), F zooms OUT (decreases zoom level)).
785
+
786
+ RF are only the physical and logical keys on QWERTY keyboards.
787
+ On non- QWERTY keyboards, such as AZERTY,
788
+ the keys in the same position as on the QWERTY keyboard is used (ie. RF on AZERTY).
789
+ """
790
+
791
+ enable_wasd_panning: bool = True
792
+ """
793
+ Whether to allow the W, A, S, D keys (*) to pan the map (in the directions UP, LEFT, DOWN, RIGHT respectively).
794
+
795
+ WASD are only the physical and logical keys on QWERTY keyboards.
796
+ On non- QWERTY keyboards, such as AZERTY,
797
+ the keys in the same position as on the QWERTY keyboard is used (ie. ZQSD on AZERTY).
798
+
799
+ If enabled, it is recommended to enable `enable_arrow_keys_panning` to provide panning functionality easily for left handed users.
800
+ """
801
+
802
+ leap_max_of_curve_component: ft.Number = 0.6
803
+ """
804
+ The percentage (0.0 - 1.0) of the curve animation component that is driven
805
+ to (from 0), then in reverse from (to 0).
806
+
807
+ Reducing means the leap occurs quicker (assuming a consistent curve
808
+ animation duration). Also see `*_leap_velocity_multiplier` properties to
809
+ change the distance of the leap assuming a consistent leap duration.
810
+
811
+ For example, if set to 1, then the leap will take `animation_curve_duration + animation_curve_reverse_duration`
812
+ to complete.
813
+
814
+ Must be greater than 0 and less than or equal to 1.
815
+ To disable leaping, or change the maximum length of the key press
816
+ that will trigger a leap, see [`perform_leap_trigger_duration`[..].
817
+ """
818
+
819
+ max_rotate_velocity: ft.Number = 3
820
+ """
821
+ The maximum angular difference to apply per frame to the camera's rotation
822
+ during a rotation animation.
823
+
824
+ Measured in degrees. Negative numbers will flip the standard rotation keys.
825
+ """
826
+
827
+ max_zoom_velocity: ft.Number = 0.03
828
+ """
829
+ The maximum zoom level difference to apply per frame to the camera's zoom
830
+ level during a zoom animation.
831
+
832
+ Measured in zoom levels. Negative numbers will flip the standard zoom keys.
833
+ """
834
+
835
+ pan_leap_velocity_multiplier: ft.Number = 5
836
+ """
837
+ The amount to scale the panning offset velocity by during a leap animation.
838
+
839
+ The larger the number, the larger the movement during a leap.
840
+ To change the duration of a leap, see [`leap_max_of_curve_component`[..].
841
+
842
+ This may cause the pan velocity to exceed [`max_pan_velocity`[..].
843
+ """
844
+
845
+ rotate_leap_velocity_multiplier: ft.Number = 3
846
+ """
847
+ The amount to scale the rotation velocity by during a leap animation
848
+
849
+ The larger the number, the larger the rotation difference during a leap.
850
+ To change the duration of a leap, see [`leap_max_of_curve_component`[..].
851
+
852
+ This may cause the pan velocity to exceed [`max_rotate_velocity`[..].
853
+ """
854
+
855
+ zoom_leap_velocity_multiplier: ft.Number = 3
856
+ """
857
+ The amount to scale the zooming velocity by during a leap animation.
858
+
859
+ The larger the number, the larger the zoom difference during a leap. To
860
+ change the duration of a leap, see [`leap_max_of_curve_component`[..].
861
+
862
+ This may cause the pan velocity to exceed [`max_zoom_velocity`[..].
863
+ """
864
+
865
+ perform_leap_trigger_duration: Optional[ft.DurationValue] = field(
866
+ default_factory=lambda: ft.Duration(milliseconds=100)
867
+ )
868
+ """
869
+ Maximum duration between the key down and key up events of an animation
870
+ which will trigger a 'leap'.
871
+
872
+ To customize the leap itself, see the [`leap_max_of_curve_component`[..] &
873
+ `[*leap_velocity_multiplier`[..] properties.
874
+
875
+ Set to `None` to disable leaping.
876
+ """
877
+
878
+ @classmethod
879
+ def disabled(cls) -> "KeyboardConfiguration":
880
+ """
881
+ Disable keyboard control of the map.
882
+
883
+ Info:
884
+ `CursorKeyboardRotationConfiguration` may still be active, and is not disabled if this is disabled.
885
+ """
886
+ return KeyboardConfiguration(
887
+ enable_arrow_keys_panning=False,
888
+ perform_leap_trigger_duration=None,
889
+ autofocus=False,
890
+ )
891
+
892
+
893
+ class CursorRotationBehaviour(Enum):
894
+ """
895
+ The behaviour of the cursor/keyboard rotation function in terms of the angle
896
+ that the map is rotated to.
897
+
898
+ Does not disable cursor/keyboard rotation, or adjust its triggers: see
899
+ `CursorKeyboardRotationConfiguration.is_key_trriger`.
900
+ """
901
+
902
+ OFFSET = "offset"
903
+ """
904
+ Offset the current rotation of the map to the angle at which the user drags their cursor.
905
+ """
906
+
907
+ SET_NORTH = "setNorth"
908
+ """
909
+ Set the North of the map to the angle at which the user drags their cursor.
910
+ """
911
+
912
+
913
+ @dataclass
914
+ class CursorKeyboardRotationConfiguration:
915
+ """
916
+ Options to configure cursor/keyboard rotation.
917
+
918
+ Cursor/keyboard rotation is designed for desktop platforms,
919
+ and allows the cursor to be used to set the rotation of the map
920
+ whilst a keyboard key is held down (as triggered by `is_key_trriger`).
921
+ """
922
+
923
+ set_north_on_click: bool = True
924
+ """
925
+ Whether to set the North of the map to the clicked angle,
926
+ when the user clicks their mouse without dragging
927
+ (a `on_pointer_down` event followed by `on_pointer_up` without a change in rotation).
928
+ """
929
+
930
+ behavior: CursorRotationBehaviour = CursorRotationBehaviour.OFFSET
931
+ """
932
+ The behaviour of the cursor/keyboard rotation function in terms of the
933
+ angle that the map is rotated to.
934
+
935
+ Does not disable cursor/keyboard rotation, or adjust its triggers: see `is_key_trriger`.
936
+ """
937
+
938
+ # TODO
939
+ trigger_keys: list = field(
940
+ default_factory=lambda: [
941
+ # ft.LogicalKeyboardKey.CONTROL,
942
+ # ft.LogicalKeyboardKey.CONTROL_LEFT,
943
+ # ft.LogicalKeyboardKey.CONTROL_RIGHT,
944
+ ]
945
+ )
946
+ """
947
+ List of keys that will trigger cursor/keyboard rotation, when pressed.
948
+ """
949
+
950
+ @classmethod
951
+ def disabled(cls) -> "CursorKeyboardRotationConfiguration":
952
+ """A disabled `CursorKeyboardRotationConfiguration`."""
953
+ return CursorKeyboardRotationConfiguration(trigger_keys=[])