diff --git a/.codex/skills/implement-flet-extension/SKILL.md b/.codex/skills/implement-flet-extension/SKILL.md index 06b969d910..b50a769447 100644 --- a/.codex/skills/implement-flet-extension/SKILL.md +++ b/.codex/skills/implement-flet-extension/SKILL.md @@ -56,6 +56,7 @@ Implement a Flet extension around an external Flutter package using existing `fl - Add dependency and registration to Flet client app. - Add extension package to `sdk/python/pyproject.toml`. +- Add extension to `sdk/python/packages/flet/pyproject.toml`. - Add extension to `sdk/python/examples/apps/flet_build_test/pyproject.toml`. - Add package entry to `.github/workflows/ci.yml`. - Add `.gitignore` to Flutter extension project if missing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 152601f87b..1e709ad11d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -713,6 +713,7 @@ jobs: flet-audio flet-audio-recorder flet-charts + flet-code-editor flet-color-pickers flet-datatable2 flet-flashlight @@ -825,7 +826,6 @@ jobs: for pkg in \ flet \ flet_cli \ - flet_color_pickers \ flet_desktop \ flet_desktop_light \ flet_web \ @@ -833,6 +833,8 @@ jobs: flet_audio \ flet_audio_recorder \ flet_charts \ + flet_code_editor \ + flet_color_pickers \ flet_datatable2 \ flet_flashlight \ flet_geolocator \ diff --git a/client/lib/main.dart b/client/lib/main.dart index 328d0903b0..cb3d3b34f1 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -17,6 +17,7 @@ import 'package:flet_permission_handler/flet_permission_handler.dart' as flet_permission_handler; import 'package:flet_secure_storage/flet_secure_storage.dart' as flet_secure_storage; +import 'package:flet_code_editor/flet_code_editor.dart' as flet_code_editor; // --FAT_CLIENT_START-- // --RIVE_IMPORT_START-- import 'package:flet_rive/flet_rive.dart' as flet_rive; @@ -54,6 +55,7 @@ void main([List? args]) async { flet_charts.Extension(), flet_color_picker.Extension(), flet_secure_storage.Extension(), + flet_code_editor.Extension(), // --FAT_CLIENT_START-- // --RIVE_EXTENSION_START-- flet_rive.Extension(), diff --git a/client/pubspec.lock b/client/pubspec.lock index 231b53b830..c2e93980ef 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.1" + autotrie: + dependency: transitive + description: + name: autotrie + sha256: "55da6faefb53cfcb0abb2f2ca8636123fb40e35286bb57440d2cf467568188f8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" battery_plus: dependency: transitive description: @@ -113,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -261,10 +277,10 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" file: dependency: transitive description: @@ -332,6 +348,13 @@ packages: relative: true source: path version: "0.1.0" + flet_code_editor: + dependency: "direct main" + description: + path: "../sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor" + relative: true + source: path + version: "0.1.0" flet_color_pickers: dependency: "direct main" description: @@ -414,6 +437,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_code_editor: + dependency: transitive + description: + name: flutter_code_editor + sha256: "9af48ba8e3558b6ea4bb98b84c5eb1649702acf53e61a84d88383eeb79b239b0" + url: "https://pub.dev" + source: hosted + version: "0.3.5" flutter_colorpicker: dependency: transitive description: @@ -679,6 +710,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" hooks: dependency: transitive description: @@ -764,6 +803,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + linked_scroll_controller: + dependency: transitive + description: + name: linked_scroll_controller + sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a + url: "https://pub.dev" + source: hosted + version: "0.2.0" lints: dependency: transitive description: @@ -916,6 +963,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" msgpack_dart: dependency: transitive description: @@ -1160,10 +1215,10 @@ packages: dependency: transitive description: name: record_android - sha256: "3bb3c6abbcb5fc1e86719fc6f0acdee89dfe8078543b92caad11854c487e435a" + sha256: "94783f08403aed33ffb68797bf0715b0812eb852f3c7985644c945faea462ba1" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.5.1" record_ios: dependency: transitive description: @@ -1184,10 +1239,10 @@ packages: dependency: transitive description: name: record_macos - sha256: f04d1547ff61ae54b4154e9726f656a17ad993f1a90f8f44bc40de94bafa072f + sha256: "084902e63fc9c0c224c29203d6c75f0bdf9b6a40536c9d916393c8f4c4256488" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" record_platform_interface: dependency: transitive description: @@ -1340,6 +1395,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + scrollable_positioned_list: + dependency: transitive + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" sensors_plus: dependency: transitive description: @@ -1581,10 +1644,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" url_launcher_linux: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 9251f1d0b4..d7e06b83c7 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -82,6 +82,9 @@ dependencies: flet_webview: path: ../sdk/python/packages/flet-webview/src/flutter/flet_webview + flet_code_editor: + path: ../sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor + cupertino_icons: ^1.0.6 wakelock_plus: ^1.4.0 package_info_plus: ^9.0.0 diff --git a/sdk/python/Taskfile.yml b/sdk/python/Taskfile.yml index c7eb793f8c..e0164b4170 100644 --- a/sdk/python/Taskfile.yml +++ b/sdk/python/Taskfile.yml @@ -71,6 +71,13 @@ tasks: cmds: - uv run --group docs --directory packages/flet mkdocs serve --dirtyreload + serve-docs-fast: + desc: "Serve MkDocs documentation from the 'packages/flet' directory without watching for file changes. Use this if you don't want to restart the server on every file change." + aliases: + - docs-fast + cmds: + - FLET_DOCS_SKIP_PYPI_INDEX=1 uv run --group docs --directory packages/flet mkdocs serve --dirtyreload + docs-coverage: desc: "Run docstring coverage report in the 'packages/flet' directory." cmds: diff --git a/sdk/python/examples/controls/code_editor/__init__.py b/sdk/python/examples/controls/code_editor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sdk/python/examples/controls/code_editor/example_1.py b/sdk/python/examples/controls/code_editor/example_1.py new file mode 100644 index 0000000000..b37af1c7c3 --- /dev/null +++ b/sdk/python/examples/controls/code_editor/example_1.py @@ -0,0 +1,47 @@ +import flet_code_editor as fce + +import flet as ft + +CODE = """import flet as ft + +def main(page: ft.Page): + counter = ft.Text("0", size=50, data=0) + + def btn_click(e): + counter.data += 1 + counter.value = str(counter.data) + counter.update() + + page.floating_action_button = ft.FloatingActionButton( + icon=ft.Icons.ADD, on_click=btn_click + ) + page.add( + ft.SafeArea( + ft.Container( + counter, + alignment=ft.Alignment.CENTER, + expand=True, + ), + expand=True, + ), + ) + +ft.run(main) +""" + + +def main(page: ft.Page): + page.add( + fce.CodeEditor( + language="python", + code_theme="atom-one-light", + # text_style=ft.TextStyle(font_family="monospace", size=14), + value=CODE, + expand=True, + on_change=lambda e: print("Changed:", e.data), + ) + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/code_editor/example_2.py b/sdk/python/examples/controls/code_editor/example_2.py new file mode 100644 index 0000000000..f8f37c3677 --- /dev/null +++ b/sdk/python/examples/controls/code_editor/example_2.py @@ -0,0 +1,124 @@ +import flet_code_editor as fce + +import flet as ft + +CODE = """import flet as ft + +def main(page: ft.Page): + counter = ft.Text("0", size=50, data=0) + + def btn_click(e): + counter.data += 1 + counter.value = str(counter.data) + counter.update() + + page.floating_action_button = ft.FloatingActionButton( + icon=ft.Icons.ADD, on_click=btn_click + ) + page.add( + ft.SafeArea( + ft.Container( + counter, + alignment=ft.Alignment.CENTER, + expand=True, + ), + expand=True, + ), + ) + +ft.run(main) +""" + + +def main(page: ft.Page): + page.title = "CodeEditor selection" + max_selection_preview = 80 + + theme = fce.CodeTheme( + styles={ + "keyword": ft.TextStyle( + color=ft.Colors.INDIGO_600, weight=ft.FontWeight.W_600 + ), + "string": ft.TextStyle(color=ft.Colors.RED_700), + "comment": ft.TextStyle(color=ft.Colors.GREY_600, italic=True), + } + ) + + text_style = ft.TextStyle( + font_family="monospace", + height=1.2, + ) + + gutter_style = fce.GutterStyle( + text_style=ft.TextStyle( + font_family="monospace", + height=1.2, + ), + show_line_numbers=True, + show_folding_handles=True, + width=80, + ) + + def handle_selection_change(e: ft.TextSelectionChangeEvent[fce.CodeEditor]): + if e.selected_text: + normalized = " ".join(e.selected_text.split()) + suffix = "..." if len(normalized) > max_selection_preview else "" + preview = normalized[:max_selection_preview] + selection.value = ( + f"Selection ({len(e.selected_text)} chars): '{preview}{suffix}'" + ) + else: + selection.value = "No selection." + selection_details.value = f"start={e.selection.start}, end={e.selection.end}" + caret.value = f"Caret position: {e.selection.end}" + + async def select_all(e: ft.Event[ft.Button]): + await editor.focus() + editor.selection = ft.TextSelection( + base_offset=0, + extent_offset=len(editor.value or ""), + ) + + async def move_caret_to_start(e: ft.Event[ft.Button]): + await editor.focus() + editor.selection = ft.TextSelection(base_offset=0, extent_offset=0) + + page.add( + ft.Column( + expand=True, + spacing=10, + controls=[ + editor := fce.CodeEditor( + language="python", + code_theme=theme, + autocompletion_enabled=True, + autocompletion_words=[ + "Container", + "Button", + "Text", + "Row", + "Column", + ], + value=CODE, + text_style=text_style, + gutter_style=gutter_style, + on_selection_change=handle_selection_change, + expand=True, + ), + selection := ft.Text("Select some text from the editor."), + selection_details := ft.Text(), + caret := ft.Text("Caret position: -"), + ft.Row( + spacing=10, + controls=[ + ft.Button("Select all text", on_click=select_all), + ft.Button("Move caret to start", on_click=move_caret_to_start), + ], + ), + ], + ) + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/examples/controls/code_editor/example_3.py b/sdk/python/examples/controls/code_editor/example_3.py new file mode 100644 index 0000000000..54fd7f2228 --- /dev/null +++ b/sdk/python/examples/controls/code_editor/example_3.py @@ -0,0 +1,43 @@ +import flet_code_editor as fce + +import flet as ft + +CODE = """# 1 +# 2 +# 3 +import json +import textwrap + +print("Folding demo") +""" + + +def main(page: ft.Page): + editor = fce.CodeEditor( + language="python", + value=CODE, + selection=ft.TextSelection(base_offset=41, extent_offset=62), + autofocus=True, + expand=True, + on_selection_change=lambda e: print("Selection:", e), + ) + + async def fold_imports(): + await editor.fold_imports() + + async def fold_comment(): + await editor.fold_comment_at_line_zero() + + page.add( + ft.Row( + [ + ft.Button("Fold imports", on_click=fold_imports), + ft.Button("Fold comment", on_click=fold_comment), + ] + ), + editor, + ) + + +if __name__ == "__main__": + ft.run(main) diff --git a/sdk/python/packages/flet-charts/src/flet_charts/bar_chart_rod.py b/sdk/python/packages/flet-charts/src/flet_charts/bar_chart_rod.py index 4486e06dd3..64883b183d 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/bar_chart_rod.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/bar_chart_rod.py @@ -18,7 +18,8 @@ class BarChartRodTooltip(ChartDataPointTooltip): """ The text to display in the tooltip. - When `None`, defaults to [`BarChartRod.to_y`][(p).]. + When `None`, defaults to + [`BarChartRod.to_y`][flet_charts.bar_chart_rod.BarChartRod.to_y]. """ def copy( @@ -112,7 +113,8 @@ class BarChartRod(ft.BaseControl): selected: bool = False """ If set to `True` a tooltip is always shown on top of the bar when - [`BarChart.interactive`][(p).] is set to `False`. + [`BarChart.interactive`][flet_charts.bar_chart.BarChart.interactive] + is set to `False`. """ tooltip: Union[BarChartRodTooltip, str] = field( diff --git a/sdk/python/packages/flet-charts/src/flet_charts/line_chart_data_point.py b/sdk/python/packages/flet-charts/src/flet_charts/line_chart_data_point.py index 03e51fbd07..c532b49ded 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/line_chart_data_point.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/line_chart_data_point.py @@ -15,7 +15,8 @@ class LineChartDataPointTooltip(ChartDataPointTooltip): """ The text to display in the tooltip. - When `None`, defaults to [`LineChartDataPoint.y`][(p).]. + When `None`, defaults to + [`LineChartDataPoint.y`][flet_charts.line_chart_data_point.LineChartDataPoint.y]. """ def copy( @@ -57,7 +58,8 @@ class LineChartDataPoint(ft.BaseControl): selected: bool = False """ - Draw the point as selected when [`LineChart.interactive`][(p).] + Draw the point as selected when + [`LineChart.interactive`][flet_charts.line_chart.LineChart.interactive] is set to `False`. """ diff --git a/sdk/python/packages/flet-charts/src/flet_charts/radar_chart.py b/sdk/python/packages/flet-charts/src/flet_charts/radar_chart.py index efe68b94df..533cb59937 100644 --- a/sdk/python/packages/flet-charts/src/flet_charts/radar_chart.py +++ b/sdk/python/packages/flet-charts/src/flet_charts/radar_chart.py @@ -46,7 +46,8 @@ class RadarChartTitle(ft.BaseControl): Note: If set, it takes precedence over the parent - [`RadarChart.title_position_percentage_offset`][(p).] value. + [`RadarChart.title_position_percentage_offset`][flet_charts.radar_chart.RadarChart.title_position_percentage_offset] + value. """ text_spans: Optional[list[ft.TextSpan]] = None @@ -89,20 +90,14 @@ class RadarChart(ft.LayoutControl): ```python fch.RadarChart( - titles=[ - fch.RadarChartTitle(text="winter"), - ... - ], + titles=[fch.RadarChartTitle(text="winter"), ...], radar_shape=fch.RadarShape.CIRCLE, data_sets=[ fch.RadarDataSet( fill_color=ft.Colors.with_opacity(0.2, ft.Colors.BLUE_GREY_700), - entries=[ - fch.RadarDataSetEntry(130), - ... - ], + entries=[fch.RadarDataSetEntry(130), ...], ), - ... + ..., ], ) ``` diff --git a/sdk/python/packages/flet-code-editor/CHANGELOG.md b/sdk/python/packages/flet-code-editor/CHANGELOG.md new file mode 100644 index 0000000000..f991420759 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## 0.1.0 + +Initial release. diff --git a/sdk/python/packages/flet-code-editor/LICENSE b/sdk/python/packages/flet-code-editor/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/sdk/python/packages/flet-code-editor/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sdk/python/packages/flet-code-editor/README.md b/sdk/python/packages/flet-code-editor/README.md new file mode 100644 index 0000000000..ef2ff92a32 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/README.md @@ -0,0 +1,34 @@ +# flet-code-editor + +[![pypi](https://img.shields.io/pypi/v/flet-code-editor.svg)](https://pypi.python.org/pypi/flet-code-editor) +[![downloads](https://static.pepy.tech/badge/flet-code-editor/month)](https://pepy.tech/project/flet-code-editor) +[![license](https://img.shields.io/badge/License-Apache_2.0-green.svg)](https://github.com/flet-dev/flet/blob/main/sdk/python/packages/flet-code-editor/LICENSE) + +A [Flet](https://flet.dev) extension for editing and highlighting source code. + +It is based on the [flutter_code_editor](https://pub.dev/packages/flutter_code_editor) Flutter package. + +## Documentation + +Detailed documentation to this package can be found [here](https://docs.flet.dev/code_editor/). + +## Usage + +### Installation + +To install the `flet-code-editor` package and add it to your project dependencies: + +- Using `uv`: + ```bash + uv add flet-code-editor + ``` + +- Using `pip`: + ```bash + pip install flet-code-editor + ``` + After this, you will have to manually add this package to your `requirements.txt` or `pyproject.toml`. + +### Examples + +For examples, see [these](https://github.com/flet-dev/flet/tree/main/examples/controls/code_editor). diff --git a/sdk/python/packages/flet-code-editor/pyproject.toml b/sdk/python/packages/flet-code-editor/pyproject.toml new file mode 100644 index 0000000000..3943357f9e --- /dev/null +++ b/sdk/python/packages/flet-code-editor/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "flet-code-editor" +version = "0.1.0" +description = "Edit and highlight source code inside Flet apps." +readme = "README.md" +authors = [{ name = "Flet contributors", email = "hello@flet.dev" }] +license = "Apache-2.0" +requires-python = ">=3.10" +dependencies = [ + "flet", +] + +[project.urls] +Homepage = "https://flet.dev" +Documentation = "https://docs.flet.dev/code_editor" +Repository = "https://github.com/flet-dev/flet/tree/main/sdk/python/packages/flet-code-editor" +Issues = "https://github.com/flet-dev/flet/issues" + +[tool.setuptools.package-data] +"flutter.flet_code_editor" = ["**/*"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/sdk/python/packages/flet-code-editor/src/flet_code_editor/__init__.py b/sdk/python/packages/flet-code-editor/src/flet_code_editor/__init__.py new file mode 100644 index 0000000000..4bc4486fc7 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flet_code_editor/__init__.py @@ -0,0 +1,8 @@ +from flet_code_editor.code_editor import CodeEditor +from flet_code_editor.types import CodeTheme, GutterStyle + +__all__ = [ + "CodeEditor", + "CodeTheme", + "GutterStyle", +] diff --git a/sdk/python/packages/flet-code-editor/src/flet_code_editor/code_editor.py b/sdk/python/packages/flet-code-editor/src/flet_code_editor/code_editor.py new file mode 100644 index 0000000000..2da4023735 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flet_code_editor/code_editor.py @@ -0,0 +1,136 @@ +from typing import Optional, Union + +import flet as ft +from flet_code_editor.types import CodeTheme, GutterStyle + +__all__ = ["CodeEditor"] + + +@ft.control("CodeEditor") +class CodeEditor(ft.LayoutControl): + """Edit and highlight source code.""" + + language: Optional[str] = None + """ + Syntax highlighting language. + + /// details | Supported languages + type: note + + `1c`, `abnf`, `accesslog`, `actionscript`, `ada`, `angelscript`, `apache`, + `applescript`, `arcade`, `arduino`, `armasm`, `asciidoc`, `aspectj`, `autohotkey`, + `autoit`, `avrasm`, `awk`, `axapta`, `bash`, `basic`, `bnf`, `brainfuck`, `cal`, + `capnproto`, `ceylon`, `clean`, `clojure`, `clojure-repl`, `cmake`, `coffeescript`, + `coq`, `cos`, `cpp`, `crmsh`, `crystal`, `cs`, `csp`, `css`, `d`, `dart`, `delphi`, + `diff`, `django`, `dns`, `dockerfile`, `dos`, `dsconfig`, `dts`, `dust`, `ebnf`, + `elixir`, `elm`, `erb`, `erlang`, `erlang-repl`, `excel`, `fix`, `flix`, `fortran`, + `fsharp`, `gams`, `gauss`, `gcode`, `gherkin`, `glsl`, `gml`, `gn`, `go`, `golo`, + `gradle`, `graphql`, `groovy`, `haml`, `handlebars`, `haskell`, `haxe`, `hsp`, + `htmlbars`, `http`, `hy`, `inform7`, `ini`, `irpf90`, `isbl`, `java`, `javascript`, + `jboss-cli`, `json`, `julia`, `julia-repl`, `kotlin`, `lasso`, `ldif`, `leaf`, + `less`, `lisp`, `livecodeserver`, `livescript`, `llvm`, `lsl`, `lua`, `makefile`, + `markdown`, `mathematica`, `matlab`, `maxima`, `mel`, `mercury`, `mipsasm`, `mizar`, + `mojolicious`, `monkey`, `moonscript`, `n1ql`, `nginx`, `nimrod`, `nix`, `nsis`, + `objectivec`, `ocaml`, `openscad`, `oxygene`, `parser3`, `perl`, `pf`, `pgsql`, + `php`, `plaintext`, `pony`, `powershell`, `processing`, `profile`, `prolog`, + `properties`, `protobuf`, `puppet`, `purebasic`, `python`, `q`, `qml`, `r`, + `reasonml`, `rib`, `roboconf`, `routeros`, `rsl`, `ruby`, `ruleslanguage`, `rust`, + `sas`, `scala`, `scheme`, `scilab`, `scss`, `shell`, `smali`, `smalltalk`, `sml`, + `solidity`, `sqf`, `sql`, `stan`, `stata`, `step21`, `stylus`, `subunit`, `swift`, + `taggerscript`, `tap`, `tcl`, `tex`, `thrift`, `tp`, `twig`, `typescript`, `vala`, + `vbnet`, `vbscript`, `vbscript-html`, `verilog`, `vhdl`, `vim`, `vue`, `x86asm`, + `xl`, `xml`, `xquery`, `yaml`, `zephir`. + /// + """ + + code_theme: Optional[Union[str, CodeTheme]] = None + """ + Highlighting theme or a named theme. + + /// details | Supported named themes + type: note + + `a11y-dark`, `a11y-light`, `agate`, `an-old-hope`, `androidstudio`, `arduino-light`, + `arta`, `ascetic`, `atelier-cave-dark`, `atelier-cave-light`, `atelier-dune-dark`, + `atelier-dune-light`, `atelier-estuary-dark`, `atelier-estuary-light`, + `atelier-forest-dark`, `atelier-forest-light`, `atelier-heath-dark`, + `atelier-heath-light`, `atelier-lakeside-dark`, `atelier-lakeside-light`, + `atelier-plateau-dark`, `atelier-plateau-light`, `atelier-savanna-dark`, + `atelier-savanna-light`, `atelier-seaside-dark`, `atelier-seaside-light`, + `atelier-sulphurpool-dark`, `atelier-sulphurpool-light`, `atom-one-dark`, + `atom-one-dark-reasonable`, `atom-one-light`, `brown-paper`, `codepen-embed`, + `color-brewer`, `darcula`, `dark`, `default`, `docco`, `dracula`, `far`, + `foundation`, `github`, `github-gist`, `gml`, `googlecode`, `gradient-dark`, + `grayscale`, `gruvbox-dark`, `gruvbox-light`, `hopscotch`, `hybrid`, `idea`, + `ir-black`, `isbl-editor-dark`, `isbl-editor-light`, `kimbie.dark`, `kimbie.light`, + `lightfair`, `magula`, `mono-blue`, `monokai`, `monokai-sublime`, `night-owl`, + `nord`, `obsidian`, `ocean`, `paraiso-dark`, `paraiso-light`, `pojoaque`, + `purebasic`, `qtcreator_dark`, `qtcreator_light`, `railscasts`, `rainbow`, + `routeros`, `school-book`, `shades-of-purple`, `solarized-dark`, `solarized-light`, + `sunburst`, `tomorrow`, `tomorrow-night`, `tomorrow-night-blue`, + `tomorrow-night-bright`, `tomorrow-night-eighties`, `vs`, `vs2015`, `xcode`, + `xt256`, `zenburn`. + /// + """ + + text_style: Optional[ft.TextStyle] = None + """Text style for the editor content.""" + + padding: Optional[ft.PaddingValue] = None + """Padding around the editor.""" + + value: Optional[str] = None + """Full text including folded sections and service comments.""" + + selection: Optional[ft.TextSelection] = None + """ + Represents the current text selection or caret position in the editor. + + Setting this property updates the editor selection and may trigger + [`on_selection_change`][(c).] when the editor is focused. + """ + + gutter_style: Optional[GutterStyle] = None + """Gutter styling.""" + + autocompletion_enabled: Optional[bool] = False + """Whether autocompletion is enabled.""" + + autocompletion_words: Optional[list[str]] = None + """Words offered by autocompletion.""" + + read_only: Optional[bool] = False + """Whether the editor is read-only.""" + + autofocus: Optional[bool] = False + """Whether this editor should focus itself if nothing else is focused.""" + + on_change: Optional[ft.ControlEventHandler["CodeEditor"]] = None + """Called when the editor text changes.""" + + on_selection_change: Optional[ + ft.EventHandler[ft.TextSelectionChangeEvent["CodeEditor"]] + ] = None + """Called when the text selection or caret position changes.""" + + on_focus: Optional[ft.ControlEventHandler["CodeEditor"]] = None + """Called when the editor receives focus.""" + + on_blur: Optional[ft.ControlEventHandler["CodeEditor"]] = None + """Called when the editor loses focus.""" + + async def focus(self): + """Request focus for this editor.""" + await self._invoke_method("focus") + + async def fold_comment_at_line_zero(self): + """Fold the comment block at line 0.""" + await self._invoke_method("fold_comment_at_line_zero") + + async def fold_imports(self): + """Fold import sections.""" + await self._invoke_method("fold_imports") + + async def fold_at(self, line_number: int): + """Fold the block starting at the given line number.""" + await self._invoke_method("fold_at", arguments={"line_number": line_number}) diff --git a/sdk/python/packages/flet-code-editor/src/flet_code_editor/types.py b/sdk/python/packages/flet-code-editor/src/flet_code_editor/types.py new file mode 100644 index 0000000000..f5931174ae --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flet_code_editor/types.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from typing import Optional + +import flet as ft + +__all__ = ["CodeTheme", "GutterStyle"] + + +@dataclass +class CodeTheme: + """ + Defines syntax highlighting styles for code tokens. + + /// details | Supported style names + type: note + + `addition`, `attr`, `attribute`, `built_in`, `builtin-name`, `bullet`, `class`, + `code`, `comment`, `deletion`, `doctag`, `emphasis`, `formula`, `function`, + `keyword`, `link`, `link_label`, `literal`, `meta`, `meta-keyword`, + `meta-string`, `name`, `number`, `operator`, `params`, `pattern-match`, `quote`, + `regexp`, `root`, `section`, `selector-attr`, `selector-class`, `selector-id`, + `selector-pseudo`, `selector-tag`, `string`, `strong`, `stronge`, `subst`, + `subtr`, `symbol`, `tag`, `template-tag`, `template-variable`, `title`, `type`, + `variable`. + /// + """ + + styles: dict[str, ft.TextStyle] + """Map of token names to text styles.""" + + +@dataclass +class GutterStyle: + """Defines gutter appearance (line numbers) for the code editor.""" + + text_style: Optional[ft.TextStyle] = None + """Text style for line numbers.""" + + background_color: Optional[ft.ColorValue] = None + """Background color for the gutter.""" + + width: Optional[ft.Number] = None + """Fixed width of the gutter.""" + + margin: Optional[ft.Number] = None + """Margin outside the gutter.""" + + show_errors: bool = True + """Whether to show errors in the gutter.""" + + show_folding_handles: bool = True + """Whether to show folding handles in the gutter.""" + + show_line_numbers: bool = True + """Whether to show line numbers in the gutter.""" diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.gitignore b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.gitignore new file mode 100644 index 0000000000..ed7794f2ab --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.gitignore @@ -0,0 +1,34 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ +.flutter-plugins +.flutter-plugins-dependencies + +# override parent rules +!lib/ diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.metadata b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.metadata new file mode 100644 index 0000000000..07d8623a38 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "2e9cb0aa71a386a91f73f7088d115c0d96654829" + channel: "stable" + +project_type: package diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/analysis_options.yaml b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/analysis_options.yaml new file mode 100644 index 0000000000..a5744c1cfb --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/flet_code_editor.dart b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/flet_code_editor.dart new file mode 100644 index 0000000000..59c49b6b56 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/flet_code_editor.dart @@ -0,0 +1,3 @@ +library flet_code_editor; + +export "src/extension.dart" show Extension; diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/code_editor.dart b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/code_editor.dart new file mode 100644 index 0000000000..b518270f57 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/code_editor.dart @@ -0,0 +1,219 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_code_editor/flutter_code_editor.dart' as fce; +import 'package:highlight/languages/all.dart'; + +import 'utils/code_editor.dart'; +import 'utils/flet_code_controller.dart'; + +class CodeEditorControl extends StatefulWidget { + final Control control; + + const CodeEditorControl({super.key, required this.control}); + + @override + State createState() => _CodeEditorControlState(); +} + +class _CodeEditorControlState extends State { + late FletCodeController _controller; + late final FocusNode _focusNode; + bool _didAutoFocus = false; + TextSelection? _selection; + String _value = ""; + String? _languageName; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + _controller = _createController(); + _value = _readValue(); + _selection = _controller.selection; + _controller.addListener(_handleControllerChange); + widget.control.addInvokeMethodListener(_invokeMethod); + } + + @override + void dispose() { + _controller.removeListener(_handleControllerChange); + _controller.dispose(); + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + widget.control.removeInvokeMethodListener(_invokeMethod); + super.dispose(); + } + + Future _invokeMethod(String name, dynamic args) async { + debugPrint("CodeEditor.$name($args)"); + switch (name) { + case "focus": + _focusNode.requestFocus(); + break; + case "fold_comment_at_line_zero": + (_controller as dynamic).foldCommentAtLineZero(); + break; + case "fold_imports": + (_controller as dynamic).foldImports(); + break; + case "fold_at": + final line = parseInt(args["line_number"]); + if (line != null) { + (_controller as dynamic).foldAt(line); + } + break; + default: + throw Exception("Unknown CodeEditor method: $name"); + } + } + + void _onFocusChange() { + widget.control.triggerEvent(_focusNode.hasFocus ? "focus" : "blur"); + } + + FletCodeController _createController() { + _languageName = widget.control.get("language", "")!; + return FletCodeController( + text: _initialValueFromControl(), + language: allLanguages[_languageName!.toLowerCase()], + ); + } + + String _initialValueFromControl() { + return widget.control.getString("value") ?? ""; + } + + List? _stringList(dynamic value) { + if (value is List) { + return value.map((e) => e.toString()).toList(); + } + return null; + } + + String _readValue() => _controller.fullText; + + void _setValue(String value) { + _controller.fullText = value; + } + + void _handleControllerChange() { + final value = _readValue(); + final selection = _controller.selection; + final selectionChanged = selection != _selection; + final valueChanged = value != _value; + + if (!valueChanged && !selectionChanged) { + return; + } + + _value = value; + _selection = selection; + + final updates = {}; + + if (valueChanged) { + updates["value"] = value; + } + + if (selectionChanged) { + if (selection.isValid) { + updates["selection"] = selection.toMap(); + } else { + updates["selection"] = null; + } + } + + if (updates.isNotEmpty) { + widget.control.updateProperties(updates); + } + + if (valueChanged && widget.control.getBool("on_change", false)!) { + widget.control.triggerEvent("change", value); + } + + if (selectionChanged && selection.isValid) { + final visibleText = _controller.text; + widget.control.triggerEvent("selection_change", { + "selected_text": visibleText.substring(selection.start, selection.end), + "selection": selection.toMap(), + }); + } + } + + @override + Widget build(BuildContext context) { + debugPrint("CodeEditor build: ${widget.control.id}"); + + final languageName = widget.control.get("language", "")!; + if (languageName != _languageName) { + final previousSelection = _controller.selection; + _controller.removeListener(_handleControllerChange); + _controller.dispose(); + _controller = _createController(); + _value = _readValue(); + _controller.addListener(_handleControllerChange); + if (previousSelection.isValid) { + _controller.selection = previousSelection; + } + } + + final value = widget.control.getString("value"); + final controllerValue = _readValue(); + if (value != null && value != controllerValue) { + _setValue(value); + _value = value; + } + + final explicitSelection = parseTextSelection( + widget.control.get("selection"), + minOffset: 0, + maxOffset: _controller.text.length, + ); + if (explicitSelection != null && explicitSelection != _controller.selection) { + _controller.selection = explicitSelection; + } + + final autofocus = widget.control.getBool("autofocus", false)!; + if (!_didAutoFocus && autofocus) { + _didAutoFocus = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + FocusScope.of(context).autofocus(_focusNode); + } + }); + } + + final themeData = parseCodeThemeData(widget.control, context); + final gutterStyle = parseGutterStyle(widget.control, context); + final autocompletionEnabled = + widget.control.getBool("autocompletion_enabled", false)!; + final autocompletionWords = + _stringList(widget.control.get("autocompletion_words")) ?? const []; + _controller.autocompletionEnabled = autocompletionEnabled; + if (autocompletionEnabled) { + _controller.autocompleter.setCustomWords(autocompletionWords); + } else { + _controller.autocompleter.setCustomWords(const []); + _controller.popupController.hide(); + } + + Widget editor = SingleChildScrollView( + child: fce.CodeField( + controller: _controller, + focusNode: _focusNode, + readOnly: widget.control.getBool("read_only", false)!, + textStyle: + parseTextStyle(widget.control.get("text_style"), Theme.of(context)), + gutterStyle: gutterStyle, + padding: widget.control.getEdgeInsets("padding", EdgeInsets.zero)!, + enabled: !widget.control.disabled, + )); + + if (themeData != null) { + editor = fce.CodeTheme(data: themeData, child: editor); + } + + return LayoutControl(control: widget.control, child: editor); + } +} diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/extension.dart b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/extension.dart new file mode 100644 index 0000000000..15f3b8e4ba --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/extension.dart @@ -0,0 +1,16 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/widgets.dart'; + +import 'code_editor.dart'; + +class Extension extends FletExtension { + @override + Widget? createWidget(Key? key, Control control) { + switch (control.type) { + case "CodeEditor": + return CodeEditorControl(control: control); + default: + return null; + } + } +} diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/code_editor.dart b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/code_editor.dart new file mode 100644 index 0000000000..74e261f09c --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/code_editor.dart @@ -0,0 +1,79 @@ +import 'package:flet/flet.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_code_editor/flutter_code_editor.dart' as fce; +import 'package:flutter_highlight/theme_map.dart'; + +fce.CodeThemeData? parseCodeThemeData(Control control, BuildContext context) { + final codeTheme = control.get("code_theme"); + if (codeTheme is! Map) { + if (codeTheme is String) { + final named = themeMap[codeTheme.toLowerCase()]; + return named == null ? null : fce.CodeThemeData(styles: named); + } + return null; + } + final themeName = codeTheme["name"]; + if (themeName is String) { + final named = themeMap[themeName.toLowerCase()]; + if (named != null) { + return fce.CodeThemeData(styles: named); + } + } + final styles = codeTheme["styles"]; + if (styles is! Map) { + return null; + } + + final parsedStyles = {}; + styles.forEach((key, value) { + final style = parseTextStyle(value, Theme.of(context)); + if (style != null) { + parsedStyles[key.toString()] = style; + } + }); + + if (parsedStyles.isEmpty) { + return null; + } + + return fce.CodeThemeData(styles: parsedStyles); +} + +fce.GutterStyle? parseGutterStyle(Control control, BuildContext context) { + final gutterStyle = control.get("gutter_style"); + if (gutterStyle is! Map) { + return null; + } + + final textStyle = parseTextStyle(gutterStyle["text_style"], Theme.of(context)); + final background = + parseColor(gutterStyle["background_color"], Theme.of(context)); + final width = parseDouble(gutterStyle["width"]); + final margin = _parseGutterMargin(gutterStyle["margin"]); + + final showErrors = gutterStyle["show_errors"]; + final showFoldingHandles = gutterStyle["show_folding_handles"]; + final showLineNumbers = gutterStyle["show_line_numbers"]; + + return fce.GutterStyle( + textStyle: textStyle, + background: background, + width: width ?? 80.0, + margin: margin ?? 10.0, + showErrors: showErrors is bool ? showErrors : true, + showFoldingHandles: showFoldingHandles is bool ? showFoldingHandles : true, + showLineNumbers: showLineNumbers is bool ? showLineNumbers : true, + ); +} + +double? _parseGutterMargin(dynamic value) { + final margin = parseDouble(value); + if (margin != null) { + return margin; + } + final edgeInsets = parseEdgeInsets(value); + if (edgeInsets == null) { + return null; + } + return (edgeInsets.left + edgeInsets.right) / 2; +} diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/flet_code_controller.dart b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/flet_code_controller.dart new file mode 100644 index 0000000000..c95065840b --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/lib/src/utils/flet_code_controller.dart @@ -0,0 +1,54 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_code_editor/flutter_code_editor.dart' as fce; + +class FletCodeController extends fce.CodeController { + FletCodeController({ + super.text, + super.language, + }); + + bool autocompletionEnabled = false; + + @override + Future generateSuggestions() async { + if (!autocompletionEnabled) { + popupController.hide(); + return; + } + return super.generateSuggestions(); + } + + @override + void insertSelectedWord() { + final previousSelection = selection; + final selectedWord = popupController.getSelectedWord(); + final startPosition = value.wordAtCursorStart; + final currentWord = value.wordAtCursor; + + if (startPosition == null || currentWord == null) { + popupController.hide(); + return; + } + + final endReplacingPosition = startPosition + currentWord.length; + final endSelectionPosition = startPosition + selectedWord.length; + + final replacedText = text.replaceRange( + startPosition, + endReplacingPosition, + selectedWord, + ); + + final adjustedSelection = previousSelection.copyWith( + baseOffset: endSelectionPosition, + extentOffset: endSelectionPosition, + ); + + value = TextEditingValue( + text: replacedText, + selection: adjustedSelection, + ); + + popupController.hide(); + } +} diff --git a/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/pubspec.yaml b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/pubspec.yaml new file mode 100644 index 0000000000..eb7ffe93a5 --- /dev/null +++ b/sdk/python/packages/flet-code-editor/src/flutter/flet_code_editor/pubspec.yaml @@ -0,0 +1,24 @@ +name: flet_code_editor +description: Flet CodeEditor control +version: 0.1.0 +publish_to: none + +environment: + sdk: '>=3.2.3 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + flutter_code_editor: ^0.3.1 + flutter_highlight: ^0.7.0 + highlight: ^0.7.0 + + flet: + path: ../../../../../../../packages/flet + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 diff --git a/sdk/python/packages/flet-color-pickers/README.md b/sdk/python/packages/flet-color-pickers/README.md index 4e99f62fbe..127d23c204 100644 --- a/sdk/python/packages/flet-color-pickers/README.md +++ b/sdk/python/packages/flet-color-pickers/README.md @@ -37,4 +37,4 @@ To install the `flet-color-pickers` package and add it to your project dependenc ### Examples -For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/color_picker). +For examples, see [these](https://github.com/flet-dev/flet/tree/main/sdk/python/examples/controls/color_pickers). diff --git a/sdk/python/packages/flet-datatable2/src/flet_datatable2/datarow2.py b/sdk/python/packages/flet-datatable2/src/flet_datatable2/datarow2.py index 419405e1a6..1e1e7477f0 100644 --- a/sdk/python/packages/flet-datatable2/src/flet_datatable2/datarow2.py +++ b/sdk/python/packages/flet-datatable2/src/flet_datatable2/datarow2.py @@ -20,7 +20,9 @@ class DataRow2(ft.DataRow): Decoration to be applied to this row. Note: - If provided, [`DataTable2.divider_thickness`][(p).] has no effect. + If provided, + [`DataTable.divider_thickness`][flet.DataTable.divider_thickness] + has no effect. """ specific_row_height: Optional[ft.Number] = None diff --git a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py index 34133c0070..282c66581e 100644 --- a/sdk/python/packages/flet-map/src/flet_map/marker_layer.py +++ b/sdk/python/packages/flet-map/src/flet_map/marker_layer.py @@ -20,7 +20,7 @@ class Marker(ft.Control): The content to be displayed at [`coordinates`][(c).]. Raises: - ValueError: If it is not [`visible`][(c).]. + ValueError: If it is not [`visible`][flet.Control.visible]. """ coordinates: MapLatitudeLongitude diff --git a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py index 61abc5754d..81f9d777e0 100644 --- a/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py +++ b/sdk/python/packages/flet-web/src/flet_web/fastapi/flet_app.py @@ -4,9 +4,10 @@ import os import traceback import weakref +from collections.abc import Awaitable from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta, timezone -from typing import Any, Optional +from typing import Any, Callable, Optional import msgpack from fastapi import WebSocket, WebSocketDisconnect @@ -38,6 +39,8 @@ DEFAULT_FLET_SESSION_TIMEOUT = 3600 DEFAULT_FLET_OAUTH_STATE_TIMEOUT = 600 +AppMainCallable = Callable[..., Any] +BeforeMainCallable = Callable[[Any], Awaitable[None]] class FletApp(Connection): @@ -64,8 +67,8 @@ def __init__( self, loop: asyncio.AbstractEventLoop, executor: ThreadPoolExecutor, - main, - before_main, + main: AppMainCallable, + before_main: BeforeMainCallable, session_timeout_seconds: int = DEFAULT_FLET_SESSION_TIMEOUT, oauth_state_timeout_seconds: int = DEFAULT_FLET_OAUTH_STATE_TIMEOUT, upload_endpoint_path: Optional[str] = None, diff --git a/sdk/python/packages/flet/docs/assets/datatable2/example_2.gif b/sdk/python/packages/flet/docs/assets/datatable2/example_2.gif new file mode 100644 index 0000000000..07fb5a645e Binary files /dev/null and b/sdk/python/packages/flet/docs/assets/datatable2/example_2.gif differ diff --git a/sdk/python/packages/flet/docs/codeeditor/index.md b/sdk/python/packages/flet/docs/codeeditor/index.md new file mode 100644 index 0000000000..697731b7b0 --- /dev/null +++ b/sdk/python/packages/flet/docs/codeeditor/index.md @@ -0,0 +1,56 @@ +--- +class_name: flet_code_editor.CodeEditor +examples: ../../examples/controls/code_editor +example_images: ../test-images/examples/extensions/code_editor/golden/macos/code_editor_examples +--- + +{{ class_summary(class_name, example_images + "/image_for_docs.png", image_caption="Basic CodeEditor") }} + +## Usage + +Add `flet-code-editor` to your project dependencies: + +/// tab | uv +```bash +uv add flet-code-editor +``` + +/// +/// tab | pip +```bash +pip install flet-code-editor # (1)! +``` + +1. After this, you will have to manually add this package to your `requirements.txt` or `pyproject.toml`. +/// + +## Examples + +### Basic example +```python +--8<-- "{{ examples }}/example_1.py" +``` + +{{ image(example_images + "/example_1.png", alt="code-editor-example-1", width="80%") }} + +### Selection handling + +```python +--8<-- "{{ examples }}/example_2.py" +``` + +{{ image(example_images + "/example_2.png", alt="code-editor-example-2", width="80%") }} + +### Folding and initial selection + +```python +--8<-- "{{ examples }}/example_3.py" +``` + +{{ image(example_images + "/example_3.png", alt="code-editor-example-3", width="80%") }} + +See also types: +- [`CodeTheme`](types/codetheme.md) +- [`GutterStyle`](types/gutterstyle.md) + +{{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/codeeditor/types/codetheme.md b/sdk/python/packages/flet/docs/codeeditor/types/codetheme.md new file mode 100644 index 0000000000..6d7adf63fb --- /dev/null +++ b/sdk/python/packages/flet/docs/codeeditor/types/codetheme.md @@ -0,0 +1 @@ +{{ class_all_options("flet_code_editor.CodeTheme", separate_signature=True) }} diff --git a/sdk/python/packages/flet/docs/codeeditor/types/gutterstyle.md b/sdk/python/packages/flet/docs/codeeditor/types/gutterstyle.md new file mode 100644 index 0000000000..70f0254ce8 --- /dev/null +++ b/sdk/python/packages/flet/docs/codeeditor/types/gutterstyle.md @@ -0,0 +1 @@ +{{ class_all_options("flet_code_editor.GutterStyle", separate_signature=True) }} diff --git a/sdk/python/packages/flet/docs/colorpickers/blockpicker.md b/sdk/python/packages/flet/docs/colorpickers/blockpicker.md index 77d9390020..9dc08f9e6c 100644 --- a/sdk/python/packages/flet/docs/colorpickers/blockpicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/blockpicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.BlockPicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # BlockPicker diff --git a/sdk/python/packages/flet/docs/colorpickers/colorpicker.md b/sdk/python/packages/flet/docs/colorpickers/colorpicker.md index 879391177c..7525d2b668 100644 --- a/sdk/python/packages/flet/docs/colorpickers/colorpicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/colorpicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.ColorPicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # ColorPicker diff --git a/sdk/python/packages/flet/docs/colorpickers/hueringpicker.md b/sdk/python/packages/flet/docs/colorpickers/hueringpicker.md index e499f53846..78c4f8a84f 100644 --- a/sdk/python/packages/flet/docs/colorpickers/hueringpicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/hueringpicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.HueRingPicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # HueRingPicker diff --git a/sdk/python/packages/flet/docs/colorpickers/index.md b/sdk/python/packages/flet/docs/colorpickers/index.md index 8c7b6ce047..2e805274de 100644 --- a/sdk/python/packages/flet/docs/colorpickers/index.md +++ b/sdk/python/packages/flet/docs/colorpickers/index.md @@ -1,5 +1,5 @@ --- -examples: ../../examples/controls/color_picker +examples: ../../examples/controls/color_pickers --- # Color pickers diff --git a/sdk/python/packages/flet/docs/colorpickers/materialpicker.md b/sdk/python/packages/flet/docs/colorpickers/materialpicker.md index 074722c0f1..396211048a 100644 --- a/sdk/python/packages/flet/docs/colorpickers/materialpicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/materialpicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.MaterialPicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # MaterialPicker diff --git a/sdk/python/packages/flet/docs/colorpickers/multiplechoiceblockpicker.md b/sdk/python/packages/flet/docs/colorpickers/multiplechoiceblockpicker.md index 7785c2021d..f6f8c1c1ff 100644 --- a/sdk/python/packages/flet/docs/colorpickers/multiplechoiceblockpicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/multiplechoiceblockpicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.MultipleChoiceBlockPicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # MultipleChoiceBlockPicker diff --git a/sdk/python/packages/flet/docs/colorpickers/slidepicker.md b/sdk/python/packages/flet/docs/colorpickers/slidepicker.md index fb2f03d0a6..b73c785884 100644 --- a/sdk/python/packages/flet/docs/colorpickers/slidepicker.md +++ b/sdk/python/packages/flet/docs/colorpickers/slidepicker.md @@ -1,7 +1,7 @@ --- class_name: flet_color_pickers.SlidePicker -examples: ../../examples/controls/color_picker -example_images: ../test-images/examples/color_picker/golden/macos/color_picker_examples +examples: ../../examples/controls/color_pickers +example_images: ../test-images/examples/extensions/color_picker/golden/macos/color_picker_examples --- # SlidePicker diff --git a/sdk/python/packages/flet/docs/contributing.md b/sdk/python/packages/flet/docs/contributing.md deleted file mode 100644 index fc162802ad..0000000000 --- a/sdk/python/packages/flet/docs/contributing.md +++ /dev/null @@ -1 +0,0 @@ ---8<-- "../../../../CONTRIBUTING.md" diff --git a/sdk/python/packages/flet/docs/controls/container.md b/sdk/python/packages/flet/docs/controls/container.md index 7e42b5e8d7..b427c8a3bd 100644 --- a/sdk/python/packages/flet/docs/controls/container.md +++ b/sdk/python/packages/flet/docs/controls/container.md @@ -2,6 +2,7 @@ class_name: flet.Container examples: ../../examples/controls/container example_images: ../examples/controls/container/media +example_test_images: ../test-images/examples/material/golden/macos/container --- {{ class_summary(class_name) }} @@ -69,7 +70,7 @@ example_images: ../examples/controls/container/media --8<-- "{{ examples }}/nested_themes_1.py" ``` -{{ image(example_images + "/nested_themes_1.png", width="80%") }} +{{ image(example_test_images + "/nested_themes_1.png", width="80%") }} ### Nested themes 2 @@ -77,7 +78,7 @@ example_images: ../examples/controls/container/media --8<-- "{{ examples }}/nested_themes_2.py" ``` -{{ image(example_images + "/nested_themes_2.png", width="80%") }} +{{ image(example_test_images + "/nested_themes_2.png", width="80%") }} ### Nested themes 3 @@ -94,6 +95,6 @@ example_images: ../examples/controls/container/media --8<-- "{{ examples }}/size_aware.py" ``` -{{ image(example_images + "/size_aware.png", width="80%") }} +{{ image(example_test_images + "/size_aware.png", width="80%") }} {{ class_members(class_name) }} diff --git a/sdk/python/packages/flet/docs/cookbook/adaptive-apps.md b/sdk/python/packages/flet/docs/cookbook/adaptive-apps.md index 580d0cbef7..975f8bc05b 100644 --- a/sdk/python/packages/flet/docs/cookbook/adaptive-apps.md +++ b/sdk/python/packages/flet/docs/cookbook/adaptive-apps.md @@ -179,7 +179,6 @@ Below is the list of adaptive Material controls and their matching Cupertino con - [:octicons-arrow-right-24: `Slider`][flet.Slider] --- - ![Slider](../examples/controls/slider/media/index.png){width="45%"} - [:octicons-arrow-right-24: `CupertinoSlider`][flet.CupertinoSlider] diff --git a/sdk/python/packages/flet/docs/cookbook/animations.md b/sdk/python/packages/flet/docs/cookbook/animations.md index 59ada263b6..d0c646cfaf 100644 --- a/sdk/python/packages/flet/docs/cookbook/animations.md +++ b/sdk/python/packages/flet/docs/cookbook/animations.md @@ -37,7 +37,7 @@ properties, described below, to enable implicit animation of its appearance: ### Opacity animation Setting control's `animate_opacity` to either `True`, number or an instance of `Animation` class (see above) -enables implicit animation of [`LayoutControl.opacity`][flet.LayoutControl.opacity] property. +enables implicit animation of [`Control.opacity`][flet.Control.opacity] property. ```python --8<-- "../../examples/controls/layout_control/animate_opacity.py" diff --git a/sdk/python/packages/flet/docs/cookbook/keyboard-shortcuts.md b/sdk/python/packages/flet/docs/cookbook/keyboard-shortcuts.md index 3449a82bcc..2ea35413ee 100644 --- a/sdk/python/packages/flet/docs/cookbook/keyboard-shortcuts.md +++ b/sdk/python/packages/flet/docs/cookbook/keyboard-shortcuts.md @@ -1,6 +1,6 @@ A solid keyboard support is a key for user productivity while using your web and, especially, desktop app. Indeed, it could be really annoying to constantly switch between mouse and keyboard. -In addition to form controls' `.autofocus` property and [`TextField.focus()`][flet.TextField.focus] method Flet allows handling "global" keyboard events. +In addition to form controls' `.autofocus` property and [`TextField.focus()`][flet.FormFieldControl.focus] method Flet allows handling "global" keyboard events. To capture all keystrokes implement `page.on_keyboard_event` handler. Event handler parameter `e` is an instance of `KeyboardEvent` class with the following properties: diff --git a/sdk/python/packages/flet/docs/cookbook/pub-sub.md b/sdk/python/packages/flet/docs/cookbook/pub-sub.md index 998fbd146e..78c9c918a1 100644 --- a/sdk/python/packages/flet/docs/cookbook/pub-sub.md +++ b/sdk/python/packages/flet/docs/cookbook/pub-sub.md @@ -6,10 +6,10 @@ Flet PubSub allows broadcasting messages to all app sessions or sending only to A typical PubSub usage would be: -* [subscribe][flet.PubSubClient.subscribe] to broadcast messages or [subscribe to a topic][flet.PubSubClient.subscribe_topic] on app session start. -* [send][flet.PubSubClient.send_all] broadcast message or [send to a topic][flet.PubSubClient.send_all_on_topic] on some event, like "Send" button click. -* [unsubscribe][flet.PubSubClient.subscribe] from broadcast messages or [unsubscribe from a topic][flet.PubSubClient.unsubscribe_topic] on some event, like "Leave" button click. -* [unsubscribe_all][flet.PubSubClient.unsubscribe_all] from everything on [`page.on_close`][flet.Page.on_close]. +* [`subscribe()`][flet.pubsub.PubSubClient.subscribe] to broadcast messages or [`subscribe_topic()`][flet.pubsub.PubSubClient.subscribe_topic] on app session start. +* [`send_all()`][flet.pubsub.PubSubClient.send_all] broadcast message or [`send_all_on_topic()`][flet.pubsub.PubSubClient.send_all_on_topic] on some event, like "Send" button click. +* [`unsubscribe()`][flet.pubsub.PubSubClient.unsubscribe] from broadcast messages or [`unsubscribe_topic()`][flet.pubsub.PubSubClient.unsubscribe_topic] on some event, like "Leave" button click. +* [`unsubscribe_all()`][flet.pubsub.PubSubClient.unsubscribe_all] from everything on [`page.on_close`][flet.Page.on_close]. This is an example of a simple chat application that uses PubSub: diff --git a/sdk/python/packages/flet/docs/datatable2/index.md b/sdk/python/packages/flet/docs/datatable2/index.md index dfaccc7948..2bf92ab65b 100644 --- a/sdk/python/packages/flet/docs/datatable2/index.md +++ b/sdk/python/packages/flet/docs/datatable2/index.md @@ -46,7 +46,7 @@ pip install flet-datatable2 # (1)! --8<-- "{{ examples }}/example_2.py" ``` -{{ image(examples + "/media/example_2.gif", width="80%") }} +{{ image("../assets/datatable2/example_2.gif", width="80%") }} ## Description diff --git a/sdk/python/packages/flet/docs/extras/macros/__init__.py b/sdk/python/packages/flet/docs/extras/macros/__init__.py index cd8ba5321e..600f4d6969 100644 --- a/sdk/python/packages/flet/docs/extras/macros/__init__.py +++ b/sdk/python/packages/flet/docs/extras/macros/__init__.py @@ -135,6 +135,13 @@ def flet_pypi_index( *, max_versions: Optional[int] = None, ) -> str: + if os.getenv("FLET_DOCS_SKIP_PYPI_INDEX", "").strip().lower() in { + "1", + "true", + "yes", + "on", + }: + return "_Package index fetch is skipped in local fast docs mode._\n" return render_pypi_index( base_url="https://pypi.flet.dev/", timeout_s=20.0, diff --git a/sdk/python/packages/flet/docs/publish/android.md b/sdk/python/packages/flet/docs/publish/android.md index db1661a457..9aa758b21c 100644 --- a/sdk/python/packages/flet/docs/publish/android.md +++ b/sdk/python/packages/flet/docs/publish/android.md @@ -173,7 +173,7 @@ If not, create one using one of the following methods: A file named `upload-keystore.jks` will be created in your home directory. If you want to store it elsewhere, change the argument passed to the `-keystore` parameter. - The location of the keystore file is equally important for the [configuration](#configuration) step below. + The location of the keystore file is equally important for the [key store](#key-store) step below. /// admonition | Note - The `keytool` command might not be in your path—it's part of Java, which is installed as part of Android Studio. diff --git a/sdk/python/packages/flet/docs/templates/python_xref/material/attribute.html.jinja b/sdk/python/packages/flet/docs/templates/python_xref/material/attribute.html.jinja index d8f6824b16..42da778a41 100644 --- a/sdk/python/packages/flet/docs/templates/python_xref/material/attribute.html.jinja +++ b/sdk/python/packages/flet/docs/templates/python_xref/material/attribute.html.jinja @@ -70,7 +70,7 @@ Context: -#} {% with labels = attribute.labels %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "labels"|get_template with context %} + {% include "labels.html.jinja" with context %} {% endwith %} {% endblock labels %} @@ -118,7 +118,7 @@ Context: -#} {% with docstring_sections = attribute.docstring.parsed %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "docstring"|get_template with context %} + {% include "docstring.html.jinja" with context %} {% endwith %} {% endblock docstring %} diff --git a/sdk/python/packages/flet/docs/templates/python_xref/material/class.html.jinja b/sdk/python/packages/flet/docs/templates/python_xref/material/class.html.jinja index a5f8ca5c57..6df2c8bb5d 100644 --- a/sdk/python/packages/flet/docs/templates/python_xref/material/class.html.jinja +++ b/sdk/python/packages/flet/docs/templates/python_xref/material/class.html.jinja @@ -11,7 +11,7 @@ {%- with backlink_type = "subclassed-by" -%} {#- YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. -#} - {%- include "expression"|get_template with context -%} + {%- include "expression.html.jinja" with context -%} {%- endwith -%} {% if not loop.last %}, {% endif %} {% endfor -%} @@ -27,7 +27,7 @@ {% if config.extra.show_class_docstring %} {% with docstring_sections = class.docstring.parsed %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "docstring"|get_template with context %} + {% include "docstring.html.jinja" with context %} {% endwith %} {% if config.merge_init_into_class %} {# We don't want to merge the inherited `__init__` method docstring into the class docstring #} @@ -37,7 +37,7 @@ {% with function = check_members["__init__"] %} {% with obj = function, docstring_sections = function.docstring.parsed %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "docstring"|get_template with context %} + {% include "docstring.html.jinja" with context %} {% endwith %} {% endwith %} {% endif %} @@ -55,6 +55,6 @@ {% set root = False %} {% set heading_level = heading_level + 1 %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "children"|get_template with context %} + {% include "children.html.jinja" with context %} {% endif %} {% endblock children %} diff --git a/sdk/python/packages/flet/docs/templates/python_xref/material/docstring/attributes.html.jinja b/sdk/python/packages/flet/docs/templates/python_xref/material/docstring/attributes.html.jinja index e632774f4f..931bca79de 100644 --- a/sdk/python/packages/flet/docs/templates/python_xref/material/docstring/attributes.html.jinja +++ b/sdk/python/packages/flet/docs/templates/python_xref/material/docstring/attributes.html.jinja @@ -20,7 +20,7 @@ {{ attribute.name }} {%- if attribute.annotation %} {%- with expression = attribute.annotation -%} - ({% include "expression"|get_template with context %}) + ({% include "expression.html.jinja" with context %}) {%- endwith %} {%- endif %} – diff --git a/sdk/python/packages/flet/docs/templates/python_xref/material/summary.html.jinja b/sdk/python/packages/flet/docs/templates/python_xref/material/summary.html.jinja index ec6526f028..ed829bc63a 100644 --- a/sdk/python/packages/flet/docs/templates/python_xref/material/summary.html.jinja +++ b/sdk/python/packages/flet/docs/templates/python_xref/material/summary.html.jinja @@ -1,21 +1,21 @@ {% with members_list = config.members if root_members else None %} {% if config.summary.modules %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "summary/modules"|get_template with context %} + {% include "summary/modules.html.jinja" with context %} {% endif %} {% if config.summary.classes %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "summary/classes"|get_template with context %} + {% include "summary/classes.html.jinja" with context %} {% endif %} {% if config.summary.attributes %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "summary/attributes"|get_template with context %} + {% include "summary/attributes.html.jinja" with context %} {% endif %} {% if config.summary.functions %} {# YORE: Bump 2: Replace `"|get_template` with `.html.jinja"` within line. #} - {% include "summary/functions"|get_template with context %} + {% include "summary/functions.html.jinja" with context %} {% endif %} {% endwith %} diff --git a/sdk/python/packages/flet/docs/tutorials/todo.md b/sdk/python/packages/flet/docs/tutorials/todo.md index 770f08c994..6ef1a66833 100644 --- a/sdk/python/packages/flet/docs/tutorials/todo.md +++ b/sdk/python/packages/flet/docs/tutorials/todo.md @@ -231,6 +231,8 @@ Run the app and try filtering tasks by clicking on the tabs: {{ image("../examples/tutorials/todo/media/filtering.gif", alt="filtering", width="80%") }} +## Final touches + Our Todo app is almost complete now. As a final touch, we will add a footer (`Column` control) displaying the number of incomplete tasks (`Text` control) and a "Clear completed" button. /// details | Full code diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_1.png b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_1.png new file mode 100644 index 0000000000..8e98297a39 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_1.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_2.png b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_2.png new file mode 100644 index 0000000000..4f55293420 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_2.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_3.png b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_3.png new file mode 100644 index 0000000000..6e421548c0 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/example_3.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/image_for_docs.png b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/image_for_docs.png new file mode 100644 index 0000000000..8e98297a39 Binary files /dev/null and b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/golden/macos/code_editor_examples/image_for_docs.png differ diff --git a/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor_examples.py b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor_examples.py new file mode 100644 index 0000000000..ca7172e18e --- /dev/null +++ b/sdk/python/packages/flet/integration_tests/examples/extensions/code_editor/test_code_editor_examples.py @@ -0,0 +1,33 @@ +import pytest + +import flet.testing as ftt +from examples.controls.code_editor import example_1, example_2, example_3 + + +@pytest.mark.parametrize( + "flet_app_function, screenshot_name, page_size", + [ + ({"flet_app_main": example_1.main}, "image_for_docs", (700, 500)), + ({"flet_app_main": example_1.main}, "example_1", (700, 500)), + ({"flet_app_main": example_2.main}, "example_2", (700, 500)), + ({"flet_app_main": example_3.main}, "example_3", (700, 500)), + ], + indirect=["flet_app_function"], +) +@pytest.mark.asyncio(loop_scope="function") +async def test_code_editor_examples( + flet_app_function: ftt.FletTestApp, + screenshot_name: str, + page_size: tuple[int, int], +): + flet_app_function.page.enable_screenshots = True + flet_app_function.resize_page(*page_size) + flet_app_function.page.update() + await flet_app_function.tester.pump_and_settle() + + flet_app_function.assert_screenshot( + screenshot_name, + await flet_app_function.page.take_screenshot( + pixel_ratio=flet_app_function.screenshots_pixel_ratio + ), + ) diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/block_picker.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/block_picker.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/block_picker.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/block_picker.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/hue_ring_picker.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/hue_ring_picker.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/hue_ring_picker.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/hue_ring_picker.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/image_for_docs.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/image_for_docs.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/image_for_docs.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/image_for_docs.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/material_picker.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/material_picker.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/material_picker.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/material_picker.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/multiple_choice_block_picker.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/multiple_choice_block_picker.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/multiple_choice_block_picker.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/multiple_choice_block_picker.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/slide_picker.png b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/slide_picker.png similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/golden/macos/color_picker_examples/slide_picker.png rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/golden/macos/color_picker_examples/slide_picker.png diff --git a/sdk/python/packages/flet/integration_tests/examples/color_picker/test_color_picker_examples.py b/sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/test_color_picker_examples.py similarity index 100% rename from sdk/python/packages/flet/integration_tests/examples/color_picker/test_color_picker_examples.py rename to sdk/python/packages/flet/integration_tests/examples/extensions/color_picker/test_color_picker_examples.py diff --git a/sdk/python/packages/flet/mkdocs.yml b/sdk/python/packages/flet/mkdocs.yml index 5fd83b4ab6..22e33f0eb2 100644 --- a/sdk/python/packages/flet/mkdocs.yml +++ b/sdk/python/packages/flet/mkdocs.yml @@ -141,7 +141,7 @@ plugins: summary: attributes: false functions: false - preload_modules: [ flet, flet_ads ] + preload_modules: [ flet ] filters: - "!^_" # Exclude private members starting with only one underscore - "!^(init|before_update|build|will_unmount|did_mount)$" @@ -269,7 +269,7 @@ nav: - BannerAd: ads/bannerad.md - BaseAd: ads/basead.md - InterstitialAd: ads/interstitialad.md - # - NativeAd: ads/nativead.md + - NativeAd: ads/nativead.md - AlertDialog: controls/alertdialog.md - AnimatedSwitcher: controls/animatedswitcher.md - AppBar: controls/appbar.md @@ -309,6 +309,7 @@ nav: - Checkbox: controls/checkbox.md - Chip: controls/chip.md - CircleAvatar: controls/circleavatar.md + - CodeEditor: codeeditor/index.md - Color pickers: - colorpickers/index.md - ColorPicker: colorpickers/colorpicker.md @@ -761,6 +762,9 @@ nav: - Url: types/url.md - WebViewConfiguration: types/webviewconfiguration.md - UnderlineTabIndicator: types/underlinetabindicator.md + - CodeEditor: + - CodeTheme: codeeditor/types/codetheme.md + - GutterStyle: codeeditor/types/gutterstyle.md - Video: - PlaylistMode: video/types/playlistmode.md - VideoConfiguration: video/types/videoconfiguration.md diff --git a/sdk/python/packages/flet/pyproject.toml b/sdk/python/packages/flet/pyproject.toml index 7e75e9034b..f2c23501d5 100644 --- a/sdk/python/packages/flet/pyproject.toml +++ b/sdk/python/packages/flet/pyproject.toml @@ -44,6 +44,8 @@ extensions = [ "flet-audio", "flet-audio-recorder", "flet-charts", + "flet-code-editor", + "flet-color-pickers", "flet-datatable2", "flet-flashlight", "flet-geolocator", diff --git a/sdk/python/packages/flet/src/flet/controls/core/page_view.py b/sdk/python/packages/flet/src/flet/controls/core/page_view.py index 381de4ec07..70b989281f 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/page_view.py +++ b/sdk/python/packages/flet/src/flet/controls/core/page_view.py @@ -35,7 +35,7 @@ class PageView(LayoutControl): """ The zero-based index of the currently visible page. - Changing it later on (followed by [`update()`][flet.Control.update]) + Changing it later on (followed by [`update()`][flet.BaseControl.update]) jumps to the specified page without animation. Raises: diff --git a/sdk/python/packages/flet/src/flet/controls/core/row.py b/sdk/python/packages/flet/src/flet/controls/core/row.py index d53825f994..e629db87a4 100644 --- a/sdk/python/packages/flet/src/flet/controls/core/row.py +++ b/sdk/python/packages/flet/src/flet/controls/core/row.py @@ -16,7 +16,7 @@ class Row(LayoutControl, ScrollableControl, AdaptiveControl): Displays its children in a horizontal array. To cause a child control to expand and fill the available horizontal space, set - its [`expand`][(c).] property. + its [`expand`][flet.Control.expand] property. Example: ```python diff --git a/sdk/python/packages/flet/src/flet/controls/material/app_bar.py b/sdk/python/packages/flet/src/flet/controls/material/app_bar.py index 9519d28c83..543e2f02fc 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/app_bar.py +++ b/sdk/python/packages/flet/src/flet/controls/material/app_bar.py @@ -62,8 +62,9 @@ class AppBar(AdaptiveControl): control that contains a description of the current contents of this app. Note: - If [`AppBar.adaptive=True`][(c).adaptive] and this app is opened on - an iOS or macOS device, this [`title`][(c).] control will be + If [`AppBar.adaptive=True`][flet.AdaptiveControl.adaptive] + and this app is opened on an iOS or macOS device, + this [`title`][(c).] control will be automatically centered, independent of the value of [`center_title`][(c).]. """ @@ -166,8 +167,9 @@ class AppBar(AdaptiveControl): action. Info: - If [`AppBar.adaptive`][(c).adaptive] is `True` and this app is opened on an - iOS or macOS device, these `actions` will be automatically placed in a + If [`AppBar.adaptive`][flet.AdaptiveControl.adaptive] is `True` + and this app is opened on an iOS or macOS device, + these `actions` will be automatically placed in a [`Row`][flet.]. This is because [`CupertinoAppBar.trailing`][flet.] (which is the counterpart property of `actions`) takes only a single `Control`. diff --git a/sdk/python/packages/flet/src/flet/controls/material/textfield.py b/sdk/python/packages/flet/src/flet/controls/material/textfield.py index be7ded2771..b14da3df41 100644 --- a/sdk/python/packages/flet/src/flet/controls/material/textfield.py +++ b/sdk/python/packages/flet/src/flet/controls/material/textfield.py @@ -294,7 +294,8 @@ class TextField(FormFieldControl, AdaptiveControl): Setting this property visually updates the field's selection to match the given value, and hence leads to the [`on_selection_change`][(c).] event being triggered. To ensure the selection is visible and the event is fired, the text field must - be focused. Call [`focus()`][(c).focus] on the field before setting this property. + be focused. Call [`focus()`][flet.FormFieldControl.focus] + on the field before setting this property. """ keyboard_type: KeyboardType = KeyboardType.TEXT diff --git a/sdk/python/packages/flet/src/flet/controls/theme.py b/sdk/python/packages/flet/src/flet/controls/theme.py index 76efb6b463..f2614d94a8 100644 --- a/sdk/python/packages/flet/src/flet/controls/theme.py +++ b/sdk/python/packages/flet/src/flet/controls/theme.py @@ -788,7 +788,8 @@ class FilledButtonTheme: style: Optional[ButtonStyle] = None """ - Overrides the default value of [`FilledButton.style`][flet.] in all descendant \ + Overrides the default value of [`Button.style`][flet.Button.style] + in all descendant \ [`FilledButton`][flet.] controls. """ diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index c292384227..98715ebd7a 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -15,6 +15,8 @@ dependencies = [ "flet-audio", "flet-audio-recorder", "flet-charts", + "flet-code-editor", + "flet-color-pickers", "flet-datatable2", "flet-flashlight", "flet-geolocator", @@ -25,7 +27,6 @@ dependencies = [ "flet-secure-storage", "flet-video", "flet-webview", - "flet-color-pickers", ] [tool.uv.sources] @@ -48,6 +49,7 @@ flet-rive = { workspace = true } flet-secure-storage = { workspace = true } flet-video = { workspace = true } flet-webview = { workspace = true } +flet-code-editor = { workspace = true } mkdocs-external-images = { git = "https://github.com/flet-dev/mkdocs-external-images", tag = "v0.2.0" } flet-color-pickers = { workspace = true }