From bb8a3d0d9584b0200c3748514132140a70207faf Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Wed, 15 Apr 2026 16:23:30 +0200 Subject: [PATCH 01/12] feat(flet-map): add overlay image layer --- sdk/python/packages/flet-map/CHANGELOG.md | 6 + .../flet-map/src/flet_map/__init__.py | 10 ++ .../flet-map/src/flet_map/map_layer.py | 1 + .../src/flet_map/overlay_image_layer.py | 114 ++++++++++++++++++ .../flutter/flet_map/lib/src/extension.dart | 3 + .../flet_map/lib/src/overlay_image_layer.dart | 84 +++++++++++++ 6 files changed, 218 insertions(+) create mode 100644 sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py create mode 100644 sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart diff --git a/sdk/python/packages/flet-map/CHANGELOG.md b/sdk/python/packages/flet-map/CHANGELOG.md index 405aad7cf7..02955ed250 100644 --- a/sdk/python/packages/flet-map/CHANGELOG.md +++ b/sdk/python/packages/flet-map/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.85.0 + +### Added + +- Added `OverlayImageLayer`, `OverlayImage`, and `RotatedOverlayImage`. + ## 0.81.1 ### Added diff --git a/sdk/python/packages/flet-map/src/flet_map/__init__.py b/sdk/python/packages/flet-map/src/flet_map/__init__.py index 7fc09030d0..dd4b377d00 100644 --- a/sdk/python/packages/flet-map/src/flet_map/__init__.py +++ b/sdk/python/packages/flet-map/src/flet_map/__init__.py @@ -2,6 +2,12 @@ from flet_map.map import Map from flet_map.map_layer import MapLayer from flet_map.marker_layer import Marker, MarkerLayer +from flet_map.overlay_image_layer import ( + BaseOverlayImage, + OverlayImage, + OverlayImageLayer, + RotatedOverlayImage, +) from flet_map.polygon_layer import PolygonLayer, PolygonMarker from flet_map.polyline_layer import PolylineLayer, PolylineMarker from flet_map.rich_attribution import RichAttribution @@ -45,6 +51,7 @@ __all__ = [ "AttributionAlignment", + "BaseOverlayImage", "Camera", "CameraFit", "CircleLayer", @@ -73,12 +80,15 @@ "Marker", "MarkerLayer", "MultiFingerGesture", + "OverlayImage", + "OverlayImageLayer", "PatternFit", "PolygonLayer", "PolygonMarker", "PolylineLayer", "PolylineMarker", "RichAttribution", + "RotatedOverlayImage", "SimpleAttribution", "SolidStrokePattern", "SourceAttribution", diff --git a/sdk/python/packages/flet-map/src/flet_map/map_layer.py b/sdk/python/packages/flet-map/src/flet_map/map_layer.py index 2e954c0e03..aea198aa5c 100644 --- a/sdk/python/packages/flet-map/src/flet_map/map_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/map_layer.py @@ -12,6 +12,7 @@ class MapLayer(ft.Control): - :class:`~flet_map.CircleLayer` - :class:`~flet_map.MarkerLayer` + - :class:`~flet_map.OverlayImageLayer` - :class:`~flet_map.PolygonLayer` - :class:`~flet_map.PolylineLayer` - :class:`~flet_map.RichAttribution` diff --git a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py new file mode 100644 index 0000000000..06149db66e --- /dev/null +++ b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py @@ -0,0 +1,114 @@ +from typing import Union + +import flet as ft +from flet_map.map_layer import MapLayer +from flet_map.types import MapLatitudeLongitude, MapLatitudeLongitudeBounds + +__all__ = [ + "BaseOverlayImage", + "OverlayImage", + "OverlayImageLayer", + "RotatedOverlayImage", +] + + +@ft.control("BaseOverlayImage", kw_only=True) +class BaseOverlayImage(ft.Control): + """ + Abstract class for image overlays displayed through + :class:`~flet_map.OverlayImageLayer`. + + The following overlay image types are available: + + - :class:`~flet_map.OverlayImage` + - :class:`~flet_map.RotatedOverlayImage` + """ + + src: Union[str, bytes] + """ + The image source. + + It can be one of the following: + - A URL or local [asset file](https://flet.dev/docs/cookbook/assets) path; + - A base64 string; + - Raw bytes. + """ + + opacity: ft.Number = 1.0 + """ + The opacity used to paint the image. + + Raises: + ValueError: If its value is not between `0.0` and `1.0` inclusive. + """ + + gapless_playback: bool = False + """ + Whether to continue showing the old image (`True`), or briefly show nothing + (`False`), when the image provider changes. + """ + + filter_quality: ft.FilterQuality = ft.FilterQuality.MEDIUM + """ + The rendering quality of the image. + """ + + def before_update(self): + super().before_update() + if not (0.0 <= self.opacity <= 1.0): + raise ValueError( + f"opacity must be between 0.0 and 1.0 inclusive, got {self.opacity}" + ) + + +@ft.control("OverlayImage", kw_only=True) +class OverlayImage(BaseOverlayImage): + """ + An unrotated image overlay that spans between a given bounding box. + """ + + bounds: MapLatitudeLongitudeBounds + """ + The latitude and longitude bounds where this image will be displayed. + """ + + +@ft.control("RotatedOverlayImage", kw_only=True) +class RotatedOverlayImage(BaseOverlayImage): + """ + An image overlay transformed across three corner points. + + The top-right corner is derived from :attr:`top_left_corner`, + :attr:`bottom_left_corner`, and :attr:`bottom_right_corner`. + """ + + top_left_corner: MapLatitudeLongitude + """ + The coordinates of the top-left corner of the image. + """ + + bottom_left_corner: MapLatitudeLongitude + """ + The coordinates of the bottom-left corner of the image. + """ + + bottom_right_corner: MapLatitudeLongitude + """ + The coordinates of the bottom-right corner of the image. + """ + + +@ft.control("OverlayImageLayer", kw_only=True) +class OverlayImageLayer(MapLayer): + """ + A layer to display image overlays. + + Note: + Place this layer after every non-translucent layer that should appear + below it. Layers rendered after this one may cover its overlay images. + """ + + overlay_images: list[BaseOverlayImage] + """ + A list of image overlays to display. + """ diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/extension.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/extension.dart index 02e25d3902..d97d9f55b6 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/extension.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/extension.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'circle_layer.dart'; import 'map.dart'; import 'marker_layer.dart'; +import 'overlay_image_layer.dart'; import 'polygon_layer.dart'; import 'polyline_layer.dart'; import 'rich_attribution.dart'; @@ -24,6 +25,8 @@ class Extension extends FletExtension { return TileLayerControl(key: key, control: control); case "MarkerLayer": return MarkerLayerControl(key: key, control: control); + case "OverlayImageLayer": + return OverlayImageLayerControl(key: key, control: control); case "CircleLayer": return CircleLayerControl(key: key, control: control); case "PolygonLayer": diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart new file mode 100644 index 0000000000..deecc9b266 --- /dev/null +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart @@ -0,0 +1,84 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; + +import 'utils/map.dart'; + +class OverlayImageLayerControl extends StatelessWidget { + final Control control; + + const OverlayImageLayerControl({super.key, required this.control}); + + @override + Widget build(BuildContext context) { + debugPrint("OverlayImageLayerControl build: ${control.id}"); + + final overlayImages = control + .children("overlay_images") + .map((overlayImage) { + overlayImage.notifyParent = true; + final imageProvider = parseImageProvider( + overlayImage.get("src"), + context, + ); + if (imageProvider == null) { + return null; + } + + final opacity = overlayImage.getDouble("opacity", 1.0)!; + final gaplessPlayback = overlayImage.getBool( + "gapless_playback", + false, + )!; + final filterQuality = overlayImage.getFilterQuality( + "filter_quality", + FilterQuality.medium, + )!; + + switch (overlayImage.type) { + case "OverlayImage": + final bounds = parseLatLngBounds(overlayImage.get("bounds")); + if (bounds == null) { + return null; + } + return OverlayImage( + imageProvider: imageProvider, + bounds: bounds, + opacity: opacity, + gaplessPlayback: gaplessPlayback, + filterQuality: filterQuality, + ); + case "RotatedOverlayImage": + final topLeftCorner = parseLatLng( + overlayImage.get("top_left_corner"), + ); + final bottomLeftCorner = parseLatLng( + overlayImage.get("bottom_left_corner"), + ); + final bottomRightCorner = parseLatLng( + overlayImage.get("bottom_right_corner"), + ); + if (topLeftCorner == null || + bottomLeftCorner == null || + bottomRightCorner == null) { + return null; + } + return RotatedOverlayImage( + imageProvider: imageProvider, + topLeftCorner: topLeftCorner, + bottomLeftCorner: bottomLeftCorner, + bottomRightCorner: bottomRightCorner, + opacity: opacity, + gaplessPlayback: gaplessPlayback, + filterQuality: filterQuality, + ); + default: + return null; + } + }) + .nonNulls + .toList(); + + return OverlayImageLayer(overlayImages: overlayImages); + } +} From ea944d0047e742d9c988564b41b9245a9f081aca Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Wed, 15 Apr 2026 16:24:01 +0200 Subject: [PATCH 02/12] docs(flet-map): add overlay image example --- .../controls/map/overlay_images/main.py | 83 +++++++++++++++++++ .../map/overlay_images/pyproject.toml | 26 ++++++ website/docs/controls/map/baseoverlayimage.md | 7 ++ website/docs/controls/map/index.md | 8 +- website/docs/controls/map/overlayimage.md | 7 ++ .../docs/controls/map/overlayimagelayer.md | 7 ++ .../docs/controls/map/rotatedoverlayimage.md | 7 ++ website/sidebars.js | 23 +++++ website/sidebars.yml | 5 ++ 9 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 sdk/python/examples/controls/map/overlay_images/main.py create mode 100644 sdk/python/examples/controls/map/overlay_images/pyproject.toml create mode 100644 website/docs/controls/map/baseoverlayimage.md create mode 100644 website/docs/controls/map/overlayimage.md create mode 100644 website/docs/controls/map/overlayimagelayer.md create mode 100644 website/docs/controls/map/rotatedoverlayimage.md diff --git a/sdk/python/examples/controls/map/overlay_images/main.py b/sdk/python/examples/controls/map/overlay_images/main.py new file mode 100644 index 0000000000..4028326449 --- /dev/null +++ b/sdk/python/examples/controls/map/overlay_images/main.py @@ -0,0 +1,83 @@ +import flet as ft +import flet_map as ftm + +IMAGE_URL = ( + "https://images.pexels.com/photos/231009/pexels-photo-231009.jpeg" + "?auto=compress&cs=tinysrgb&dpr=2&h=300&w=600" +) + + +def label(text: str, color: ft.ColorValue) -> ft.Container: + return ft.Container( + width=32, + height=32, + bgcolor=color, + border_radius=16, + alignment=ft.Alignment.CENTER, + content=ft.Text(text, color=ft.Colors.WHITE, weight=ft.FontWeight.BOLD), + ) + + +def main(page: ft.Page): + page.appbar = ft.AppBar(title="Overlay Images") + page.add( + ft.SafeArea( + expand=True, + content=ftm.Map( + expand=True, + initial_center=ftm.MapLatitudeLongitude(51.5, -0.09), + initial_zoom=6, + layers=[ + ftm.TileLayer( + url_template="https://tile.openstreetmap.org/{z}/{x}/{y}.png", + user_agent_package_name="Flet Overlay Images Example", + ), + ftm.OverlayImageLayer( + overlay_images=[ + ftm.OverlayImage( + src=IMAGE_URL, + bounds=ftm.MapLatitudeLongitudeBounds( + corner_1=ftm.MapLatitudeLongitude(51.5, -0.09), + corner_2=ftm.MapLatitudeLongitude(48.8566, 2.3522), + ), + opacity=0.8, + ), + ftm.RotatedOverlayImage( + src=IMAGE_URL, + top_left_corner=ftm.MapLatitudeLongitude( + 53.377, -2.999 + ), + bottom_left_corner=ftm.MapLatitudeLongitude( + 52.503, -1.868 + ), + bottom_right_corner=ftm.MapLatitudeLongitude( + 53.475, 0.275 + ), + opacity=0.8, + ), + ] + ), + ftm.MarkerLayer( + markers=[ + ftm.Marker( + content=label("TL", ft.Colors.RED_ACCENT), + coordinates=ftm.MapLatitudeLongitude(53.377, -2.999), + ), + ftm.Marker( + content=label("BL", ft.Colors.RED_ACCENT), + coordinates=ftm.MapLatitudeLongitude(52.503, -1.868), + ), + ftm.Marker( + content=label("BR", ft.Colors.RED_ACCENT), + coordinates=ftm.MapLatitudeLongitude(53.475, 0.275), + ), + ] + ), + ], + ), + ) + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/map/overlay_images/pyproject.toml b/sdk/python/examples/controls/map/overlay_images/pyproject.toml new file mode 100644 index 0000000000..d9efab3e2e --- /dev/null +++ b/sdk/python/examples/controls/map/overlay_images/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "map-overlay-images" +version = "1.0.0" +description = "Places rectangular and rotated image overlays on top of an interactive map." +requires-python = ">=3.10" +keywords = ["map", "overlay", "image", "rotated overlay"] +authors = [{ name = "Flet team", email = "hello@flet.dev" }] +dependencies = ["flet", "flet-map"] + +[dependency-groups] +dev = ["flet-cli", "flet-desktop", "flet-web"] + +[tool.flet.gallery] +categories = ["Extensions/Map"] + +[tool.flet.metadata] +title = "Overlay Images" +controls = ["SafeArea", "Map", "TileLayer", "OverlayImageLayer", "OverlayImage", "RotatedOverlayImage", "MarkerLayer", "Marker", "Container", "Text", "AppBar"] +layout_pattern = "single-panel" +complexity = "basic" +features = ["rectangular image overlay", "rotated image overlay", "corner markers"] + +[tool.flet] +org = "dev.flet" +company = "Flet" +copyright = "Copyright (C) 2023-2026 by Flet" diff --git a/website/docs/controls/map/baseoverlayimage.md b/website/docs/controls/map/baseoverlayimage.md new file mode 100644 index 0000000000..59c11bf3ad --- /dev/null +++ b/website/docs/controls/map/baseoverlayimage.md @@ -0,0 +1,7 @@ +--- +title: "BaseOverlayImage" +--- + +import {ClassAll} from '@site/src/components/crocodocs'; + + diff --git a/website/docs/controls/map/index.md b/website/docs/controls/map/index.md index 5d75fefea6..d35e3d49fc 100644 --- a/website/docs/controls/map/index.md +++ b/website/docs/controls/map/index.md @@ -65,11 +65,15 @@ More details [here](tilelayer.md). +### Overlay Images + + + ## Reference - [`Map`](mapcontrol.md) -- Layers: [`TileLayer`](tilelayer.md), [`MarkerLayer`](markerlayer.md), [`CircleLayer`](circlelayer.md), [`PolygonLayer`](polygonlayer.md), [`PolylineLayer`](polylinelayer.md) -- Markers and overlays: [`Marker`](marker.md), [`CircleMarker`](circlemarker.md), [`PolygonMarker`](polygonmarker.md), [`PolylineMarker`](polylinemarker.md) +- Layers: [`TileLayer`](tilelayer.md), [`MarkerLayer`](markerlayer.md), [`OverlayImageLayer`](overlayimagelayer.md), [`CircleLayer`](circlelayer.md), [`PolygonLayer`](polygonlayer.md), [`PolylineLayer`](polylinelayer.md) +- Markers and overlays: [`Marker`](marker.md), [`CircleMarker`](circlemarker.md), [`PolygonMarker`](polygonmarker.md), [`PolylineMarker`](polylinemarker.md), [`OverlayImage`](overlayimage.md), [`RotatedOverlayImage`](rotatedoverlayimage.md) - Attributions: [`SimpleAttribution`](simpleattribution.md), [`RichAttribution`](richattribution.md), [`SourceAttribution`](sourceattribution.md) See the [types](types/attributionalignment.md) section for additional configuration helpers. diff --git a/website/docs/controls/map/overlayimage.md b/website/docs/controls/map/overlayimage.md new file mode 100644 index 0000000000..b093df3ca5 --- /dev/null +++ b/website/docs/controls/map/overlayimage.md @@ -0,0 +1,7 @@ +--- +title: "OverlayImage" +--- + +import {ClassAll} from '@site/src/components/crocodocs'; + + diff --git a/website/docs/controls/map/overlayimagelayer.md b/website/docs/controls/map/overlayimagelayer.md new file mode 100644 index 0000000000..eb6fe7290c --- /dev/null +++ b/website/docs/controls/map/overlayimagelayer.md @@ -0,0 +1,7 @@ +--- +title: "OverlayImageLayer" +--- + +import {ClassAll} from '@site/src/components/crocodocs'; + + diff --git a/website/docs/controls/map/rotatedoverlayimage.md b/website/docs/controls/map/rotatedoverlayimage.md new file mode 100644 index 0000000000..2855133904 --- /dev/null +++ b/website/docs/controls/map/rotatedoverlayimage.md @@ -0,0 +1,7 @@ +--- +title: "RotatedOverlayImage" +--- + +import {ClassAll} from '@site/src/components/crocodocs'; + + diff --git a/website/sidebars.js b/website/sidebars.js index f0e887ab7d..67ef5c7735 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -973,6 +973,10 @@ module.exports = { "type": "doc", "id": "controls/map/markerlayer" }, + { + "type": "doc", + "id": "controls/map/overlayimagelayer" + }, { "type": "doc", "id": "controls/map/circlelayer" @@ -1010,6 +1014,25 @@ module.exports = { } ] }, + { + "type": "category", + "label": "Overlays", + "collapsed": true, + "items": [ + { + "type": "doc", + "id": "controls/map/baseoverlayimage" + }, + { + "type": "doc", + "id": "controls/map/overlayimage" + }, + { + "type": "doc", + "id": "controls/map/rotatedoverlayimage" + } + ] + }, { "type": "category", "label": "Attributions", diff --git a/website/sidebars.yml b/website/sidebars.yml index 5abf04ebd5..6577fde4e2 100644 --- a/website/sidebars.yml +++ b/website/sidebars.yml @@ -198,6 +198,7 @@ docs: - controls/map/maplayer.md - controls/map/tilelayer.md - controls/map/markerlayer.md + - controls/map/overlayimagelayer.md - controls/map/circlelayer.md - controls/map/polygonlayer.md - controls/map/polylinelayer.md @@ -206,6 +207,10 @@ docs: - controls/map/circlemarker.md - controls/map/polygonmarker.md - controls/map/polylinemarker.md + Overlays: + - controls/map/baseoverlayimage.md + - controls/map/overlayimage.md + - controls/map/rotatedoverlayimage.md Attributions: - controls/map/sourceattribution.md - controls/map/simpleattribution.md From 360923f31fc3992d7526a999558b1fa60286e0e1 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 16 Apr 2026 12:51:10 +0200 Subject: [PATCH 03/12] fix(flet-map): align map control inheritance --- .../flet-map/src/flet_map/circle_layer.py | 2 +- .../flet-map/src/flet_map/marker_layer.py | 2 +- .../src/flet_map/overlay_image_layer.py | 2 +- .../flet-map/src/flet_map/polygon_layer.py | 2 +- .../flet-map/src/flet_map/polyline_layer.py | 2 +- .../flet_map/lib/src/circle_layer.dart | 2 +- .../flet_map/lib/src/marker_layer.dart | 11 ++++--- .../flet_map/lib/src/overlay_image_layer.dart | 5 ++- .../flet_map/lib/src/polygon_layer.dart | 19 +++++++----- .../flet_map/lib/src/polyline_layer.dart | 15 +++++---- .../flet_map/lib/src/rich_attribution.dart | 31 ++++++++++--------- .../flet_map/lib/src/simple_attribution.dart | 15 +++++---- 12 files changed, 63 insertions(+), 45 deletions(-) diff --git a/sdk/python/packages/flet-map/src/flet_map/circle_layer.py b/sdk/python/packages/flet-map/src/flet_map/circle_layer.py index 4ec7bd9b10..023298b1dc 100644 --- a/sdk/python/packages/flet-map/src/flet_map/circle_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/circle_layer.py @@ -8,7 +8,7 @@ @ft.control("CircleMarker") -class CircleMarker(ft.Control): +class CircleMarker(ft.BaseControl): """ A circular marker displayed on the Map at the specified location through the :class:`~flet_map.CircleLayer`. diff --git a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py index f45031b225..29bb2cf177 100644 --- a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py @@ -9,7 +9,7 @@ @ft.control("Marker") -class Marker(ft.Control): +class Marker(ft.BaseControl): """ A marker displayed on the Map at the specified location through the :class:`~flet_map.MarkerLayer`. diff --git a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py index 06149db66e..3fcd2d9f2a 100644 --- a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py @@ -13,7 +13,7 @@ @ft.control("BaseOverlayImage", kw_only=True) -class BaseOverlayImage(ft.Control): +class BaseOverlayImage(ft.BaseControl): """ Abstract class for image overlays displayed through :class:`~flet_map.OverlayImageLayer`. diff --git a/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py b/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py index 2caf97c1d2..49db7b832b 100644 --- a/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py @@ -8,7 +8,7 @@ @ft.control("PolygonMarker") -class PolygonMarker(ft.Control): +class PolygonMarker(ft.BaseControl): """ A marker for the :class:`~flet_map.PolygonLayer`. """ diff --git a/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py b/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py index 5d552e79ad..aa8d81a56d 100644 --- a/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py @@ -9,7 +9,7 @@ @ft.control("PolylineMarker") -class PolylineMarker(ft.Control): +class PolylineMarker(ft.BaseControl): """ A marker for the :class:`~flet_map.PolylineLayer`. """ diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart index be2fbfd009..5e24a76e30 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart @@ -29,6 +29,6 @@ class CircleLayerControl extends StatelessWidget with FletStoreMixin { radius: circle.getDouble("radius", 10)!); }).toList(); - return CircleLayer(circles: circles); + return BaseControl(control: control, child: CircleLayer(circles: circles)); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart index cc8c19dafb..65f28a503a 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart @@ -30,10 +30,13 @@ class MarkerLayerControl extends StatelessWidget with FletStoreMixin { }); }).toList(); - return AnimatedMarkerLayer( - markers: markers, - rotate: control.getBool("rotate", false)!, - alignment: control.getAlignment("alignment", Alignment.center)!, + return BaseControl( + control: control, + child: AnimatedMarkerLayer( + markers: markers, + rotate: control.getBool("rotate", false)!, + alignment: control.getAlignment("alignment", Alignment.center)!, + ), ); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart index deecc9b266..81a3097107 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart @@ -79,6 +79,9 @@ class OverlayImageLayerControl extends StatelessWidget { .nonNulls .toList(); - return OverlayImageLayer(overlayImages: overlayImages); + return BaseControl( + control: control, + child: OverlayImageLayer(overlayImages: overlayImages), + ); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart index cb8eb9c069..f4ae41240f 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart @@ -36,14 +36,17 @@ class PolygonLayerControl extends StatelessWidget with FletStoreMixin { .toList()); }).toList(); - return PolygonLayer( - polygons: polygons, - polygonCulling: control.getBool("polygon_culling", true)!, - polygonLabels: control.getBool("polygon_labels", true)!, - drawLabelsLast: control.getBool("draw_labels_last", false)!, - simplificationTolerance: - control.getDouble("simplification_tolerance", 0.3)!, - useAltRendering: control.getBool("use_alternative_rendering", false)!, + return BaseControl( + control: control, + child: PolygonLayer( + polygons: polygons, + polygonCulling: control.getBool("polygon_culling", true)!, + polygonLabels: control.getBool("polygon_labels", true)!, + drawLabelsLast: control.getBool("draw_labels_last", false)!, + simplificationTolerance: + control.getDouble("simplification_tolerance", 0.3)!, + useAltRendering: control.getBool("use_alternative_rendering", false)!, + ), ); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart index c604c31a12..94fa31f408 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart @@ -47,12 +47,15 @@ class PolylineLayerControl extends StatelessWidget with FletStoreMixin { .toList()); }).toList(); - return PolylineLayer( - polylines: polylines, - cullingMargin: control.getDouble("culling_margin", 10.0)!, - minimumHitbox: control.getDouble("min_hittable_radius", 10.0)!, - simplificationTolerance: - control.getDouble("simplification_tolerance", 0.3)!, + return BaseControl( + control: control, + child: PolylineLayer( + polylines: polylines, + cullingMargin: control.getDouble("culling_margin", 10.0)!, + minimumHitbox: control.getDouble("min_hittable_radius", 10.0)!, + simplificationTolerance: + control.getDouble("simplification_tolerance", 0.3)!, + ), ); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart index 18bd4be9e8..08630607ae 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart @@ -44,19 +44,22 @@ class _RichAttributionControlState extends State .nonNulls .toList(); - return RichAttributionWidget( - attributions: attributions, - permanentHeight: widget.control.getDouble("permanent_height", 24.0)!, - popupBackgroundColor: widget.control.getColor( - "popup_bgcolor", context, Theme.of(context).colorScheme.surface), - showFlutterMapAttribution: - widget.control.getBool("show_flutter_map_attribution", true)!, - alignment: parseAttributionAlignment( - widget.control.getString("alignment"), - AttributionAlignment.bottomRight)!, - popupBorderRadius: - widget.control.getBorderRadius("popup_border_radius"), - popupInitialDisplayDuration: widget.control - .getDuration("popup_initial_display_duration", Duration.zero)!); + return BaseControl( + control: widget.control, + child: RichAttributionWidget( + attributions: attributions, + permanentHeight: widget.control.getDouble("permanent_height", 24.0)!, + popupBackgroundColor: widget.control.getColor( + "popup_bgcolor", context, Theme.of(context).colorScheme.surface), + showFlutterMapAttribution: + widget.control.getBool("show_flutter_map_attribution", true)!, + alignment: parseAttributionAlignment( + widget.control.getString("alignment"), + AttributionAlignment.bottomRight)!, + popupBorderRadius: + widget.control.getBorderRadius("popup_border_radius"), + popupInitialDisplayDuration: widget.control + .getDuration("popup_initial_display_duration", Duration.zero)!), + ); } } diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/simple_attribution.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/simple_attribution.dart index 3153cbeedd..844601ded1 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/simple_attribution.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/simple_attribution.dart @@ -12,12 +12,15 @@ class SimpleAttributionControl extends StatelessWidget { debugPrint("SimpleAttributionControl build: ${control.id}"); var text = control.buildTextOrWidget("text"); - return SimpleAttributionWidget( - source: text is Text ? text : const Text("Placeholder Text"), - onTap: () => control.triggerEvent("click"), - backgroundColor: control.getColor( - "bgcolor", context, Theme.of(context).colorScheme.surface)!, - alignment: control.getAlignment("alignment", Alignment.bottomRight)!, + return BaseControl( + control: control, + child: SimpleAttributionWidget( + source: text is Text ? text : const Text("Placeholder Text"), + onTap: () => control.triggerEvent("click"), + backgroundColor: control.getColor( + "bgcolor", context, Theme.of(context).colorScheme.surface)!, + alignment: control.getAlignment("alignment", Alignment.bottomRight)!, + ), ); } } From 3459358e5b3fc93d55befaf79323fb554276872b Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 16 Apr 2026 13:00:45 +0200 Subject: [PATCH 04/12] fix(flet-map): use validation rule for overlay opacity --- .../src/flet_map/overlay_image_layer.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py index 3fcd2d9f2a..fc38e4080d 100644 --- a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py @@ -1,6 +1,7 @@ -from typing import Union +from typing import Annotated, Union import flet as ft +from flet.utils.validation import V from flet_map.map_layer import MapLayer from flet_map.types import MapLatitudeLongitude, MapLatitudeLongitudeBounds @@ -34,12 +35,15 @@ class BaseOverlayImage(ft.BaseControl): - Raw bytes. """ - opacity: ft.Number = 1.0 + opacity: Annotated[ + ft.Number, + V.between(0.0, 1.0), + ] = 1.0 """ The opacity used to paint the image. Raises: - ValueError: If its value is not between `0.0` and `1.0` inclusive. + ValueError: If it is not between `0.0` and `1.0`, inclusive. """ gapless_playback: bool = False @@ -53,13 +57,6 @@ class BaseOverlayImage(ft.BaseControl): The rendering quality of the image. """ - def before_update(self): - super().before_update() - if not (0.0 <= self.opacity <= 1.0): - raise ValueError( - f"opacity must be between 0.0 and 1.0 inclusive, got {self.opacity}" - ) - @ft.control("OverlayImage", kw_only=True) class OverlayImage(BaseOverlayImage): @@ -103,7 +100,7 @@ class OverlayImageLayer(MapLayer): """ A layer to display image overlays. - Note: + Tip: Place this layer after every non-translucent layer that should appear below it. Layers rendered after this one may cover its overlay images. """ From 717d6776c37ce0164d0f32eeab62eec673430da7 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 16 Apr 2026 13:01:56 +0200 Subject: [PATCH 05/12] chore: clarify flet validation skill triggers --- .agents/skills/flet-validation/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/flet-validation/SKILL.md b/.agents/skills/flet-validation/SKILL.md index b86eb07456..2a2854f587 100644 --- a/.agents/skills/flet-validation/SKILL.md +++ b/.agents/skills/flet-validation/SKILL.md @@ -1,6 +1,6 @@ --- name: flet-validation -description: Use when adding or changing validation for Python controls (dataclasses) in sdk/python/packages/, including Annotated/V rules, __validation_rules__, and property Raises docstrings. +description: Use whenever editing validation for Python controls in sdk/python/packages/: adding/changing constrained properties, `Raises: ValueError` docstrings, `before_update()` checks, `raise ValueError`, Annotated/V rules, or __validation_rules__. --- ## When To Use From c4689d14381490e4bb360e1581d43922cfd8bab7 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 16 Apr 2026 13:11:05 +0200 Subject: [PATCH 06/12] update changelog --- client/pubspec.lock | 18 ++--- .../controls/map/overlay_images/main.py | 40 +--------- .../map/overlay_images/pyproject.toml | 4 +- sdk/python/packages/flet-map/CHANGELOG.md | 2 +- .../src/flet_map/overlay_image_layer.py | 2 +- .../flet_map/lib/src/circle_layer.dart | 2 +- .../flet_map/lib/src/marker_layer.dart | 2 +- .../flet_map/lib/src/overlay_image_layer.dart | 40 ++++------ .../flet_map/lib/src/polygon_layer.dart | 6 +- .../flet_map/lib/src/polyline_layer.dart | 10 +-- .../flet_map/lib/src/rich_attribution.dart | 5 +- .../flutter/flet_map/lib/src/tile_layer.dart | 13 ++-- .../lib/src/utils/attribution_alignment.dart | 7 ++ .../flutter/flet_map/lib/src/utils/map.dart | 78 +++++++++++++++++-- website/docs/controls/map/overlayimage.md | 9 ++- .../docs/controls/map/overlayimagelayer.md | 9 ++- .../docs/controls/map/rotatedoverlayimage.md | 9 ++- 17 files changed, 145 insertions(+), 111 deletions(-) diff --git a/client/pubspec.lock b/client/pubspec.lock index 6292be3ed7..f865fa37eb 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -359,7 +359,7 @@ packages: path: "../packages/flet" relative: true source: path - version: "0.82.2" + version: "0.85.0" flet_ads: dependency: "direct main" description: @@ -911,18 +911,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" media_kit: dependency: transitive description: @@ -1628,10 +1628,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.10" torch_light: dependency: transitive description: diff --git a/sdk/python/examples/controls/map/overlay_images/main.py b/sdk/python/examples/controls/map/overlay_images/main.py index 4028326449..7118545981 100644 --- a/sdk/python/examples/controls/map/overlay_images/main.py +++ b/sdk/python/examples/controls/map/overlay_images/main.py @@ -1,25 +1,10 @@ import flet as ft import flet_map as ftm -IMAGE_URL = ( - "https://images.pexels.com/photos/231009/pexels-photo-231009.jpeg" - "?auto=compress&cs=tinysrgb&dpr=2&h=300&w=600" -) - - -def label(text: str, color: ft.ColorValue) -> ft.Container: - return ft.Container( - width=32, - height=32, - bgcolor=color, - border_radius=16, - alignment=ft.Alignment.CENTER, - content=ft.Text(text, color=ft.Colors.WHITE, weight=ft.FontWeight.BOLD), - ) +base64_image = "iVBORw0KGgoAAAANSUhEUgAAABkAAAAgCAYAAADnnNMGAAAACXBIWXMAAAORAAADkQFnq8zdAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAA6dJREFUSImllltoHFUYx3/fzOzm0lt23ZrQ1AQbtBehNpvQohgkBYVo410RwQctNE3Sh0IfiiBoIAjqi6TYrKnFy4O3oiiRavDJFi3mXomIBmOxNZe63ay52GR3Zj4f2sTEzmx3m//TYf7/c35zvgPnO6KqrESXqpq3muocAikv6m+/zytj3ejik1VN21G31YA9CgJ6xC+bMyQZPVCuarciPAMYC99V6Vw5pLbFSibHmlVoRVj9P3cmPBM8tSJI/M6mzabpfoAQ9fIF7WK4bd5vvuFnLGgy2vi0abg94A0AcJGvMq3hDxGRyar9r4F+iLAm0yIiRk8m37tctS1WsrIhhrI30+Srmg+J87OXUf3lWGS1q89dC6ltsSanxk4Aj2QBABii96300g87P/rtlrWr8l+vyDMfdlXSyyEikqxsiOUAQJCBhfHdXRfCq1LSsSlcWG+KBAGStvvrMkgiuv8lUc2mREukPwLUfHG+uTQv8Eown7VL3XlbBxYhf1c17hbVF3MDwA9bts280TnaU1YYqPby07aeFlUlHt27wSQ4CLo+F8AvoTCvHmyKF+ZbEb/M77P2LgvAwmrTHAHflN3KZxVbMC2jMFNOpgPnrMSOhvvFkMezXdwV4ePbtvHtxnJAMQ0j4JtVnO+eLb5oiSlt5HDbv7t1O90lpYCCCKbhfzW5kAIwUAazR0BlfII8Ow0I6uoVmI9MyAMwbMs8CExmDbk4zgu931MyO4OI4KrYflkRjOoTI+uM9d1vjotwKPu9QMk/sxzuO8POiVFcdZ1M2YBVsMEAKOqLvaPIe7mACuw0z/80SMH58SMplxlfiDhVi7dw2pltRhjKBQTQdrSja2KKTfE551NHuaZ0QVPvWYQUn31/Vm2nDvgjF4grVJx6suSvrvrSJ/6cSW2Oz9mf264uNrB806xZ1k/CZ49dUKgDEtlCROX2hfHpx8pGuuo3PpqYulw8fjndOp1yhgtNKRevJ1FyR2Ola+jXAjdnwTkZ6o896GdWdxDw7IxFg+0DpmXchTKSBWQnIuJn9u4j7dt+13UfHXEkXQOcuQ4kMhVtqsgUyPiQiPQfHw1NB2sRjmXKuTg1NwwBYLhtPtQX26eqTwGXPDOqvmcC4Hnwfrrad94GrVsOYTqUTkQY+iTlNe/6O1miSP/x0VB/+wMIDwHn/vtV1iQC4Xv95uUEWVCoL9Y5Z+gdovoyMHUFJHv88jmVy0vTuw7cZNv2YaA61Bfb7ZX5F8SaUv2xwZevAAAAAElFTkSuQmCC" # noqa: E501 def main(page: ft.Page): - page.appbar = ft.AppBar(title="Overlay Images") page.add( ft.SafeArea( expand=True, @@ -29,13 +14,12 @@ def main(page: ft.Page): initial_zoom=6, layers=[ ftm.TileLayer( - url_template="https://tile.openstreetmap.org/{z}/{x}/{y}.png", - user_agent_package_name="Flet Overlay Images Example", + url_template="https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png" ), ftm.OverlayImageLayer( overlay_images=[ ftm.OverlayImage( - src=IMAGE_URL, + src=base64_image, bounds=ftm.MapLatitudeLongitudeBounds( corner_1=ftm.MapLatitudeLongitude(51.5, -0.09), corner_2=ftm.MapLatitudeLongitude(48.8566, 2.3522), @@ -43,7 +27,7 @@ def main(page: ft.Page): opacity=0.8, ), ftm.RotatedOverlayImage( - src=IMAGE_URL, + src=base64_image, top_left_corner=ftm.MapLatitudeLongitude( 53.377, -2.999 ), @@ -57,22 +41,6 @@ def main(page: ft.Page): ), ] ), - ftm.MarkerLayer( - markers=[ - ftm.Marker( - content=label("TL", ft.Colors.RED_ACCENT), - coordinates=ftm.MapLatitudeLongitude(53.377, -2.999), - ), - ftm.Marker( - content=label("BL", ft.Colors.RED_ACCENT), - coordinates=ftm.MapLatitudeLongitude(52.503, -1.868), - ), - ftm.Marker( - content=label("BR", ft.Colors.RED_ACCENT), - coordinates=ftm.MapLatitudeLongitude(53.475, 0.275), - ), - ] - ), ], ), ) diff --git a/sdk/python/examples/controls/map/overlay_images/pyproject.toml b/sdk/python/examples/controls/map/overlay_images/pyproject.toml index d9efab3e2e..547ea55171 100644 --- a/sdk/python/examples/controls/map/overlay_images/pyproject.toml +++ b/sdk/python/examples/controls/map/overlay_images/pyproject.toml @@ -15,10 +15,10 @@ categories = ["Extensions/Map"] [tool.flet.metadata] title = "Overlay Images" -controls = ["SafeArea", "Map", "TileLayer", "OverlayImageLayer", "OverlayImage", "RotatedOverlayImage", "MarkerLayer", "Marker", "Container", "Text", "AppBar"] +controls = ["SafeArea", "Map", "TileLayer", "OverlayImageLayer", "OverlayImage", "RotatedOverlayImage"] layout_pattern = "single-panel" complexity = "basic" -features = ["rectangular image overlay", "rotated image overlay", "corner markers"] +features = ["rectangular image overlay", "rotated image overlay"] [tool.flet] org = "dev.flet" diff --git a/sdk/python/packages/flet-map/CHANGELOG.md b/sdk/python/packages/flet-map/CHANGELOG.md index 02955ed250..c913a0ba56 100644 --- a/sdk/python/packages/flet-map/CHANGELOG.md +++ b/sdk/python/packages/flet-map/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added -- Added `OverlayImageLayer`, `OverlayImage`, and `RotatedOverlayImage`. +- Added image overlay support for maps with `OverlayImageLayer`, `OverlayImage`, and `RotatedOverlayImage` ([#6319](https://github.com/flet-dev/flet/issues/6319)) by @ndonkoHenri. ## 0.81.1 diff --git a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py index fc38e4080d..c8f05b807b 100644 --- a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py @@ -40,7 +40,7 @@ class BaseOverlayImage(ft.BaseControl): V.between(0.0, 1.0), ] = 1.0 """ - The opacity used to paint the image. + The opacity in which the image should get rendered on the map. Raises: ValueError: If it is not between `0.0` and `1.0`, inclusive. diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart index 5e24a76e30..86c4c8e778 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/circle_layer.dart @@ -20,7 +20,7 @@ class CircleLayerControl extends StatelessWidget with FletStoreMixin { .map((circle) { circle.notifyParent = true; return CircleMarker( - point: parseLatLng(circle.get("coordinates"))!, + point: circle.getLatLng("coordinates")!, color: circle.getColor("color", context, const Color(0xFF00FF00))!, borderColor: circle.getColor( "border_color", context, const Color(0xFFFFFF00))!, diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart index 65f28a503a..53563af9c9 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/marker_layer.dart @@ -19,7 +19,7 @@ class MarkerLayerControl extends StatelessWidget with FletStoreMixin { .map((marker) { marker.notifyParent = true; return AnimatedMarker( - point: parseLatLng(marker.get("coordinates"))!, + point: marker.getLatLng("coordinates")!, rotate: marker.getBool("rotate"), height: marker.getDouble("height", 30.0)!, width: marker.getDouble("width", 30.0)!, diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart index 81a3097107..305766ca3f 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/overlay_image_layer.dart @@ -17,30 +17,20 @@ class OverlayImageLayerControl extends StatelessWidget { .children("overlay_images") .map((overlayImage) { overlayImage.notifyParent = true; - final imageProvider = parseImageProvider( - overlayImage.get("src"), - context, - ); - if (imageProvider == null) { - return null; - } + + final imageProvider = overlayImage.getImageProvider("src", context); + if (imageProvider == null) return null; final opacity = overlayImage.getDouble("opacity", 1.0)!; - final gaplessPlayback = overlayImage.getBool( - "gapless_playback", - false, - )!; + final gaplessPlayback = + overlayImage.getBool("gapless_playback", false)!; final filterQuality = overlayImage.getFilterQuality( - "filter_quality", - FilterQuality.medium, - )!; + "filter_quality", FilterQuality.medium)!; switch (overlayImage.type) { case "OverlayImage": - final bounds = parseLatLngBounds(overlayImage.get("bounds")); - if (bounds == null) { - return null; - } + final bounds = overlayImage.getLatLngBounds("bounds"); + if (bounds == null) return null; return OverlayImage( imageProvider: imageProvider, bounds: bounds, @@ -49,15 +39,11 @@ class OverlayImageLayerControl extends StatelessWidget { filterQuality: filterQuality, ); case "RotatedOverlayImage": - final topLeftCorner = parseLatLng( - overlayImage.get("top_left_corner"), - ); - final bottomLeftCorner = parseLatLng( - overlayImage.get("bottom_left_corner"), - ); - final bottomRightCorner = parseLatLng( - overlayImage.get("bottom_right_corner"), - ); + final topLeftCorner = overlayImage.getLatLng("top_left_corner"); + final bottomLeftCorner = + overlayImage.getLatLng("bottom_left_corner"); + final bottomRightCorner = + overlayImage.getLatLng("bottom_right_corner"); if (topLeftCorner == null || bottomLeftCorner == null || bottomRightCorner == null) { diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart index f4ae41240f..af588fcdca 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polygon_layer.dart @@ -29,11 +29,7 @@ class PolygonLayerControl extends StatelessWidget with FletStoreMixin { "label_text_style", Theme.of(context), const TextStyle())!, strokeCap: polygon.getStrokeCap("stroke_cap", StrokeCap.round)!, strokeJoin: polygon.getStrokeJoin("stroke_join", StrokeJoin.round)!, - points: polygon - .get("coordinates", [])! - .map((c) => parseLatLng(c)) - .nonNulls - .toList()); + points: polygon.getLatLngList("coordinates")); }).toList(); return BaseControl( diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart index 94fa31f408..e4c8bfd32e 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/polyline_layer.dart @@ -23,8 +23,8 @@ class PolylineLayerControl extends StatelessWidget with FletStoreMixin { borderColor: polyline.getColor("border_color", context, Colors.yellow)!, color: polyline.getColor("color", context, Colors.yellow)!, - pattern: parseStrokePattern( - polyline.get("stroke_pattern"), const StrokePattern.solid())!, + pattern: polyline.getStrokePattern( + "stroke_pattern", const StrokePattern.solid())!, strokeCap: polyline.getStrokeCap("stroke_cap", StrokeCap.round)!, strokeJoin: polyline.getStrokeJoin("stroke_join", StrokeJoin.round)!, strokeWidth: polyline.getDouble("stroke_width", 1.0)!, @@ -40,11 +40,7 @@ class PolylineLayerControl extends StatelessWidget with FletStoreMixin { .map((e) => parseColor(e, Theme.of(context))) .nonNulls .toList(), - points: polyline - .get("coordinates", [])! - .map((c) => parseLatLng(c)) - .nonNulls - .toList()); + points: polyline.getLatLngList("coordinates")); }).toList(); return BaseControl( diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart index 08630607ae..344a56d58a 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/rich_attribution.dart @@ -53,9 +53,8 @@ class _RichAttributionControlState extends State "popup_bgcolor", context, Theme.of(context).colorScheme.surface), showFlutterMapAttribution: widget.control.getBool("show_flutter_map_attribution", true)!, - alignment: parseAttributionAlignment( - widget.control.getString("alignment"), - AttributionAlignment.bottomRight)!, + alignment: widget.control.getAttributionAlignment( + "alignment", AttributionAlignment.bottomRight)!, popupBorderRadius: widget.control.getBorderRadius("popup_border_radius"), popupInitialDisplayDuration: widget.control diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/tile_layer.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/tile_layer.dart index f9552b5eee..dac12189a3 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/tile_layer.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/tile_layer.dart @@ -34,8 +34,8 @@ class TileLayerControl extends StatelessWidget { control.get("subdomains")?.map((e) => e.toString()).toList() ?? const ['a', 'b', 'c'], tileProvider: NetworkTileProvider(), - tileDisplay: parseTileDisplay( - control.get("display_mode"), const TileDisplay.fadeIn())!, + tileDisplay: + control.getTileDisplay("display_mode", const TileDisplay.fadeIn())!, tileDimension: control.getInt("tile_size", 256)!, userAgentPackageName: control.getString("user_agent_package_name", 'unknown')!, @@ -46,13 +46,12 @@ class TileLayerControl extends StatelessWidget { keepBuffer: control.getInt("keep_buffer", 2)!, panBuffer: control.getInt("pan_buffer", 1)!, tms: control.getBool("enable_tms", false)!, - tileBounds: parseLatLngBounds(control.get("tile_bounds")), + tileBounds: control.getLatLngBounds("tile_bounds"), retinaMode: control.getBool("enable_retina_mode"), maxZoom: control.getDouble("max_zoom", double.infinity)!, minZoom: control.getDouble("min_zoom", 0)!, - evictErrorTileStrategy: parseEvictErrorTileStrategy( - control.getString("evict_error_tile_strategy"), - EvictErrorTileStrategy.none)!, + evictErrorTileStrategy: control.getEvictErrorTileStrategy( + "evict_error_tile_strategy", EvictErrorTileStrategy.none)!, errorImage: errorImage, errorTileCallback: (TileImage t, Object o, StackTrace? s) { control.triggerEvent("image_error", o.toString()); @@ -61,7 +60,7 @@ class TileLayerControl extends StatelessWidget { .get("additional_options") ?.map((k, v) => MapEntry(k.toString(), v.toString())) ?? const {}, - wmsOptions: parseWMSTileLayerOptions(control.get("wms_configuration")), + wmsOptions: control.getWMSTileLayerOptions("wms_configuration"), ); return BaseControl(control: control, child: tileLayer); diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/attribution_alignment.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/attribution_alignment.dart index 2bb3f29490..ac8b3c0bba 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/attribution_alignment.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/attribution_alignment.dart @@ -5,3 +5,10 @@ AttributionAlignment? parseAttributionAlignment(String? value, [AttributionAlignment? defaultValue]) { return parseEnum(AttributionAlignment.values, value, defaultValue); } + +extension AttributionAlignmentControlExtension on Control { + AttributionAlignment? getAttributionAlignment(String propertyName, + [AttributionAlignment? defaultValue]) { + return parseAttributionAlignment(getString(propertyName), defaultValue); + } +} diff --git a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/map.dart b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/map.dart index 34d0666811..7caaf54814 100644 --- a/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/map.dart +++ b/sdk/python/packages/flet-map/src/flutter/flet_map/lib/src/utils/map.dart @@ -153,10 +153,12 @@ KeyboardOptions? parseKeyboardOptions(dynamic value, parseDouble(value["rotate_leap_velocity_multiplier"], 3)!, zoomLeapVelocityMultiplier: parseDouble(value["zoom_leap_velocity_multiplier"], 3)!, - performLeapTriggerDuration: - parseDuration(value["perform_leap_trigger_duration"], const Duration(milliseconds: 100))!, - animationCurveReverseDuration: - parseDuration(value["animation_curve_reverse_duration"], const Duration(milliseconds: 600))!); + performLeapTriggerDuration: parseDuration( + value["perform_leap_trigger_duration"], + const Duration(milliseconds: 100))!, + animationCurveReverseDuration: parseDuration( + value["animation_curve_reverse_duration"], + const Duration(milliseconds: 600))!); } CursorRotationBehaviour? parseCursorRotationBehaviour(String? value, @@ -257,16 +259,16 @@ MapOptions? parseConfiguration(Control control, BuildContext context, [MapOptions? defaultValue]) { return MapOptions( initialCenter: - parseLatLng(control.get("initial_center"), const LatLng(50.5, 30.51))!, - interactionOptions: parseInteractionOptions( - control.get("interaction_configuration"), const InteractionOptions())!, + control.getLatLng("initial_center", const LatLng(50.5, 30.51))!, + interactionOptions: control.getInteractionOptions( + "interaction_configuration", const InteractionOptions())!, backgroundColor: control.getColor("bgcolor", context, Colors.grey[300])!, initialRotation: control.getDouble("initial_rotation", 0.0)!, initialZoom: control.getDouble("initial_zoom", 13.0)!, keepAlive: control.getBool("keep_alive", false)!, maxZoom: control.getDouble("max_zoom"), minZoom: control.getDouble("min_zoom"), - initialCameraFit: parseCameraFit(control.get("initial_camera_fit")), + initialCameraFit: control.getCameraFit("initial_camera_fit"), onPointerHover: control.hasEventHandler("hover") ? (PointerHoverEvent e, LatLng latlng) { control.triggerEvent("hover", { @@ -393,3 +395,63 @@ extension MapEventExtension on MapEvent { }; } } + +extension MapParsersControlExtension on Control { + LatLng? getLatLng(String propertyName, [LatLng? defaultValue]) { + return parseLatLng(get(propertyName), defaultValue); + } + + LatLngBounds? getLatLngBounds(String propertyName, + [LatLngBounds? defaultValue]) { + return parseLatLngBounds(get(propertyName), defaultValue); + } + + List getLatLngList(String propertyName, + [List defaultValue = const []]) { + return get(propertyName) + ?.map((c) => parseLatLng(c)) + .nonNulls + .toList() ?? + defaultValue; + } + + StrokePattern? getStrokePattern(String propertyName, + [StrokePattern? defaultValue]) { + return parseStrokePattern(get(propertyName), defaultValue); + } + + TileDisplay? getTileDisplay(String propertyName, + [TileDisplay? defaultValue]) { + return parseTileDisplay(get(propertyName), defaultValue); + } + + InteractionOptions? getInteractionOptions(String propertyName, + [InteractionOptions? defaultValue]) { + return parseInteractionOptions(get(propertyName), defaultValue); + } + + CameraFit? getCameraFit(String propertyName, [CameraFit? defaultValue]) { + return parseCameraFit(get(propertyName), defaultValue); + } + + KeyboardOptions? getKeyboardOptions(String propertyName, + [KeyboardOptions? defaultValue]) { + return parseKeyboardOptions(get(propertyName), defaultValue); + } + + CursorKeyboardRotationOptions? getCursorKeyboardRotationOptions( + String propertyName, + [CursorKeyboardRotationOptions? defaultValue]) { + return parseCursorKeyboardRotationOptions(get(propertyName), defaultValue); + } + + EvictErrorTileStrategy? getEvictErrorTileStrategy(String propertyName, + [EvictErrorTileStrategy? defaultValue]) { + return parseEvictErrorTileStrategy(getString(propertyName), defaultValue); + } + + WMSTileLayerOptions? getWMSTileLayerOptions(String propertyName, + [WMSTileLayerOptions? defaultValue]) { + return parseWMSTileLayerOptions(get(propertyName), defaultValue); + } +} diff --git a/website/docs/controls/map/overlayimage.md b/website/docs/controls/map/overlayimage.md index b093df3ca5..0060d0bdf5 100644 --- a/website/docs/controls/map/overlayimage.md +++ b/website/docs/controls/map/overlayimage.md @@ -1,7 +1,14 @@ --- +examples: "controls/map" title: "OverlayImage" --- -import {ClassAll} from '@site/src/components/crocodocs'; +import {ClassAll, CodeExample} from '@site/src/components/crocodocs'; + +## Examples + +### Basic example + + diff --git a/website/docs/controls/map/overlayimagelayer.md b/website/docs/controls/map/overlayimagelayer.md index eb6fe7290c..227a24ae76 100644 --- a/website/docs/controls/map/overlayimagelayer.md +++ b/website/docs/controls/map/overlayimagelayer.md @@ -1,7 +1,14 @@ --- +examples: "controls/map" title: "OverlayImageLayer" --- -import {ClassAll} from '@site/src/components/crocodocs'; +import {ClassAll, CodeExample} from '@site/src/components/crocodocs'; + +## Examples + +### Basic example + + diff --git a/website/docs/controls/map/rotatedoverlayimage.md b/website/docs/controls/map/rotatedoverlayimage.md index 2855133904..971cb1885a 100644 --- a/website/docs/controls/map/rotatedoverlayimage.md +++ b/website/docs/controls/map/rotatedoverlayimage.md @@ -1,7 +1,14 @@ --- +examples: "controls/map" title: "RotatedOverlayImage" --- -import {ClassAll} from '@site/src/components/crocodocs'; +import {ClassAll, CodeExample} from '@site/src/components/crocodocs'; + +## Examples + +### Basic example + + From db25575328f5b149b2dc944429507e4009f1467d Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Thu, 16 Apr 2026 14:05:09 +0200 Subject: [PATCH 07/12] docs(changelog): update with additional PR reference --- sdk/python/packages/flet-map/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/packages/flet-map/CHANGELOG.md b/sdk/python/packages/flet-map/CHANGELOG.md index c913a0ba56..da6616d915 100644 --- a/sdk/python/packages/flet-map/CHANGELOG.md +++ b/sdk/python/packages/flet-map/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added -- Added image overlay support for maps with `OverlayImageLayer`, `OverlayImage`, and `RotatedOverlayImage` ([#6319](https://github.com/flet-dev/flet/issues/6319)) by @ndonkoHenri. +- Added image overlay support for maps with `OverlayImageLayer`, `OverlayImage`, and `RotatedOverlayImage` ([#6319](https://github.com/flet-dev/flet/issues/6319), [#6421](https://github.com/flet-dev/flet/pull/6421)) by @ndonkoHenri. ## 0.81.1 From 8264d5bc2fa831fc5f0819e504b0dd5d9eb9c058 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 24 Apr 2026 12:46:43 +0200 Subject: [PATCH 08/12] add `visible` attribute to `BaseControl` inheriters --- sdk/python/packages/flet-map/src/flet_map/circle_layer.py | 5 +++++ sdk/python/packages/flet-map/src/flet_map/marker_layer.py | 5 +++++ .../packages/flet-map/src/flet_map/overlay_image_layer.py | 5 +++++ sdk/python/packages/flet-map/src/flet_map/polygon_layer.py | 5 +++++ sdk/python/packages/flet-map/src/flet_map/polyline_layer.py | 5 +++++ website/docs/controls/map/index.md | 6 +++--- 6 files changed, 28 insertions(+), 3 deletions(-) diff --git a/sdk/python/packages/flet-map/src/flet_map/circle_layer.py b/sdk/python/packages/flet-map/src/flet_map/circle_layer.py index 023298b1dc..1f2f4d4f86 100644 --- a/sdk/python/packages/flet-map/src/flet_map/circle_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/circle_layer.py @@ -45,6 +45,11 @@ class CircleMarker(ft.BaseControl): Whether the :attr:`radius` should use the unit meters. """ + visible: bool = True + """ + Whether this marker is rendered on the map. + """ + def before_update(self): super().before_update() if self.border_stroke_width < 0: diff --git a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py index 29bb2cf177..4a10bc7bc3 100644 --- a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py @@ -68,6 +68,11 @@ class Marker(ft.BaseControl): Defaults to the value of the parent :attr:`flet_map.MarkerLayer.alignment`. """ + visible: bool = True + """ + Whether this marker is rendered on the map. + """ + def before_update(self): super().before_update() if not self.content.visible: diff --git a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py index c8f05b807b..a06c854afd 100644 --- a/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/overlay_image_layer.py @@ -57,6 +57,11 @@ class BaseOverlayImage(ft.BaseControl): The rendering quality of the image. """ + visible: bool = True + """ + Whether this overlay image is rendered on the map. + """ + @ft.control("OverlayImage", kw_only=True) class OverlayImage(BaseOverlayImage): diff --git a/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py b/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py index 49db7b832b..f0d371b36c 100644 --- a/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/polygon_layer.py @@ -74,6 +74,11 @@ class PolygonMarker(ft.BaseControl): Style to use for line segment joins. """ + visible: bool = True + """ + Whether this marker is rendered on the map. + """ + def before_update(self): super().before_update() if self.border_stroke_width < 0: diff --git a/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py b/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py index aa8d81a56d..eb469317e5 100644 --- a/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/polyline_layer.py @@ -76,6 +76,11 @@ class PolylineMarker(ft.BaseControl): Style to use for line segment joins. """ + visible: bool = True + """ + Whether this marker is rendered on the map. + """ + def before_update(self): super().before_update() if self.border_stroke_width < 0: diff --git a/website/docs/controls/map/index.md b/website/docs/controls/map/index.md index d35e3d49fc..c73172b6ec 100644 --- a/website/docs/controls/map/index.md +++ b/website/docs/controls/map/index.md @@ -72,8 +72,8 @@ More details [here](tilelayer.md). ## Reference - [`Map`](mapcontrol.md) -- Layers: [`TileLayer`](tilelayer.md), [`MarkerLayer`](markerlayer.md), [`OverlayImageLayer`](overlayimagelayer.md), [`CircleLayer`](circlelayer.md), [`PolygonLayer`](polygonlayer.md), [`PolylineLayer`](polylinelayer.md) -- Markers and overlays: [`Marker`](marker.md), [`CircleMarker`](circlemarker.md), [`PolygonMarker`](polygonmarker.md), [`PolylineMarker`](polylinemarker.md), [`OverlayImage`](overlayimage.md), [`RotatedOverlayImage`](rotatedoverlayimage.md) -- Attributions: [`SimpleAttribution`](simpleattribution.md), [`RichAttribution`](richattribution.md), [`SourceAttribution`](sourceattribution.md) + - Layers: [`TileLayer`](tilelayer.md), [`MarkerLayer`](markerlayer.md), [`OverlayImageLayer`](overlayimagelayer.md), [`CircleLayer`](circlelayer.md), [`PolygonLayer`](polygonlayer.md), [`PolylineLayer`](polylinelayer.md) + - Markers and overlays: [`Marker`](marker.md), [`CircleMarker`](circlemarker.md), [`PolygonMarker`](polygonmarker.md), [`PolylineMarker`](polylinemarker.md), [`OverlayImage`](overlayimage.md), [`RotatedOverlayImage`](rotatedoverlayimage.md) + - Attributions: [`SimpleAttribution`](simpleattribution.md), [`RichAttribution`](richattribution.md), [`SourceAttribution`](sourceattribution.md) See the [types](types/attributionalignment.md) section for additional configuration helpers. From 571fac113b949e020192ecaa1746788d986cdb79 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 24 Apr 2026 13:12:12 +0200 Subject: [PATCH 09/12] crocodocs: improve rendering of signatures --- .../src/components/crocodocs/ClassBlock.js | 145 +++++++++++++++++- 1 file changed, 143 insertions(+), 2 deletions(-) diff --git a/website/src/components/crocodocs/ClassBlock.js b/website/src/components/crocodocs/ClassBlock.js index 337f8583ff..c7487862c2 100644 --- a/website/src/components/crocodocs/ClassBlock.js +++ b/website/src/components/crocodocs/ClassBlock.js @@ -35,6 +35,146 @@ function stripImplicitSelf(signatureText) { .replace(/\(\s*self(?:\s*:\s*[^,)]+)?\s*\)/g, "()"); } +/** + * Split a comma-separated list while respecting nested (), [], {}, and quoted strings. + */ +function splitTopLevelCommaList(text) { + const items = []; + let start = 0; + let parenDepth = 0; + let bracketDepth = 0; + let braceDepth = 0; + let quote = null; + + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + const previous = index > 0 ? text[index - 1] : ""; + + if (quote) { + if (char === quote && previous !== "\\") { + quote = null; + } + continue; + } + + if (char === "'" || char === '"') { + quote = char; + continue; + } + + if (char === "(") { + parenDepth += 1; + continue; + } + if (char === ")") { + parenDepth = Math.max(0, parenDepth - 1); + continue; + } + if (char === "[") { + bracketDepth += 1; + continue; + } + if (char === "]") { + bracketDepth = Math.max(0, bracketDepth - 1); + continue; + } + if (char === "{") { + braceDepth += 1; + continue; + } + if (char === "}") { + braceDepth = Math.max(0, braceDepth - 1); + continue; + } + + // Only split on commas that belong to the outer parameter list. + if ( + char === "," && + parenDepth === 0 && + bracketDepth === 0 && + braceDepth === 0 + ) { + items.push(text.slice(start, index).trim()); + start = index + 1; + } + } + + const tail = text.slice(start).trim(); + if (tail) { + items.push(tail); + } + + return items; +} + +/** + * Format long call signatures into a stacked parameter layout for readability. + * This changes display only; the copied signature text remains unchanged. + */ +function formatMethodSignatureForDisplay(signatureText) { + if (!signatureText) { + return signatureText; + } + + const openIndex = signatureText.indexOf("("); + if (openIndex === -1) { + return signatureText; + } + + let closeIndex = -1; + let depth = 0; + let quote = null; + + // Find the closing parenthesis that matches the outer call signature. + for (let index = openIndex; index < signatureText.length; index += 1) { + const char = signatureText[index]; + const previous = index > 0 ? signatureText[index - 1] : ""; + + if (quote) { + if (char === quote && previous !== "\\") { + quote = null; + } + continue; + } + + if (char === "'" || char === '"') { + quote = char; + continue; + } + + if (char === "(") { + depth += 1; + continue; + } + if (char === ")") { + depth -= 1; + if (depth === 0) { + closeIndex = index; + break; + } + } + } + + if (closeIndex === -1) { + return signatureText; + } + + const prefix = signatureText.slice(0, openIndex); + const paramsText = signatureText.slice(openIndex + 1, closeIndex).trim(); + const suffix = signatureText.slice(closeIndex + 1); + + if (!paramsText) { + return signatureText; + } + + const params = splitTopLevelCommaList(paramsText); + if (params.length < 3 && signatureText.length < 88) { + return signatureText; + } + + return `${prefix}(\n ${params.join(",\n ")}\n)${suffix}`; +} + /** * Renders a Docusaurus-aware heading element at the given level with an anchor id. */ @@ -272,11 +412,12 @@ function renderAttribute(item, classSymbol, docId) { */ function renderMethod(item, classSymbol, docId) { const signatureText = stripImplicitSelf(item.signature ?? item.name); + const displaySignatureText = formatMethodSignatureForDisplay(signatureText); return (
{renderMemberHeading(item, classSymbol, "method")} - - {renderCodeExpression(signatureText, {classSymbol, docId})} + + {renderCodeExpression(displaySignatureText, {classSymbol, docId})} {renderDocstringSections(item.docstring_sections, {classSymbol, docId}) ?? renderDocstring(item.docstring, {classSymbol, docId})} From 4bcea951ec71f094ce0c7c12fcbdc6651f6874b6 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 24 Apr 2026 13:14:26 +0200 Subject: [PATCH 10/12] crocodocs: enhance method signature display to include return type --- website/src/components/crocodocs/ClassBlock.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/src/components/crocodocs/ClassBlock.js b/website/src/components/crocodocs/ClassBlock.js index c7487862c2..4fa5738294 100644 --- a/website/src/components/crocodocs/ClassBlock.js +++ b/website/src/components/crocodocs/ClassBlock.js @@ -412,7 +412,11 @@ function renderAttribute(item, classSymbol, docId) { */ function renderMethod(item, classSymbol, docId) { const signatureText = stripImplicitSelf(item.signature ?? item.name); - const displaySignatureText = formatMethodSignatureForDisplay(signatureText); + const typedSignatureText = + item.return_type && !signatureText.includes("->") + ? `${signatureText} -> ${item.return_type}` + : signatureText; + const displaySignatureText = formatMethodSignatureForDisplay(typedSignatureText); return (
{renderMemberHeading(item, classSymbol, "method")} From 6d6dc977608db8edb535d0d495a8ef422c7e4cfb Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 24 Apr 2026 13:19:54 +0200 Subject: [PATCH 11/12] fix #6419: improve handling of colors with 3 and 4 digit forms --- packages/flet/lib/src/utils/colors.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/flet/lib/src/utils/colors.dart b/packages/flet/lib/src/utils/colors.dart index 72d4273f7f..60376af2bb 100644 --- a/packages/flet/lib/src/utils/colors.dart +++ b/packages/flet/lib/src/utils/colors.dart @@ -253,12 +253,20 @@ extension HexColor on Color { return null; } - /// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#". - static Color _fromHex(String hexString) { + /// String is in CSS hex format: "rgb", "argb", "rrggbb" or "aarrggbb" + /// (the leading "#" or "0x" is stripped by the caller). + /// Shorthand 3/4-digit forms are expanded by doubling each character + /// (e.g. "c00" -> "cc0000", "fc00" -> "ffcc0000"). + static Color? _fromHex(String hexString) { + if (hexString.length == 3 || hexString.length == 4) { + hexString = hexString.split('').map((c) => '$c$c').join(''); + } + if (hexString.length != 6 && hexString.length != 8) return null; final buffer = StringBuffer(); if (hexString.length == 6) buffer.write('ff'); buffer.write(hexString); - return Color(int.parse(buffer.toString(), radix: 16)); + final value = int.tryParse(buffer.toString(), radix: 16); + return value == null ? null : Color(value); } /// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`). From 62cf467fcbf7b09548e279f62c575bff49249a65 Mon Sep 17 00:00:00 2001 From: ndonkoHenri Date: Fri, 24 Apr 2026 14:16:08 +0200 Subject: [PATCH 12/12] Revert crocodocs changes --- .../packages/flet-map/src/flet_map/map.py | 5 +- .../src/components/crocodocs/ClassBlock.js | 149 +----------------- 2 files changed, 5 insertions(+), 149 deletions(-) diff --git a/sdk/python/packages/flet-map/src/flet_map/map.py b/sdk/python/packages/flet-map/src/flet_map/map.py index 7a420d2c22..4ec95b00c4 100644 --- a/sdk/python/packages/flet-map/src/flet_map/map.py +++ b/sdk/python/packages/flet-map/src/flet_map/map.py @@ -45,7 +45,8 @@ class Map(ft.LayoutControl): initial_zoom: ft.Number = 13.0 """ The zoom when the map is first loaded. - If initial_camera_fit is defined this has no effect. + + If :attr:`initial_camera_fit` is non-`None` this has no effect. """ interaction_configuration: InteractionConfiguration = field( @@ -379,7 +380,7 @@ async def get_camera(self) -> Camera: Gets the current camera snapshot of the map. Returns: - Current :class:`~flet_map.Camera` state. + The current camera state. """ camera = await self._invoke_method("get_camera") return from_dict(Camera, camera) diff --git a/website/src/components/crocodocs/ClassBlock.js b/website/src/components/crocodocs/ClassBlock.js index 46daf737e5..21686a1783 100644 --- a/website/src/components/crocodocs/ClassBlock.js +++ b/website/src/components/crocodocs/ClassBlock.js @@ -35,146 +35,6 @@ function stripImplicitSelf(signatureText) { .replace(/\(\s*self(?:\s*:\s*[^,)]+)?\s*\)/g, "()"); } -/** - * Split a comma-separated list while respecting nested (), [], {}, and quoted strings. - */ -function splitTopLevelCommaList(text) { - const items = []; - let start = 0; - let parenDepth = 0; - let bracketDepth = 0; - let braceDepth = 0; - let quote = null; - - for (let index = 0; index < text.length; index += 1) { - const char = text[index]; - const previous = index > 0 ? text[index - 1] : ""; - - if (quote) { - if (char === quote && previous !== "\\") { - quote = null; - } - continue; - } - - if (char === "'" || char === '"') { - quote = char; - continue; - } - - if (char === "(") { - parenDepth += 1; - continue; - } - if (char === ")") { - parenDepth = Math.max(0, parenDepth - 1); - continue; - } - if (char === "[") { - bracketDepth += 1; - continue; - } - if (char === "]") { - bracketDepth = Math.max(0, bracketDepth - 1); - continue; - } - if (char === "{") { - braceDepth += 1; - continue; - } - if (char === "}") { - braceDepth = Math.max(0, braceDepth - 1); - continue; - } - - // Only split on commas that belong to the outer parameter list. - if ( - char === "," && - parenDepth === 0 && - bracketDepth === 0 && - braceDepth === 0 - ) { - items.push(text.slice(start, index).trim()); - start = index + 1; - } - } - - const tail = text.slice(start).trim(); - if (tail) { - items.push(tail); - } - - return items; -} - -/** - * Format long call signatures into a stacked parameter layout for readability. - * This changes display only; the copied signature text remains unchanged. - */ -function formatMethodSignatureForDisplay(signatureText) { - if (!signatureText) { - return signatureText; - } - - const openIndex = signatureText.indexOf("("); - if (openIndex === -1) { - return signatureText; - } - - let closeIndex = -1; - let depth = 0; - let quote = null; - - // Find the closing parenthesis that matches the outer call signature. - for (let index = openIndex; index < signatureText.length; index += 1) { - const char = signatureText[index]; - const previous = index > 0 ? signatureText[index - 1] : ""; - - if (quote) { - if (char === quote && previous !== "\\") { - quote = null; - } - continue; - } - - if (char === "'" || char === '"') { - quote = char; - continue; - } - - if (char === "(") { - depth += 1; - continue; - } - if (char === ")") { - depth -= 1; - if (depth === 0) { - closeIndex = index; - break; - } - } - } - - if (closeIndex === -1) { - return signatureText; - } - - const prefix = signatureText.slice(0, openIndex); - const paramsText = signatureText.slice(openIndex + 1, closeIndex).trim(); - const suffix = signatureText.slice(closeIndex + 1); - - if (!paramsText) { - return signatureText; - } - - const params = splitTopLevelCommaList(paramsText); - if (params.length < 3 && signatureText.length < 88) { - return signatureText; - } - - return `${prefix}(\n ${params.join(",\n ")}\n)${suffix}`; -} - /** * Renders a Docusaurus-aware heading element at the given level with an anchor id. */ @@ -416,16 +276,11 @@ function renderAttribute(item, classSymbol, docId) { */ function renderMethod(item, classSymbol, docId) { const signatureText = stripImplicitSelf(item.signature ?? item.name); - const typedSignatureText = - item.return_type && !signatureText.includes("->") - ? `${signatureText} -> ${item.return_type}` - : signatureText; - const displaySignatureText = formatMethodSignatureForDisplay(typedSignatureText); return (
{renderMemberHeading(item, classSymbol, "method")} - - {renderCodeExpression(displaySignatureText, {classSymbol, docId})} + + {renderCodeExpression(signatureText, {classSymbol, docId})} {renderDocstringSections(item.docstring_sections, {classSymbol, docId}) ?? renderDocstring(item.docstring, {classSymbol, docId})}