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.
Files changed (321) hide show
  1. {flet-0.86.0.dev2 → flet-0.86.2}/PKG-INFO +9 -9
  2. {flet-0.86.0.dev2 → flet-0.86.2}/pyproject.toml +9 -9
  3. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/__init__.py +3 -0
  4. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/base_control.py +1 -2
  5. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/context.py +25 -5
  6. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control_event.py +1 -2
  7. flet-0.86.2/src/flet/controls/core/raw_image.py +488 -0
  8. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/search_bar.py +14 -1
  9. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/page.py +55 -0
  10. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/types.py +11 -9
  11. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/connection.py +5 -0
  12. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/flet_dart_bridge_server.py +2 -0
  13. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/flet_socket_server.py +12 -3
  14. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/pyodide_connection.py +2 -0
  15. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/session.py +27 -0
  16. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/version.py +2 -2
  17. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/PKG-INFO +9 -9
  18. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/SOURCES.txt +2 -0
  19. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/requires.txt +8 -8
  20. flet-0.86.2/tests/test_raw_image_pixels.py +133 -0
  21. {flet-0.86.0.dev2 → flet-0.86.2}/README.md +0 -0
  22. {flet-0.86.0.dev2 → flet-0.86.2}/setup.cfg +0 -0
  23. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/app.py +0 -0
  24. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/__init__.py +0 -0
  25. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/authorization.py +0 -0
  26. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/authorization_service.py +0 -0
  27. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/group.py +0 -0
  28. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/oauth_provider.py +0 -0
  29. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/oauth_token.py +0 -0
  30. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/__init__.py +0 -0
  31. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/auth0_oauth_provider.py +0 -0
  32. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/azure_oauth_provider.py +0 -0
  33. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/github_oauth_provider.py +0 -0
  34. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/providers/google_oauth_provider.py +0 -0
  35. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/auth/user.py +0 -0
  36. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/canvas/__init__.py +0 -0
  37. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/cli.py +0 -0
  38. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/__init__.py +0 -0
  39. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component.py +0 -0
  40. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component_decorator.py +0 -0
  41. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/component_owned.py +0 -0
  42. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/__init__.py +0 -0
  43. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/hook.py +0 -0
  44. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_callback.py +0 -0
  45. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_context.py +0 -0
  46. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_dialog.py +0 -0
  47. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_effect.py +0 -0
  48. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_memo.py +0 -0
  49. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_ref.py +0 -0
  50. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/hooks/use_state.py +0 -0
  51. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/memo.py +0 -0
  52. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/observable.py +0 -0
  53. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/public_utils.py +0 -0
  54. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/router.py +0 -0
  55. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/components/utils.py +0 -0
  56. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/__init__.py +0 -0
  57. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/adaptive_control.py +0 -0
  58. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/alignment.py +0 -0
  59. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/animation.py +0 -0
  60. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/base_page.py +0 -0
  61. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/blur.py +0 -0
  62. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/border.py +0 -0
  63. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/border_radius.py +0 -0
  64. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/box.py +0 -0
  65. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/buttons.py +0 -0
  66. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/colors.py +0 -0
  67. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control.py +0 -0
  68. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/control_state.py +0 -0
  69. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/__init__.py +0 -0
  70. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/animated_switcher.py +0 -0
  71. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/autofill_group.py +0 -0
  72. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/__init__.py +0 -0
  73. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/arc.py +0 -0
  74. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/canvas.py +0 -0
  75. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/circle.py +0 -0
  76. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/color.py +0 -0
  77. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/fill.py +0 -0
  78. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/image.py +0 -0
  79. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/line.py +0 -0
  80. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/oval.py +0 -0
  81. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/path.py +0 -0
  82. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/points.py +0 -0
  83. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/rect.py +0 -0
  84. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/shadow.py +0 -0
  85. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/shape.py +0 -0
  86. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/canvas/text.py +0 -0
  87. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/column.py +0 -0
  88. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/dismissible.py +0 -0
  89. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/drag_target.py +0 -0
  90. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/draggable.py +0 -0
  91. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/flet_app.py +0 -0
  92. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/gesture_detector.py +0 -0
  93. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/grid_view.py +0 -0
  94. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/hero.py +0 -0
  95. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/icon.py +0 -0
  96. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/image.py +0 -0
  97. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/interactive_viewer.py +0 -0
  98. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/keyboard_listener.py +0 -0
  99. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/list_view.py +0 -0
  100. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/markdown.py +0 -0
  101. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/merge_semantics.py +0 -0
  102. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/page_view.py +0 -0
  103. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/pagelet.py +0 -0
  104. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/placeholder.py +0 -0
  105. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/reorderable_drag_handle.py +0 -0
  106. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/responsive_row.py +0 -0
  107. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/rotated_box.py +0 -0
  108. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/row.py +0 -0
  109. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/safe_area.py +0 -0
  110. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/screenshot.py +0 -0
  111. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/semantics.py +0 -0
  112. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/shader_mask.py +0 -0
  113. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/shimmer.py +0 -0
  114. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/stack.py +0 -0
  115. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/text.py +0 -0
  116. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/text_span.py +0 -0
  117. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/transparent_pointer.py +0 -0
  118. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/view.py +0 -0
  119. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/window.py +0 -0
  120. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/core/window_drag_area.py +0 -0
  121. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/__init__.py +0 -0
  122. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_action_sheet.py +0 -0
  123. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_action_sheet_action.py +0 -0
  124. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_activity_indicator.py +0 -0
  125. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_alert_dialog.py +0 -0
  126. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_app_bar.py +0 -0
  127. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_bottom_sheet.py +0 -0
  128. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_button.py +0 -0
  129. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_checkbox.py +0 -0
  130. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_colors.py +0 -0
  131. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_context_menu.py +0 -0
  132. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_context_menu_action.py +0 -0
  133. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_date_picker.py +0 -0
  134. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_dialog_action.py +0 -0
  135. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_filled_button.py +0 -0
  136. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.json +0 -0
  137. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.py +0 -0
  138. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_icons.pyi +0 -0
  139. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_list_tile.py +0 -0
  140. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_navigation_bar.py +0 -0
  141. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_picker.py +0 -0
  142. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_radio.py +0 -0
  143. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_segmented_button.py +0 -0
  144. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_slider.py +0 -0
  145. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_sliding_segmented_button.py +0 -0
  146. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_switch.py +0 -0
  147. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_textfield.py +0 -0
  148. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_timer_picker.py +0 -0
  149. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/cupertino/cupertino_tinted_button.py +0 -0
  150. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/device_info.py +0 -0
  151. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/dialog_control.py +0 -0
  152. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/duration.py +0 -0
  153. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/embed_json_encoder.py +0 -0
  154. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/events.py +0 -0
  155. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/exceptions.py +0 -0
  156. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/geometry.py +0 -0
  157. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/gradients.py +0 -0
  158. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/icon_data.py +0 -0
  159. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/id_counter.py +0 -0
  160. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/keys.py +0 -0
  161. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/layout_control.py +0 -0
  162. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/margin.py +0 -0
  163. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/__init__.py +0 -0
  164. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/alert_dialog.py +0 -0
  165. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/app_bar.py +0 -0
  166. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/auto_complete.py +0 -0
  167. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/badge.py +0 -0
  168. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/banner.py +0 -0
  169. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/bottom_app_bar.py +0 -0
  170. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/bottom_sheet.py +0 -0
  171. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/button.py +0 -0
  172. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/card.py +0 -0
  173. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/checkbox.py +0 -0
  174. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/chip.py +0 -0
  175. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/circle_avatar.py +0 -0
  176. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/container.py +0 -0
  177. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/context_menu.py +0 -0
  178. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/datatable.py +0 -0
  179. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/date_picker.py +0 -0
  180. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/date_range_picker.py +0 -0
  181. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/divider.py +0 -0
  182. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/dropdown.py +0 -0
  183. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/dropdownm2.py +0 -0
  184. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/elevated_button.py +0 -0
  185. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/expansion_panel.py +0 -0
  186. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/expansion_tile.py +0 -0
  187. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/filled_button.py +0 -0
  188. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/filled_tonal_button.py +0 -0
  189. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/floating_action_button.py +0 -0
  190. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/form_field_control.py +0 -0
  191. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icon_button.py +0 -0
  192. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.json +0 -0
  193. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.py +0 -0
  194. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/icons.pyi +0 -0
  195. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/list_tile.py +0 -0
  196. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/menu_bar.py +0 -0
  197. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/menu_item_button.py +0 -0
  198. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_bar.py +0 -0
  199. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_drawer.py +0 -0
  200. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/navigation_rail.py +0 -0
  201. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/outlined_button.py +0 -0
  202. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/popup_menu_button.py +0 -0
  203. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/progress_bar.py +0 -0
  204. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/progress_ring.py +0 -0
  205. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/radio.py +0 -0
  206. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/radio_group.py +0 -0
  207. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/range_slider.py +0 -0
  208. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/reorderable_list_view.py +0 -0
  209. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/segmented_button.py +0 -0
  210. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/selection_area.py +0 -0
  211. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/slider.py +0 -0
  212. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/snack_bar.py +0 -0
  213. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/submenu_button.py +0 -0
  214. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/switch.py +0 -0
  215. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/tabs.py +0 -0
  216. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/text_button.py +0 -0
  217. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/textfield.py +0 -0
  218. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/time_picker.py +0 -0
  219. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/tooltip.py +0 -0
  220. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/material/vertical_divider.py +0 -0
  221. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/multi_view.py +0 -0
  222. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/object_patch.py +0 -0
  223. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/padding.py +0 -0
  224. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/painting.py +0 -0
  225. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/query_string.py +0 -0
  226. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/ref.py +0 -0
  227. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/scrollable_control.py +0 -0
  228. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/__init__.py +0 -0
  229. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/accelerometer.py +0 -0
  230. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/barometer.py +0 -0
  231. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/battery.py +0 -0
  232. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/browser_context_menu.py +0 -0
  233. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/clipboard.py +0 -0
  234. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/connectivity.py +0 -0
  235. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/file_picker.py +0 -0
  236. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/gyroscope.py +0 -0
  237. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/haptic_feedback.py +0 -0
  238. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/magnetometer.py +0 -0
  239. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/screen_brightness.py +0 -0
  240. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/semantics_service.py +0 -0
  241. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/sensor_error_event.py +0 -0
  242. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/service.py +0 -0
  243. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/shake_detector.py +0 -0
  244. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/share.py +0 -0
  245. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/shared_preferences.py +0 -0
  246. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/storage_paths.py +0 -0
  247. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/url_launcher.py +0 -0
  248. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/user_accelerometer.py +0 -0
  249. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/services/wakelock.py +0 -0
  250. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/template_route.py +0 -0
  251. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/text_style.py +0 -0
  252. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/theme.py +0 -0
  253. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/transform.py +0 -0
  254. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/controls/value_types.py +0 -0
  255. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/data_channel.py +0 -0
  256. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/fastapi/__init__.py +0 -0
  257. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/protocol.py +0 -0
  258. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/messaging/session_store.py +0 -0
  259. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/__init__.py +0 -0
  260. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/pubsub_client.py +0 -0
  261. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pubsub/pubsub_hub.py +0 -0
  262. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/py.typed +0 -0
  263. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/pytest_plugin.py +0 -0
  264. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/security/__init__.py +0 -0
  265. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/__init__.py +0 -0
  266. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/finder.py +0 -0
  267. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/flet_test_app.py +0 -0
  268. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/remote_tester.py +0 -0
  269. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/testing/tester.py +0 -0
  270. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/__init__.py +0 -0
  271. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/browser.py +0 -0
  272. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/classproperty.py +0 -0
  273. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/deprecated.py +0 -0
  274. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/deprecated_enum.py +0 -0
  275. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/files.py +0 -0
  276. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/from_dict.py +0 -0
  277. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/griffe_deprecations.py +0 -0
  278. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/hashing.py +0 -0
  279. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/json_utils.py +0 -0
  280. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/locks.py +0 -0
  281. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/network.py +0 -0
  282. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/object_model.py +0 -0
  283. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/once.py +0 -0
  284. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/pip.py +0 -0
  285. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/platform_utils.py +0 -0
  286. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/slugify.py +0 -0
  287. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/strings.py +0 -0
  288. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/typing_utils.py +0 -0
  289. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/validation.py +0 -0
  290. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet/utils/vector.py +0 -0
  291. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/dependency_links.txt +0 -0
  292. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/entry_points.txt +0 -0
  293. {flet-0.86.0.dev2 → flet-0.86.2}/src/flet.egg-info/top_level.txt +0 -0
  294. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_auth_lazy_imports.py +0 -0
  295. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_base_control.py +0 -0
  296. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_colors.py +0 -0
  297. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_diff.py +0 -0
  298. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_effects.py +0 -0
  299. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_component_renderer.py +0 -0
  300. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_dataclasses.py +0 -0
  301. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_datetime.py +0 -0
  302. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_deprecated.py +0 -0
  303. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_events.py +0 -0
  304. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_from_dict.py +0 -0
  305. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_griffe_deprecations.py +0 -0
  306. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_icons.py +0 -0
  307. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_frozen.py +0 -0
  308. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_in_place.py +0 -0
  309. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_memory_churn.py +0 -0
  310. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_object_diff_performance.py +0 -0
  311. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_observable.py +0 -0
  312. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_patch_dataclass.py +0 -0
  313. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_modal.py +0 -0
  314. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_pop.py +0 -0
  315. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_router_recursive.py +0 -0
  316. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_session_disconnect_buffering.py +0 -0
  317. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_skip_double_update.py +0 -0
  318. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_use_dialog.py +0 -0
  319. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_validation.py +0 -0
  320. {flet-0.86.0.dev2 → flet-0.86.2}/tests/test_validation_benchmark.py +0 -0
  321. {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.0.dev2
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.0.dev2; extra == "cli"
13
- Requires-Dist: flet-web==0.86.0.dev2; extra == "web"
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.0.dev2; extra == "all"
21
- Requires-Dist: flet-web==0.86.0.dev2; extra == "all"
22
- Requires-Dist: flet-desktop==0.86.0.dev2; extra == "all"
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.0.dev2; extra == "cli"
24
+ Requires-Dist: flet-cli==0.86.2; extra == "cli"
25
25
  Provides-Extra: desktop
26
- Requires-Dist: flet-desktop==0.86.0.dev2; extra == "desktop"
26
+ Requires-Dist: flet-desktop==0.86.2; extra == "desktop"
27
27
  Provides-Extra: web
28
- Requires-Dist: flet-web==0.86.0.dev2; extra == "web"
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.0.dev2"
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.0.dev2; extra == 'cli'",
11
- "flet-web==0.86.0.dev2; extra == 'web'",
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.0.dev2",
27
- "flet-web==0.86.0.dev2",
28
- "flet-desktop==0.86.0.dev2",
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.0.dev2"]
30
+ cli = ["flet-cli==0.86.2"]
31
31
  desktop = [
32
- "flet-desktop==0.86.0.dev2",
32
+ "flet-desktop==0.86.2",
33
33
  ]
34
- web = ["flet-web==0.86.0.dev2"]
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) -> "Union[Page, BasePage]":
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
- self.__components_mode = False
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.__components_mode = True
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.__components_mode = False
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
- return self.__components_mode
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.__components_mode
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) -> Union["Page", "BasePage"]:
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)