flet 0.86.0.dev2__tar.gz → 0.86.2__tar.gz
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.
- {flet-0.86.0.dev2 → flet-0.86.2}/PKG-INFO +9 -9
- {flet-0.86.0.dev2 → flet-0.86.2}/pyproject.toml +9 -9
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/__init__.py +3 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/base_control.py +1 -2
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/context.py +25 -5
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control_event.py +1 -2
- flet-0.86.2/src/flet/controls/core/raw_image.py +488 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/search_bar.py +14 -1
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/page.py +55 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/types.py +11 -9
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/connection.py +5 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/flet_dart_bridge_server.py +2 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/flet_socket_server.py +12 -3
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/pyodide_connection.py +2 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/session.py +27 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/version.py +2 -2
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/PKG-INFO +9 -9
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/SOURCES.txt +2 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/requires.txt +8 -8
- flet-0.86.2/tests/test_raw_image_pixels.py +133 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/README.md +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/setup.cfg +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/app.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/authorization.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/authorization_service.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/group.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/oauth_provider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/oauth_token.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/auth0_oauth_provider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/azure_oauth_provider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/github_oauth_provider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/google_oauth_provider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/user.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/canvas/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/cli.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component_decorator.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component_owned.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/hook.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_callback.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_context.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_dialog.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_effect.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_memo.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_ref.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_state.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/memo.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/observable.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/public_utils.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/router.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/utils.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/adaptive_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/alignment.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/animation.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/base_page.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/blur.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/border.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/border_radius.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/box.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/buttons.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/colors.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control_state.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/animated_switcher.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/autofill_group.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/arc.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/canvas.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/circle.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/color.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/fill.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/image.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/line.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/oval.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/path.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/points.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/rect.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/shadow.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/shape.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/text.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/column.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/dismissible.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/drag_target.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/draggable.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/flet_app.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/gesture_detector.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/grid_view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/hero.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/icon.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/image.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/interactive_viewer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/keyboard_listener.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/list_view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/markdown.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/merge_semantics.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/page_view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/pagelet.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/placeholder.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/reorderable_drag_handle.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/responsive_row.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/rotated_box.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/row.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/safe_area.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/screenshot.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/semantics.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/shader_mask.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/shimmer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/stack.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/text.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/text_span.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/transparent_pointer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/window.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/window_drag_area.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_action_sheet.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_action_sheet_action.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_activity_indicator.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_alert_dialog.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_app_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_bottom_sheet.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_checkbox.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_colors.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_context_menu.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_context_menu_action.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_date_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_dialog_action.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_filled_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.json +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.pyi +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_list_tile.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_navigation_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_radio.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_segmented_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_slider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_sliding_segmented_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_switch.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_textfield.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_timer_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_tinted_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/device_info.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/dialog_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/duration.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/embed_json_encoder.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/events.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/exceptions.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/geometry.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/gradients.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/icon_data.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/id_counter.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/keys.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/layout_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/margin.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/alert_dialog.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/app_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/auto_complete.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/badge.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/banner.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/bottom_app_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/bottom_sheet.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/card.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/checkbox.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/chip.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/circle_avatar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/container.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/context_menu.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/datatable.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/date_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/date_range_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/divider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/dropdown.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/dropdownm2.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/elevated_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/expansion_panel.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/expansion_tile.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/filled_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/filled_tonal_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/floating_action_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/form_field_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icon_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.json +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.pyi +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/list_tile.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/menu_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/menu_item_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_drawer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_rail.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/outlined_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/popup_menu_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/progress_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/progress_ring.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/radio.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/radio_group.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/range_slider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/reorderable_list_view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/segmented_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/selection_area.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/slider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/snack_bar.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/submenu_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/switch.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/tabs.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/text_button.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/textfield.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/time_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/tooltip.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/vertical_divider.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/multi_view.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/object_patch.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/padding.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/painting.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/query_string.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/ref.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/scrollable_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/accelerometer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/barometer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/battery.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/browser_context_menu.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/clipboard.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/connectivity.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/file_picker.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/gyroscope.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/haptic_feedback.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/magnetometer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/screen_brightness.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/semantics_service.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/sensor_error_event.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/service.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/shake_detector.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/share.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/shared_preferences.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/storage_paths.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/url_launcher.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/user_accelerometer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/wakelock.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/template_route.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/text_style.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/theme.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/transform.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/value_types.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/data_channel.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/fastapi/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/protocol.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/session_store.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/pubsub_client.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/pubsub_hub.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/py.typed +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pytest_plugin.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/security/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/finder.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/flet_test_app.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/remote_tester.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/tester.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/__init__.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/browser.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/classproperty.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/deprecated.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/deprecated_enum.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/files.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/from_dict.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/griffe_deprecations.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/hashing.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/json_utils.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/locks.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/network.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/object_model.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/once.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/pip.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/platform_utils.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/slugify.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/strings.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/typing_utils.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/validation.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/vector.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/dependency_links.txt +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/entry_points.txt +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/top_level.txt +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_auth_lazy_imports.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_base_control.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_colors.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_diff.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_effects.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_renderer.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_dataclasses.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_datetime.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_deprecated.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_events.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_from_dict.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_griffe_deprecations.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_icons.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_frozen.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_in_place.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_memory_churn.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_performance.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_observable.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_patch_dataclass.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_modal.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_pop.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_recursive.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_session_disconnect_buffering.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_skip_double_update.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_use_dialog.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_validation.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_validation_benchmark.py +0 -0
- {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flet
|
|
3
|
-
Version: 0.86.
|
|
3
|
+
Version: 0.86.2
|
|
4
4
|
Summary: Flet for Python - easily build interactive multi-platform apps in Python
|
|
5
5
|
Author-email: "Appveyor Systems Inc." <hello@flet.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,23 +9,23 @@ Project-URL: Repository, https://github.com/flet-dev/flet
|
|
|
9
9
|
Project-URL: Documentation, https://flet.dev/docs/
|
|
10
10
|
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: flet-cli==0.86.
|
|
13
|
-
Requires-Dist: flet-web==0.86.
|
|
12
|
+
Requires-Dist: flet-cli==0.86.2; extra == "cli"
|
|
13
|
+
Requires-Dist: flet-web==0.86.2; extra == "web"
|
|
14
14
|
Requires-Dist: oauthlib>=3.2.2; platform_system != "Emscripten"
|
|
15
15
|
Requires-Dist: httpx>=0.28.1; platform_system != "Emscripten"
|
|
16
16
|
Requires-Dist: repath>=0.9.0
|
|
17
17
|
Requires-Dist: msgpack>=1.1.0
|
|
18
18
|
Requires-Dist: typing-extensions; python_version < "3.11"
|
|
19
19
|
Provides-Extra: all
|
|
20
|
-
Requires-Dist: flet-cli==0.86.
|
|
21
|
-
Requires-Dist: flet-web==0.86.
|
|
22
|
-
Requires-Dist: flet-desktop==0.86.
|
|
20
|
+
Requires-Dist: flet-cli==0.86.2; extra == "all"
|
|
21
|
+
Requires-Dist: flet-web==0.86.2; extra == "all"
|
|
22
|
+
Requires-Dist: flet-desktop==0.86.2; extra == "all"
|
|
23
23
|
Provides-Extra: cli
|
|
24
|
-
Requires-Dist: flet-cli==0.86.
|
|
24
|
+
Requires-Dist: flet-cli==0.86.2; extra == "cli"
|
|
25
25
|
Provides-Extra: desktop
|
|
26
|
-
Requires-Dist: flet-desktop==0.86.
|
|
26
|
+
Requires-Dist: flet-desktop==0.86.2; extra == "desktop"
|
|
27
27
|
Provides-Extra: web
|
|
28
|
-
Requires-Dist: flet-web==0.86.
|
|
28
|
+
Requires-Dist: flet-web==0.86.2; extra == "web"
|
|
29
29
|
Provides-Extra: test
|
|
30
30
|
Requires-Dist: pytest>=7.2.0; extra == "test"
|
|
31
31
|
Requires-Dist: pytest-asyncio>=1.1.0; extra == "test"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flet"
|
|
3
|
-
version = "0.86.
|
|
3
|
+
version = "0.86.2"
|
|
4
4
|
description = "Flet for Python - easily build interactive multi-platform apps in Python"
|
|
5
5
|
authors = [{name = "Appveyor Systems Inc.", email = "hello@flet.dev"}]
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
dependencies = [
|
|
10
|
-
"flet-cli==0.86.
|
|
11
|
-
"flet-web==0.86.
|
|
10
|
+
"flet-cli==0.86.2; extra == 'cli'",
|
|
11
|
+
"flet-web==0.86.2; extra == 'web'",
|
|
12
12
|
"oauthlib >=3.2.2; platform_system != 'Emscripten'",
|
|
13
13
|
"httpx >=0.28.1; platform_system != 'Emscripten'",
|
|
14
14
|
"repath >=0.9.0",
|
|
@@ -23,15 +23,15 @@ Documentation = "https://flet.dev/docs/"
|
|
|
23
23
|
|
|
24
24
|
[project.optional-dependencies]
|
|
25
25
|
all = [
|
|
26
|
-
"flet-cli==0.86.
|
|
27
|
-
"flet-web==0.86.
|
|
28
|
-
"flet-desktop==0.86.
|
|
26
|
+
"flet-cli==0.86.2",
|
|
27
|
+
"flet-web==0.86.2",
|
|
28
|
+
"flet-desktop==0.86.2",
|
|
29
29
|
]
|
|
30
|
-
cli = ["flet-cli==0.86.
|
|
30
|
+
cli = ["flet-cli==0.86.2"]
|
|
31
31
|
desktop = [
|
|
32
|
-
"flet-desktop==0.86.
|
|
32
|
+
"flet-desktop==0.86.2",
|
|
33
33
|
]
|
|
34
|
-
web = ["flet-web==0.86.
|
|
34
|
+
web = ["flet-web==0.86.2"]
|
|
35
35
|
# Dependencies needed to run integration tests written with `flet.testing`
|
|
36
36
|
# (the `flet test` command / the pytest plugin). Pillow/numpy/scikit-image
|
|
37
37
|
# back screenshot and GIF comparison.
|
|
@@ -183,6 +183,7 @@ if TYPE_CHECKING:
|
|
|
183
183
|
from flet.controls.core.page_view import PageView
|
|
184
184
|
from flet.controls.core.pagelet import Pagelet
|
|
185
185
|
from flet.controls.core.placeholder import Placeholder
|
|
186
|
+
from flet.controls.core.raw_image import RawImage
|
|
186
187
|
from flet.controls.core.reorderable_drag_handle import ReorderableDragHandle
|
|
187
188
|
from flet.controls.core.responsive_row import ResponsiveRow
|
|
188
189
|
from flet.controls.core.rotated_box import RotatedBox
|
|
@@ -1070,6 +1071,7 @@ __all__ = [
|
|
|
1070
1071
|
"RadioGroup",
|
|
1071
1072
|
"RadioTheme",
|
|
1072
1073
|
"RangeSlider",
|
|
1074
|
+
"RawImage",
|
|
1073
1075
|
"Rect",
|
|
1074
1076
|
"Ref",
|
|
1075
1077
|
"ReorderableDragHandle",
|
|
@@ -1603,6 +1605,7 @@ _LAZY = {
|
|
|
1603
1605
|
"RadioGroup": "flet.controls.material.radio_group",
|
|
1604
1606
|
"RadioTheme": "flet.controls.theme",
|
|
1605
1607
|
"RangeSlider": "flet.controls.material.range_slider",
|
|
1608
|
+
"RawImage": "flet.controls.core.raw_image",
|
|
1606
1609
|
"Rect": "flet.controls.geometry",
|
|
1607
1610
|
"Ref": "flet.controls.ref",
|
|
1608
1611
|
"ReorderableDragHandle": "flet.controls.core.reorderable_drag_handle",
|
|
@@ -23,7 +23,6 @@ else:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
|
-
from .base_page import BasePage
|
|
27
26
|
from .keys import KeyValue
|
|
28
27
|
from .page import Page
|
|
29
28
|
|
|
@@ -296,7 +295,7 @@ class BaseControl:
|
|
|
296
295
|
return parent_ref() if parent_ref else None
|
|
297
296
|
|
|
298
297
|
@property
|
|
299
|
-
def page(self) -> "
|
|
298
|
+
def page(self) -> "Page":
|
|
300
299
|
"""
|
|
301
300
|
The page to which this control belongs to.
|
|
302
301
|
"""
|
|
@@ -14,7 +14,24 @@ class Context:
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
def __init__(self) -> None:
|
|
17
|
-
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def __current_session(self):
|
|
20
|
+
"""Return the Session bound to the current context, or None.
|
|
21
|
+
|
|
22
|
+
Components mode is tracked per-session rather than on this global
|
|
23
|
+
singleton so that multiple concurrently running apps in one process
|
|
24
|
+
(e.g. an embedded `FletApp` inside a host app) don't clobber each
|
|
25
|
+
other's mode. Resolved via the same page context var used by
|
|
26
|
+
:attr:`page`.
|
|
27
|
+
"""
|
|
28
|
+
page = _context_page.get()
|
|
29
|
+
if page is None:
|
|
30
|
+
return None
|
|
31
|
+
try:
|
|
32
|
+
return page.session
|
|
33
|
+
except RuntimeError:
|
|
34
|
+
return None
|
|
18
35
|
|
|
19
36
|
@property
|
|
20
37
|
def page(self) -> "Page":
|
|
@@ -99,13 +116,15 @@ class Context:
|
|
|
99
116
|
"""
|
|
100
117
|
Enables components mode in the current context.
|
|
101
118
|
"""
|
|
102
|
-
self.
|
|
119
|
+
if (session := self.__current_session()) is not None:
|
|
120
|
+
session.components_mode = True
|
|
103
121
|
|
|
104
122
|
def disable_components_mode(self):
|
|
105
123
|
"""
|
|
106
124
|
Disables components mode in the current context.
|
|
107
125
|
"""
|
|
108
|
-
self.
|
|
126
|
+
if (session := self.__current_session()) is not None:
|
|
127
|
+
session.components_mode = False
|
|
109
128
|
|
|
110
129
|
def is_components_mode(self) -> bool:
|
|
111
130
|
"""
|
|
@@ -114,7 +133,8 @@ class Context:
|
|
|
114
133
|
Returns:
|
|
115
134
|
`True` if in components mode, `False` otherwise.
|
|
116
135
|
"""
|
|
117
|
-
|
|
136
|
+
session = self.__current_session()
|
|
137
|
+
return session.components_mode if session is not None else False
|
|
118
138
|
|
|
119
139
|
def mark_update_called(self):
|
|
120
140
|
"""
|
|
@@ -145,7 +165,7 @@ class Context:
|
|
|
145
165
|
`True` if auto-update is enabled, `False` otherwise.
|
|
146
166
|
"""
|
|
147
167
|
return (
|
|
148
|
-
not self.
|
|
168
|
+
not self.is_components_mode()
|
|
149
169
|
and _update_behavior_context_var.get()._auto_update_enabled
|
|
150
170
|
)
|
|
151
171
|
|
|
@@ -19,7 +19,6 @@ from flet.utils.typing_utils import eval_type
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
from .base_control import BaseControl # noqa
|
|
21
21
|
from .page import Page
|
|
22
|
-
from .base_page import BasePage
|
|
23
22
|
|
|
24
23
|
_BaseControlType = BaseControl
|
|
25
24
|
else:
|
|
@@ -128,7 +127,7 @@ class Event(Generic[EventControlType]):
|
|
|
128
127
|
"""
|
|
129
128
|
|
|
130
129
|
@property
|
|
131
|
-
def page(self) ->
|
|
130
|
+
def page(self) -> "Page":
|
|
132
131
|
"""
|
|
133
132
|
Page that owns the event source control.
|
|
134
133
|
"""
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import io
|
|
4
|
+
from collections import deque
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from flet.controls.base_control import control
|
|
8
|
+
from flet.controls.box import BoxFit, FilterQuality
|
|
9
|
+
from flet.controls.control_event import EventHandler
|
|
10
|
+
from flet.controls.layout_control import LayoutControl
|
|
11
|
+
from flet.controls.types import Number
|
|
12
|
+
from flet.data_channel import DataChannel, DataChannelOpenEvent
|
|
13
|
+
|
|
14
|
+
__all__ = ["RawImage"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@control("RawImage")
|
|
18
|
+
class RawImage(LayoutControl):
|
|
19
|
+
"""
|
|
20
|
+
Displays pixel frames pushed from Python at full bandwidth.
|
|
21
|
+
|
|
22
|
+
Unlike :class:`~flet.Image`, whose `src` travels through the regular
|
|
23
|
+
Flet protocol on every update, `RawImage` streams frames over a
|
|
24
|
+
dedicated :class:`~flet.DataChannel`: bytes skip MsgPack
|
|
25
|
+
encode/decode entirely and, on local transports (desktop app, `flet run`,
|
|
26
|
+
Pyodide), are displayed from raw RGBA pixels without any image
|
|
27
|
+
encoding or decoding. This makes it suitable for animations, generated
|
|
28
|
+
graphics, camera frames and Pillow output at interactive frame rates.
|
|
29
|
+
|
|
30
|
+
Frames are pushed with the awaitable :meth:`render`, :meth:`render_rgba`
|
|
31
|
+
and :meth:`render_encoded` methods. Each call resolves when the client
|
|
32
|
+
has actually displayed the frame, so a plain loop self-paces to display
|
|
33
|
+
speed:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
raw_image = ft.RawImage(width=400, height=300)
|
|
37
|
+
page.add(raw_image)
|
|
38
|
+
|
|
39
|
+
while True:
|
|
40
|
+
await raw_image.render(produce_pil_image())
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Transport behavior is selected automatically:
|
|
44
|
+
|
|
45
|
+
- **Local transports** (`local_data_transport` connections): frames are
|
|
46
|
+
sent as uncompressed premultiplied RGBA8888 and uploaded straight to
|
|
47
|
+
a GPU texture on the client.
|
|
48
|
+
- **Remote transports** (`flet-web` over WebSocket): Pillow and NumPy
|
|
49
|
+
frames are PNG-encoded off the event loop to save bandwidth; raw
|
|
50
|
+
pixel frames for which no straight-alpha source is available are sent
|
|
51
|
+
uncompressed.
|
|
52
|
+
|
|
53
|
+
**Premultiplied alpha.** The raw-pixel path uploads frames directly as
|
|
54
|
+
GPU textures in Flutter's `rgba8888` format, which expects RGB values
|
|
55
|
+
already multiplied by alpha ("premultiplied"). Pillow, numpy code and
|
|
56
|
+
image files normally produce *straight* alpha instead, where color and
|
|
57
|
+
opacity are independent: a half-transparent white pixel is
|
|
58
|
+
`(255, 255, 255, 128)` straight but `(128, 128, 128, 128)`
|
|
59
|
+
premultiplied (each color channel becomes `value * alpha // 255`).
|
|
60
|
+
The `premultiplied` argument of the render methods says whether that
|
|
61
|
+
conversion is already done:
|
|
62
|
+
|
|
63
|
+
- `premultiplied=False` — pixels carry straight alpha; RGB is
|
|
64
|
+
multiplied by alpha before sending. The conversion runs in fast C
|
|
65
|
+
loops and is skipped when the frame turns out to be fully opaque.
|
|
66
|
+
- `premultiplied=True` — skip the conversion. Use it when pixels are
|
|
67
|
+
genuinely premultiplied or, the common case, when the frame is
|
|
68
|
+
**fully opaque**: with every alpha at 255 both forms are identical,
|
|
69
|
+
and declaring it saves an alpha scan per frame in streaming loops.
|
|
70
|
+
|
|
71
|
+
Passing straight-alpha pixels with `premultiplied=True` makes
|
|
72
|
+
semi-transparent areas render too bright; for fully opaque frames the
|
|
73
|
+
flag cannot be wrong either way.
|
|
74
|
+
|
|
75
|
+
The last frame is retained and replayed automatically when the client
|
|
76
|
+
widget remounts (page rebuild, route navigation), mirroring how
|
|
77
|
+
`Image.src` persists.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
fit: Optional[BoxFit] = None
|
|
81
|
+
"""
|
|
82
|
+
Defines how to inscribe the current frame into the space allocated
|
|
83
|
+
during layout.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
filter_quality: FilterQuality = FilterQuality.LOW
|
|
87
|
+
"""
|
|
88
|
+
The rendering quality of the displayed frame.
|
|
89
|
+
|
|
90
|
+
Defaults to `LOW` (bilinear), a good trade-off for streamed frames.
|
|
91
|
+
Use `NONE` for crisp nearest-neighbor scaling of pixel-art-style
|
|
92
|
+
content.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
scale: Number = 1.0
|
|
96
|
+
"""
|
|
97
|
+
How many physical frame pixels correspond to one logical pixel.
|
|
98
|
+
|
|
99
|
+
Only affects the intrinsic size of this control when it is laid out
|
|
100
|
+
without tight constraints: a 800x600 frame with `scale=2` measures
|
|
101
|
+
400x300 logical pixels. Set it to the device pixel ratio when frames
|
|
102
|
+
are rendered at physical resolution.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
ready_timeout: Optional[Number] = 5.0
|
|
106
|
+
"""
|
|
107
|
+
Seconds the `render` methods wait for the client widget to attach its
|
|
108
|
+
data channel before raising `TimeoutError`.
|
|
109
|
+
|
|
110
|
+
Set to `None` to wait indefinitely.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
ack_timeout: Optional[Number] = 10.0
|
|
114
|
+
"""
|
|
115
|
+
Seconds a `render` call waits for the client's frame-applied
|
|
116
|
+
acknowledgment before raising `TimeoutError`.
|
|
117
|
+
|
|
118
|
+
The ack normally arrives within milliseconds of the frame being
|
|
119
|
+
displayed. The timeout is a liveness guard: transports drop frames
|
|
120
|
+
sent while the client is disconnected (closed browser tab, network
|
|
121
|
+
loss), and without it a `while True: await raw_image.render(...)`
|
|
122
|
+
loop would wait for the lost ack forever. Raise it when streaming
|
|
123
|
+
very large frames to slow remote clients, or set to `None` to wait
|
|
124
|
+
indefinitely.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
on_data_channel_open: Optional[EventHandler[DataChannelOpenEvent]] = None
|
|
128
|
+
"""
|
|
129
|
+
Framework hook — Dart fires this when it opens the data channel on
|
|
130
|
+
mount. The default handler captures the channel used by the `render`
|
|
131
|
+
methods; override only to do something extra at attach-time.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def init(self) -> None:
|
|
135
|
+
super().init()
|
|
136
|
+
self._channel: Optional[DataChannel] = None
|
|
137
|
+
# Set once the Dart side has announced its data channel; `render`
|
|
138
|
+
# calls block on it so frames pushed before first mount are held
|
|
139
|
+
# (up to `ready_timeout`) instead of dropped.
|
|
140
|
+
self._ready = asyncio.Event()
|
|
141
|
+
# FIFO of per-frame ack futures. Each send enqueues a future and
|
|
142
|
+
# awaits it; `_on_dart_message` resolves the head when Dart's
|
|
143
|
+
# `[0xFF]` frame-applied ack arrives. The await is the
|
|
144
|
+
# backpressure that paces producer loops to display speed.
|
|
145
|
+
self._pending_acks: deque[asyncio.Future] = deque()
|
|
146
|
+
# Last frame packet sent, replayed when the client widget
|
|
147
|
+
# remounts so the image survives page rebuilds like `Image.src`.
|
|
148
|
+
self._last_packet: Optional[bytes] = None
|
|
149
|
+
if self.on_data_channel_open is None:
|
|
150
|
+
self.on_data_channel_open = self._capture_channel
|
|
151
|
+
|
|
152
|
+
# -- channel plumbing ---------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def _capture_channel(self, e: DataChannelOpenEvent) -> None:
|
|
155
|
+
# Single-channel widget; no need to dispatch on e.channel_name.
|
|
156
|
+
self._channel = self.get_data_channel(e.channel_id)
|
|
157
|
+
self._channel.on_bytes(self._on_dart_message)
|
|
158
|
+
# Acks pending on a previous channel will never arrive; resolve
|
|
159
|
+
# them so old `render` awaits return instead of hanging.
|
|
160
|
+
while self._pending_acks:
|
|
161
|
+
fut = self._pending_acks.popleft()
|
|
162
|
+
if not fut.done():
|
|
163
|
+
fut.set_result(None)
|
|
164
|
+
# Replay the last frame without registering an ack future — its
|
|
165
|
+
# ack finds an empty deque and is ignored.
|
|
166
|
+
if self._last_packet is not None:
|
|
167
|
+
self._channel.send(self._last_packet)
|
|
168
|
+
self._ready.set()
|
|
169
|
+
|
|
170
|
+
def _on_dart_message(self, payload: bytes) -> None:
|
|
171
|
+
# Reverse direction (Dart -> Python) wire format:
|
|
172
|
+
# [0xFF] — frame-applied ack, sent after each frame completes.
|
|
173
|
+
if not payload or payload[0] != 0xFF:
|
|
174
|
+
return
|
|
175
|
+
if self._pending_acks:
|
|
176
|
+
fut = self._pending_acks.popleft()
|
|
177
|
+
if not fut.done():
|
|
178
|
+
fut.set_result(None)
|
|
179
|
+
|
|
180
|
+
async def _wait_until_visible(self) -> None:
|
|
181
|
+
# Park while the tab/window is hidden: the client can't paint, so
|
|
182
|
+
# streaming frames would only build a backlog that floods it on
|
|
183
|
+
# resume. The Dart side also defends against this, but parking the
|
|
184
|
+
# producer saves the wasted CPU/bandwidth of rendering to a hidden
|
|
185
|
+
# client. No-op when the control isn't attached to a page yet.
|
|
186
|
+
try:
|
|
187
|
+
page = self.page
|
|
188
|
+
except RuntimeError:
|
|
189
|
+
return
|
|
190
|
+
await page.wait_until_visible()
|
|
191
|
+
|
|
192
|
+
async def _send_and_wait(self, packet: bytes) -> None:
|
|
193
|
+
await self._wait_until_visible()
|
|
194
|
+
if not self._ready.is_set():
|
|
195
|
+
await asyncio.wait_for(
|
|
196
|
+
self._ready.wait(),
|
|
197
|
+
None if self.ready_timeout is None else float(self.ready_timeout),
|
|
198
|
+
)
|
|
199
|
+
assert self._channel is not None
|
|
200
|
+
loop = asyncio.get_running_loop()
|
|
201
|
+
fut: asyncio.Future = loop.create_future()
|
|
202
|
+
self._pending_acks.append(fut)
|
|
203
|
+
self._channel.send(packet)
|
|
204
|
+
if self.ack_timeout is None:
|
|
205
|
+
await fut
|
|
206
|
+
return
|
|
207
|
+
try:
|
|
208
|
+
await asyncio.wait_for(fut, float(self.ack_timeout))
|
|
209
|
+
except (TimeoutError, asyncio.TimeoutError):
|
|
210
|
+
# Withdraw the abandoned future so a future ack doesn't
|
|
211
|
+
# resolve it instead of the frame it belongs to.
|
|
212
|
+
with contextlib.suppress(ValueError):
|
|
213
|
+
self._pending_acks.remove(fut)
|
|
214
|
+
raise TimeoutError(
|
|
215
|
+
f"frame was not acknowledged within {self.ack_timeout}s — "
|
|
216
|
+
"the client may have disconnected"
|
|
217
|
+
) from None
|
|
218
|
+
|
|
219
|
+
async def _send_frame(self, packet: bytes) -> None:
|
|
220
|
+
self._last_packet = packet
|
|
221
|
+
await self._send_and_wait(packet)
|
|
222
|
+
|
|
223
|
+
def _local_data_transport(self) -> bool:
|
|
224
|
+
try:
|
|
225
|
+
page = self.page
|
|
226
|
+
except RuntimeError: # not added to a page yet
|
|
227
|
+
return False
|
|
228
|
+
return bool(getattr(page.session.connection, "local_data_transport", False))
|
|
229
|
+
|
|
230
|
+
# -- public API ---------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
async def render(self, image: Any, *, premultiplied: bool = False) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Display a Pillow image or a NumPy-style array and wait until the
|
|
235
|
+
client has shown it.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
image: A `PIL.Image.Image` (any mode; converted to RGBA as
|
|
239
|
+
needed), or an object exposing `__array_interface__`
|
|
240
|
+
(e.g. a NumPy array) of shape `(height, width, 4)` or
|
|
241
|
+
`(height, width, 3)` with `uint8` values.
|
|
242
|
+
premultiplied: Set to `True` if RGB values are already
|
|
243
|
+
premultiplied by alpha to skip the premultiplication step.
|
|
244
|
+
|
|
245
|
+
Raises:
|
|
246
|
+
TypeError: If `image` is neither a Pillow image nor an
|
|
247
|
+
array-like object.
|
|
248
|
+
TimeoutError: If the client widget doesn't attach within
|
|
249
|
+
:attr:`ready_timeout`.
|
|
250
|
+
"""
|
|
251
|
+
if hasattr(image, "mode") and hasattr(image, "tobytes"):
|
|
252
|
+
await self._render_pil(image, premultiplied)
|
|
253
|
+
elif hasattr(image, "__array_interface__"):
|
|
254
|
+
await self._render_array(image, premultiplied)
|
|
255
|
+
else:
|
|
256
|
+
raise TypeError(
|
|
257
|
+
"RawImage.render() accepts a PIL.Image.Image or an object "
|
|
258
|
+
f"with __array_interface__, got {type(image).__name__}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
async def render_rgba(
|
|
262
|
+
self,
|
|
263
|
+
width: int,
|
|
264
|
+
height: int,
|
|
265
|
+
pixels: bytes,
|
|
266
|
+
*,
|
|
267
|
+
premultiplied: bool = True,
|
|
268
|
+
) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Display a raw RGBA8888 frame and wait until the client has shown it.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
width: Frame width in physical pixels.
|
|
274
|
+
height: Frame height in physical pixels.
|
|
275
|
+
pixels: Tightly packed RGBA8888 bytes, `width * height * 4`
|
|
276
|
+
long. RGB values must be premultiplied by alpha (fully
|
|
277
|
+
opaque frames are premultiplied by definition).
|
|
278
|
+
premultiplied: Set to `False` if RGB values are straight
|
|
279
|
+
(non-premultiplied); Pillow is then required to
|
|
280
|
+
premultiply them.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
ValueError: If `pixels` has the wrong length.
|
|
284
|
+
TimeoutError: If the client widget doesn't attach within
|
|
285
|
+
:attr:`ready_timeout`.
|
|
286
|
+
"""
|
|
287
|
+
if len(pixels) != width * height * 4:
|
|
288
|
+
raise ValueError(
|
|
289
|
+
f"pixels must be width * height * 4 = {width * height * 4} "
|
|
290
|
+
f"bytes, got {len(pixels)}"
|
|
291
|
+
)
|
|
292
|
+
if not premultiplied and _rgba_is_opaque(pixels):
|
|
293
|
+
premultiplied = True # straight == premultiplied when opaque
|
|
294
|
+
local = self._local_data_transport()
|
|
295
|
+
if not premultiplied:
|
|
296
|
+
# Straight alpha: Pillow is required either to premultiply
|
|
297
|
+
# (local raw upload) or to PNG-encode (remote).
|
|
298
|
+
img = _pil_from_rgba(width, height, pixels)
|
|
299
|
+
if local:
|
|
300
|
+
pixels = _premultiply_pil(img).tobytes()
|
|
301
|
+
else:
|
|
302
|
+
await self._send_frame(
|
|
303
|
+
b"\x01" + await asyncio.to_thread(_pil_to_png_bytes, img)
|
|
304
|
+
)
|
|
305
|
+
return
|
|
306
|
+
elif not local and _rgba_is_opaque(pixels):
|
|
307
|
+
# Opaque frames PNG-encode losslessly (nothing to premultiply);
|
|
308
|
+
# non-opaque premultiplied frames have no straight-alpha source
|
|
309
|
+
# to encode and are sent raw.
|
|
310
|
+
img = _try_pil_from_rgba(width, height, pixels)
|
|
311
|
+
if img is not None:
|
|
312
|
+
await self._send_frame(
|
|
313
|
+
b"\x01" + await asyncio.to_thread(_pil_to_png_bytes, img)
|
|
314
|
+
)
|
|
315
|
+
return
|
|
316
|
+
await self._send_frame(_encode_raw_packet(width, height, pixels))
|
|
317
|
+
|
|
318
|
+
async def render_encoded(self, data: bytes) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Display an encoded image (PNG, JPEG, WebP, ...) and wait until the
|
|
321
|
+
client has shown it.
|
|
322
|
+
|
|
323
|
+
The bytes still travel over the data channel (skipping the Flet
|
|
324
|
+
protocol), but the client decodes them with its image codecs
|
|
325
|
+
instead of a raw pixel upload.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
data: Encoded image bytes.
|
|
329
|
+
|
|
330
|
+
Raises:
|
|
331
|
+
TimeoutError: If the client widget doesn't attach within
|
|
332
|
+
:attr:`ready_timeout`.
|
|
333
|
+
"""
|
|
334
|
+
await self._send_frame(b"\x01" + data)
|
|
335
|
+
|
|
336
|
+
async def clear(self) -> None:
|
|
337
|
+
"""
|
|
338
|
+
Clear the displayed frame.
|
|
339
|
+
"""
|
|
340
|
+
self._last_packet = None
|
|
341
|
+
await self._send_and_wait(b"\x03")
|
|
342
|
+
|
|
343
|
+
# -- input paths ----------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
async def _render_pil(self, image: Any, premultiplied: bool) -> None:
|
|
346
|
+
if not self._local_data_transport():
|
|
347
|
+
# PNG carries straight alpha — encode the original image,
|
|
348
|
+
# never a premultiplied copy. Encoding is CPU-heavy; keep it
|
|
349
|
+
# off the event loop.
|
|
350
|
+
await self._send_frame(
|
|
351
|
+
b"\x01" + await asyncio.to_thread(_pil_to_png_bytes, image)
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
if image.mode != "RGBA":
|
|
355
|
+
image = image.convert("RGBA")
|
|
356
|
+
if not premultiplied:
|
|
357
|
+
image = _premultiply_pil(image)
|
|
358
|
+
width, height = image.size
|
|
359
|
+
await self._send_frame(_encode_raw_packet(width, height, image.tobytes()))
|
|
360
|
+
|
|
361
|
+
async def _render_array(self, array: Any, premultiplied: bool) -> None:
|
|
362
|
+
# An object with __array_interface__ guarantees numpy is importable
|
|
363
|
+
# in practice; import lazily so flet itself never depends on it.
|
|
364
|
+
import numpy as np
|
|
365
|
+
|
|
366
|
+
arr = np.asarray(array)
|
|
367
|
+
if arr.dtype != np.uint8 or arr.ndim != 3 or arr.shape[2] not in (3, 4):
|
|
368
|
+
raise TypeError(
|
|
369
|
+
"array must be uint8 with shape (height, width, 4) or "
|
|
370
|
+
f"(height, width, 3), got {arr.dtype} {arr.shape}"
|
|
371
|
+
)
|
|
372
|
+
if arr.shape[2] == 3:
|
|
373
|
+
rgba = np.empty(arr.shape[:2] + (4,), dtype=np.uint8)
|
|
374
|
+
rgba[:, :, :3] = arr
|
|
375
|
+
rgba[:, :, 3] = 255
|
|
376
|
+
arr = rgba
|
|
377
|
+
premultiplied = True # opaque
|
|
378
|
+
if not self._local_data_transport():
|
|
379
|
+
try:
|
|
380
|
+
from PIL import Image as PILImage
|
|
381
|
+
except ImportError:
|
|
382
|
+
pass
|
|
383
|
+
else:
|
|
384
|
+
if premultiplied and (arr[:, :, 3] != 255).any():
|
|
385
|
+
# No straight-alpha source to encode — send raw.
|
|
386
|
+
await self._send_frame(self._array_packet(np, arr, True))
|
|
387
|
+
return
|
|
388
|
+
img = PILImage.frombuffer(
|
|
389
|
+
"RGBA",
|
|
390
|
+
(arr.shape[1], arr.shape[0]),
|
|
391
|
+
np.ascontiguousarray(arr).tobytes(),
|
|
392
|
+
)
|
|
393
|
+
await self._send_frame(
|
|
394
|
+
b"\x01" + await asyncio.to_thread(_pil_to_png_bytes, img)
|
|
395
|
+
)
|
|
396
|
+
return
|
|
397
|
+
await self._send_frame(self._array_packet(np, arr, premultiplied))
|
|
398
|
+
|
|
399
|
+
@staticmethod
|
|
400
|
+
def _array_packet(np: Any, arr: Any, premultiplied: bool) -> bytes:
|
|
401
|
+
if not premultiplied and (arr[:, :, 3] != 255).any():
|
|
402
|
+
# Flutter's rgba8888 upload assumes premultiplied alpha.
|
|
403
|
+
out = arr.copy()
|
|
404
|
+
a = out[:, :, 3:4].astype(np.uint16)
|
|
405
|
+
out[:, :, :3] = (out[:, :, :3].astype(np.uint16) * a // 255).astype(
|
|
406
|
+
np.uint8
|
|
407
|
+
)
|
|
408
|
+
arr = out
|
|
409
|
+
arr = np.ascontiguousarray(arr)
|
|
410
|
+
return _encode_raw_packet(arr.shape[1], arr.shape[0], arr.tobytes())
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# -- Pillow helpers (Pillow is an optional dependency; import lazily) ---------
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _premultiply_pil(image: Any) -> Any:
|
|
417
|
+
"""
|
|
418
|
+
Premultiplies RGB by alpha in an RGBA Pillow image.
|
|
419
|
+
|
|
420
|
+
Flutter's rgba8888 texture upload assumes premultiplied alpha, while
|
|
421
|
+
Pillow produces straight alpha. All per-pixel work happens in Pillow's
|
|
422
|
+
C loops: an opaque image is detected with `getextrema()` and returned
|
|
423
|
+
as-is; otherwise each color band is multiplied by the alpha band.
|
|
424
|
+
"""
|
|
425
|
+
alpha = image.getchannel("A")
|
|
426
|
+
if alpha.getextrema() == (255, 255):
|
|
427
|
+
return image
|
|
428
|
+
from PIL import Image as PILImage
|
|
429
|
+
from PIL import ImageChops
|
|
430
|
+
|
|
431
|
+
r, g, b, a = image.split()
|
|
432
|
+
return PILImage.merge(
|
|
433
|
+
"RGBA",
|
|
434
|
+
(
|
|
435
|
+
ImageChops.multiply(r, a),
|
|
436
|
+
ImageChops.multiply(g, a),
|
|
437
|
+
ImageChops.multiply(b, a),
|
|
438
|
+
a,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _pil_from_rgba(width: int, height: int, pixels: bytes) -> Any:
|
|
444
|
+
try:
|
|
445
|
+
from PIL import Image as PILImage
|
|
446
|
+
except ImportError:
|
|
447
|
+
raise RuntimeError(
|
|
448
|
+
"Pillow is required to premultiply raw RGBA pixels; install it "
|
|
449
|
+
"or pass premultiplied pixels"
|
|
450
|
+
) from None
|
|
451
|
+
return PILImage.frombuffer("RGBA", (width, height), pixels)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _try_pil_from_rgba(width: int, height: int, pixels: bytes) -> Optional[Any]:
|
|
455
|
+
try:
|
|
456
|
+
from PIL import Image as PILImage
|
|
457
|
+
except ImportError:
|
|
458
|
+
return None
|
|
459
|
+
return PILImage.frombuffer("RGBA", (width, height), pixels)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _rgba_is_opaque(pixels: bytes) -> bool:
|
|
463
|
+
"""
|
|
464
|
+
Whether every alpha byte in tightly packed RGBA8888 is 255.
|
|
465
|
+
|
|
466
|
+
The strided slice and count both run in C — no per-pixel Python loop.
|
|
467
|
+
"""
|
|
468
|
+
alphas = pixels[3::4]
|
|
469
|
+
return alphas.count(255) == len(alphas)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _pil_to_png_bytes(image: Any) -> bytes:
|
|
473
|
+
buf = io.BytesIO()
|
|
474
|
+
image.save(buf, format="PNG")
|
|
475
|
+
return buf.getvalue()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _encode_raw_packet(width: int, height: int, rgba: bytes) -> bytes:
|
|
479
|
+
"""
|
|
480
|
+
Encodes a raw full frame into a 0x04 wire packet:
|
|
481
|
+
`[0x04][width u32 LE][height u32 LE][premultiplied RGBA8888]`.
|
|
482
|
+
"""
|
|
483
|
+
pkt = bytearray(9 + len(rgba))
|
|
484
|
+
pkt[0] = 0x04
|
|
485
|
+
pkt[1:5] = width.to_bytes(4, "little")
|
|
486
|
+
pkt[5:9] = height.to_bytes(4, "little")
|
|
487
|
+
pkt[9:] = rgba
|
|
488
|
+
return bytes(pkt)
|